From 7de350dc3e74a5982c9f1c4ed548187b223b8b24 Mon Sep 17 00:00:00 2001 From: TVo Date: Fri, 17 May 2024 18:10:16 -0600 Subject: [PATCH 01/75] Added docs for new RBAC changes (#15150) * Added docs for new RBAC changes * Added UI changes with screens and API endpoints with sample commands. * Update docs/docsite/rst/userguide/rbac.rst Co-authored-by: Vidya Nambiar <43621546+vidyanambiar@users.noreply.github.com> * Incorporated review feedback from @vidyanambiar. --------- Co-authored-by: Vidya Nambiar <43621546+vidyanambiar@users.noreply.github.com> --- .../rst/common/images/rbac_jt_team_access.png | Bin 0 -> 52492 bytes .../rst/common/images/rbac_jt_user_access.png | Bin 0 -> 53279 bytes .../rbac_team_access_add-roles-review.png | Bin 0 -> 62171 bytes .../rbac_team_access_add-roles-success.png | Bin 0 -> 96851 bytes .../images/rbac_team_access_add-roles.png | Bin 0 -> 52856 bytes .../images/rbac_team_access_apply-roles.png | Bin 0 -> 92307 bytes .../rbac_user_access_add-roles-review.png | Bin 0 -> 49020 bytes .../images/rbac_user_access_add-roles.png | Bin 0 -> 51487 bytes .../images/rbac_user_access_apply-roles.png | Bin 0 -> 71864 bytes docs/docsite/rst/userguide/index.rst | 1 + docs/docsite/rst/userguide/rbac.rst | 517 ++++++++++++++++++ docs/docsite/rst/userguide/security.rst | 318 +---------- 12 files changed, 519 insertions(+), 317 deletions(-) create mode 100644 docs/docsite/rst/common/images/rbac_jt_team_access.png create mode 100644 docs/docsite/rst/common/images/rbac_jt_user_access.png create mode 100644 docs/docsite/rst/common/images/rbac_team_access_add-roles-review.png create mode 100644 docs/docsite/rst/common/images/rbac_team_access_add-roles-success.png create mode 100644 docs/docsite/rst/common/images/rbac_team_access_add-roles.png create mode 100644 docs/docsite/rst/common/images/rbac_team_access_apply-roles.png create mode 100644 docs/docsite/rst/common/images/rbac_user_access_add-roles-review.png create mode 100644 docs/docsite/rst/common/images/rbac_user_access_add-roles.png create mode 100644 docs/docsite/rst/common/images/rbac_user_access_apply-roles.png create mode 100644 docs/docsite/rst/userguide/rbac.rst diff --git a/docs/docsite/rst/common/images/rbac_jt_team_access.png b/docs/docsite/rst/common/images/rbac_jt_team_access.png new file mode 100644 index 0000000000000000000000000000000000000000..ba4f0ce40854b4a80ff7ea72141c2b46758562f4 GIT binary patch literal 52492 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 KmxV&QKK~2kxZINf literal 0 HcmV?d00001 diff --git a/docs/docsite/rst/common/images/rbac_jt_user_access.png b/docs/docsite/rst/common/images/rbac_jt_user_access.png new file mode 100644 index 0000000000000000000000000000000000000000..1c5134a0780d8d5dcd504a8345843b7f2e8d8f85 GIT binary patch literal 53279 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 z zw?}j1NECpZ@7n`c-;!C5(b``FIBHFWQSg_+4(ExQ7lt0jESD|>DO4vQpX6oUUPURB zc_>}7<2_+EibZM&k@o=5l~I|P!#eFO3P8M(T7k=;b4+yw$P4sY_k(wjytatryLp?v zouFxb0mZCYXvT5x^;|o5k{iW;IfQ)KjYw)N@+wWhxhNFP;FAsaLh4IaGvSj9A%2hS zwEY$`@Fj*n<|V`H2v9(6XSP%!)R3Q<#B)cb>jG}u9a!nEyjKY(Azkzm809l!&vT)< zkmYsII;2iR0QrFsq|l17R>u&OH^=h^CByOrRpsX>e+X4TU>+pJG#EqsZc`zOtAJsK z*6SQKS{UH9NC>K(@yh6(VVr=u$PY69Lj+)h`b~6XitV)zbz0l2w?FPNUiH2_L&Lf! z2mM`juQbl19X5sKTALTa5pAY8NSol9&hvgW0k=+ra>&I z8hF|K(|rqlA|82}rZR=ZJylUJfo@1QfzBPC2x8dAD$0>H+TII0I+MZnA#Tuy9}L+M zsSaKAXt47_U(&rp9j79b9kd)q*}NmWZH5pipq>20NZwR>7NTNMIxv;@c%5pr+d6Dx zNPzh1=Zi!IzEXUWrQY)AJy8Cmlk36zeNkja=B~;dwFBZgtoUkR^arM>IIH^F%8aP{ z8Tk7t1Qn!8z6Xxn?{IkAYDxS+R$UO5e{YJ}S@Z##3EI)FKMXVW?HfoL^tadQr3Mvp z4#fnVs&6rVP&Q)E+p5)I)Zo2W^j*hu+#5;W3PSSEMb?`l_B6#D(LKi>87wM=V6Z78WBCt3(%CyRyv+#|zr=p4iR%DfFA87Pf8-_Ue^pzq>}ovvE; zg@kU=>w4cVpV_UQtab*OVVqBKy$VLkwijeP7?3bEIKC_M*wKVh|kO=Fx+D=-K7O8Vvv{K zEK24Hy{xh?%QDG`EROsQ$;Vv5Tzayv3TWoBaj^bxV--s9x~zQoH{ab$WPU%5qvm*d z5kFchZ?EUgiX?kdpx=gtM3l(T$KeDeWuXDeU^4-Y)ToL$ZhMin*_P&y>iCKr2gK-{ z=4)|OShg7(L??t2jnjpl4a$3RVo;h{o>?y>J$g4V7PO7j@_~j!NJ0_p-GQwI@yL0Z zZ7-YQ47Xh$3?YGsej*QoJR#%+1MrB!S()NVg{AP%I_Cub@H^tq{)EQpMKWS`kmIBx z!64>tG$~jDD1?UG7NlBxmR)5;{)_LF7Q8sfiph>9b63 z5uZLj7{fP*)cn+Hi$bzo^YE(dwxWZwORW#PnooP1V&Pty-=L!T1D#Ph2%HE}wfeuK z^{Fg_d+RZ*0&Sn|c5IcX`Oth|4(a-*WEMR<;(erp5L;?ibo<6W^{^^5A)Dwv z?eZVFUKJP>#=)P=gK5P>CERr>w~36O7EeqJLX~ovlC7_E?JqlxL=Gd9tA{0N`$fm> zCkPb8SHNAPCw&S>aGb^cwr0GF0RCvIKg)JAia+Cl;F(`azK{73%@AFRik6Ssa8skk z_PcIUPE^)wZo2-3kXtTEEY=?p?jM9uzjBNOZ5wxoq)$Q%GpmVLh7=h?KQM$N7Xcz= zHlg}yW(i#TNVws5+VtAXDi-OeXl@m3jL3t}K$_8+pB~2a|%k47CoXN+} z@N%W;CQ^An;0VxXnXl&`Y0iH`G7tUkp5|pR?43X2&B;YSv5O{D@Y4B4ceqFK#oFMY zWSduXVisyekyyF`ML0BhWn1=3%&o1q`3;JGID20YPDn*ij4uL_Q*%wz$7eE>UP5z9 z+9~21im7oax|26z6F(GlEidB+b)p}Ot7QztuC`<4?fq}43j|;KNDI&lIDnH+9kre4E}8+26NfS9(oZU5sG+vZbeiR;ACB;hf=FH#zxPq|qH5P= z0xnD->Xk&`+uItUIz&CUE_Ut`r7$3KScoEa`+>{PJ8DCJyV#R2)XQ$E5G9gSTMlmY z186S;9AOBw6i^{-FkO^d{?0hd#rss|;`;?S66>*3vUp}#x zh20ktXSYqj!yR;y`JKRWHUw57?-KSN$)QutLB9gFuZ6Cq?>H?O<1lNISB&5GsLWD1 zkS`)Tz<_uO5T$XxQE~$D;#0rO^MJ;9;GzPtq*i>*# zlq+TpZ(AnTL&|I!sVlp>SU4#k&06eaaxgxrt03z{?AruB8J?4&!U@4i#bF#58sB_H zK#KLCOz#}B9LUZ0=G&XwyAMT%CNVdTHi~CTDv1Z_A#k97vn%1Qavv`evg;4LGpqOS z+>TJ`*V~~x?>7o;S;xE9Ak~AA$Pye7Y3w#45`s3d+_?w)y{Ds+$ppWG$w#-5Vikw5 zs=W!q@F`c$1L-mWi`iXT3b+GQu_(7pEC~7(oHM$T4_#8e*vca%K4&BAKY6~dg9DSB zR1UWKwc+kzQ$bB)UdESVFG*U71$u}a#OEUOy@A*)%j;2C_e{pdN&`fP-!mhxu}r5# z`NR;Iu6we*H&HzZj6b?Dbrzy3ol)0!9H+VWOEKTzQ!Hq|whZr~0N<+$EY(_^jrJ~J z1_a5=a>tRVLy`ne3uI-zJAEUUm#m@~2m_*gqfRZl@L5@iBm)_G%?lmhQFqYDEMgYX8G`uj)ROaxC3Q=)gt~me=*W@#}LvXM} zPtf{5?^!x+Xov7(&0PFcq-B%urwjF92P|+lkfi%mlSrtQi5taNy7ixhc_a|j5(4mz z;jloEoHbv5O+`h_$o5$r64B#5-pi%t$iZvA{}I)ryR7dOED-3@B|+1Z1SjFDWCAH) zLg0TM7NY#CT@fPd2RX{QkvAky-@8TX;)fa2I@$Go9l+rDg4f| zBL_c}9~cUc>YJ4a8(@yXWJ(GExd1DET(0}o`CbBN%U718hiu-)(X32NhQn~sCSrvC z0tp0+21=b@ob39GAdmaL5p9i9aTr-sUv}wzTMwv(ibbczLx0kf&PWm^C%|C$N5YbZ zJ7cp@eH6jKFtC{Vjz&vEZ8}7h?I)5$EkWRBj2R}_v;BQ=4!omq1WR@5vQk)#s|qa! zIc~^3*pr0aaY?D0_8_QjbUB=NR*+f;q)1A!!+>+Zp0B>UiEpT@U*rcxWoS?$%9?of)W*EPr91Y$-K`51j~31VmYDtrC-vFl1`A)I;>m#Ag70+d;@jNQ;??7 z8c6C>vY*YHm*KDqrAer+l82Jzn)7Ymztanpn`I+*0c}{qq$kHefd34;^a~KcA8q<& z=?QNK@%h!DJ_26CM{rdSn>zHTA+@9Jc0{T3wfXh)2KO7rIc<;hGiW{W5C|sXzs(Re$e_lD(o$ zqAkc1+g74&&sIGmt+c9=m>Tlj`>VJ*8U`5*NwF&CtQYZc3p#C<-bLOcVStC6ZUn)LBc&Mxq`9QQI*7gvY`?|eM3ZjTZ zy>1;T1jmM5$^@iqS?4GZT+BoAB<22$Dck~j8icIpFq+b?*H2n+`JD$>{JYCzyQk_TM}0I{hEyc-mg&_|@4ySYubjhS+=C zHoOVo4@0Ir#I}?9kbLgI%^UmulAy$ma-N*{Ff$Zq@15h}OyfY_cNK!mmNoBI;Z}Fj zwCNYk+ApocQY&&q(hr^M^1nXV{HlxV?`|7`33M#TcUtb{_WOlR`4dpcNy!$D%W(OC zMg=vG4u_R^NlPq+$e{l7Cn{WxP@L!t0%cNmBuJdEIMP*Qi{>RAq~fWYRH|@^Ve;$5n>GXu@eeV(FEmg9^|b~xJ<~ClE9#L2GuEG0L#`j z_%Vt;JX)G@?s!q918{o@rMkeo^bgga!`6`YI1CY;;2;@hMl%Ju?@@?fQxJG;*6b?5}j#WJ1{KKB{<4c5q*BSbgZX=57 z7nUxJ$9148^`FQ7QzbzdvTxuTV5pKvMgA9?;%_E|C=kqk$f@0qv;H^s1LJS@gSsCz z*MCS4|Bx@xOMkN;W@D6_{-ggtrIK_3J{_K5N+;4sR9I4TeHSNTU>K(|2k%gRPpEk( z9fdc8cW6&OG(OHS7sdMt&9AFVcsfau&$1hZjf$$js=AtvzD!BPyJ7N}?Pa~u?aCxP zDrznFQYHft5s{Xi-8pJVO^lfw4m+Eo9b7dJ9>1}4p2RQ`lT?TNRl1#E%Hw?BNn;Fj zzps?O(Y+S>J6|Gw`@&NUOOyW7LRkf1i&~b)#bg&?mbw1t`ae1d!sLYbjv0N;s8W*p zHx+XbJ`SQ!_U88d3+qYn8q7c+)h28deQf|UbW|{8;zKIIi4SPXkm6L3 zDNZsnGLDowKXLp%lPFAEQIe8&#>B)Fy)SHh&^(zeS6vFvq^*C7_+AK89wQ^JPjV4j z>D-%4csxa(s$ofZ)32x=C{^``u>~m*dQ>hOr3Cs!>AAQT4rfa>4;a5(b%IGZb0*iu zecFX`gwm3dG}BSmrw=d{I7352Rqi*38lIj{8JYS!3NX&Gf+i*fYD4)21*aw-H~8f6 zOQnvgzq7EM^r9s$^r2LlA(g{?=~I(CRrNSb&rg_r-wAhDl-#7k!lg|67itvP{obw6 zD>^GVs{IYmtQ;8C^1<2&4JF1;ahkIC9nD_>JkWG>8%TuPFf@ZV6r^Bd)tP@gXUtvf3LX6}Ks!$bFLFAoe`D;paPR!eoCqpVk~ z<_AT(FNi9=xm`{z@j0xKfnF^+m&euPb>Ju=C++Ly`@dHKuGl+ZqueE__0ImpGK@%} zS~Qnnt6w9ST>hNbvvgSE>EUtd?lIO%#==5Ahls>wtVFMbK&xH1OyqWquvBFnuI>2Z z<~%?OFaAX-p8gN@{P>atMQPqH)>~MB*W2Fe4J^#Fi}hPnfjb;AeCZyijklM>3`+&F zsY9|RpFgLhgZUdfAmE^gySw}3PG1E688eMZ6)%`N)V=myg6M#Xda2H;Iv53iu|%V~ zM7!BJAtpxYO<%?}1(?CLm&nUzHqj9i8+(^`d=i5jm~(t=33SZt)h+^V$)4(XT9&BY zmul4x9nO{8pe*aEG#OiyE$5kINlVF;e5!bOct|**oNNeea-yqL=bTxowU}EtWV4{9 zp&4APGq(bhrRW)MmQ5qu=fGq)sb0bZv>h&X0A54W#YKiJxSQlr@f=Pirly+edh-q4 zBp09LWmBnFn~dhrqh%vRhDKE8g4k;XGFLhmIf^tLiyM_$wYYvXYOkL@Z?ziVZX^Qf zsG2MGy~WykC@ncMXh&9R&k#@wHNxMkg&lH?$P5-n(5gEFoRB+MIC)OZ`X;}`960H4T zG~!d(_Nl4WS_`#iFa-ik!t>5|qqzcMt7++jfm7QgN78)JhyQ#x0lzzzZULrbl{=lS zuJm7k_Z>Ypwo@$1D&dJ-JIY#bKl7~nLO+oMet5)?@Ei019>IHEv>Y6Ba47GLOw;Vd zsZ;n)A3-;~JW32pAb=-rxkhk%yb1LNQg!&XAibx8Kj*zr=EOp#KFJL{$4RfO_E zIkPowj;G$l6aOo3E5gP4;!g@!xU@BshHW=D6%tWiTAy8XN|$QPtGzCMQHR?Bt523{ z@32lM4f_|&yXBbQP;IrP>$SIT3lnq1+6}ce6Fd)7`NUJa?fdBZT!lc&z(VTS+6MLsSbCCi}P z(0;znej8t%k;Lp~b)i=x5%mdw|Aok1zmxv1t97EtE#`)&B-HEtK&o&Zykl7T<9x10 z5(_CVY=rJFH%v5(9q@5b0e?nhQ$|*{R@1t%S3Oy*f?+UQ@BYl^)#CB2-AA&%yNO-D z3#J)#SX^p4o-tcvKCPXR%mPNNcQ*>hef%1hJnv*G@@yWH5 zv&}V`o|}49X7x3ra!k&muiiQ|mB!MM##YPvT-R%b8P#ne#9!cv6drC>-opL5kx88` zJ;@{OZ;oG$A44=Bv$iWMV?9OKFu-=W3>C5J9a6d7TFyM~P1LQGrf=!^%-t(j(+HBq zI$6Zh#HGRe_{t$*Rh77=`Q<^P_F4+AjLk93UwT{ZzC&kd3ss0Y`j>FVWG;QZvHzo z=*)aU+51o5D<`l+C)>z%H`(+!KT+pHK#nw2mA0VMppzu>D%oL#q+NOPPVu?ywXxGSwe?~hhJrCq47CD@4zl$-HQ(T zH!`(rXzXfC)qp+F_O21WkyWOf@sW}1OfW6+N|^laBmRs2!W2S$Cn74;&6oP2kN0`l zaGf9Iqd6`+i8Vz4)$9Z^KcQJnM&>EplUCgJ(r3YZPVG~bTj6x>N}Z23IwL*!x8S8& zb%Oy=FRmX*uVJO4V)Ddc9v$s5I zILbyn@&I8GaNw0pq3GbS zgXV`ShZg_rjAC4;v~%1UBwnS&sx)SQW{G6sR>V3O#WMw&9&R}r8k81T^l_ucE9H5A z-8v*P=1ZGTeO#0@*7(vb((*sZbzTORGN?s^c5B(&mB*Lr z%#G)CoRay@Ck$_=TVQW5Eo|n4#)JzssTJFQ#UqCtuMP00RLr8DXsvyAl&uw;76E;p z%fN3sLAtOW7MDZ7!Ms_<2{kg6RNTkJ+k1-J#$0)}DwB!9GSJpFtaBF0=;am@%LQHV zFUxXSKlbi=rtHzvv(xt}rMHXj5&LM2Wj-7HnC#FlPsFo$h?G=y_S;FU{$-fhVQ~-j z{PWeJ zstNI%OUCZ~{ibSm{ESpWP>j@F7gQ|RX zk6BBLwYFB=8bzsZ@7RVyLZ_fRuPVAxtC1L5CV)V_n1loq%L_rlH`CKvm)CU+q3V7Lz$9suCFI0~;?D_HLLKB$4B^`vu z|88TbxfJUY;<0GGY3sdnAi|ow=<2N%R`z30XaB-ywT!Ly$JO*v`wR&|FVBs9wc{RA zD)s&v)NFOB=P^~pF$*EPtI?#&gxTx>X$#!P+hzpz?fb2;3=#ELP-Cb!c`?U%W+V)^bxNcx)JHI@g#8RjpK6uS2N{GqOkbfoix}Fr1 zf(tw`jN~d~;W&88(l`y_HmrIUjy|q|zrBD*UkJ_E{3DY9DxU6X?GeVHp5{2u2 zJ)m`+&w80t1fvTFHR+rkbqM9v(oXPuCr%`=yk)8a#_c!5tr0V=x`P-8N}aL7cpJ$NYo$CCjq zj3Yg7+6ZFs4mo4>Q#HlLZQ;XE@VV!K$R=YMh7Nn9)ehra)0~XuyB0#!`g*bThkyTIEsU>UQs0-%jBG= zL8-uVeeI6YZn|~Qq(;5MVLNMk{Vtor{0okVse|S{yL#eS`Y|W2wVpP5V8}0+S$qki z$?;U6&+{mr$I;3ipBnA^22`pyT-~pCRh7mtumBt+b2-1R1%%?XUnpVUyuDDbG=(^6 zUIL;d)C+(NwC@x$2)3u(e7D3i+H4WCd&3je@2nr;1}99}Fp{pev^*Yu%?+V=fxc5ELT#rhMiS02*=E9tkN#^9%4pk^QFVmJ!Fs`=fcCSdYgEqB_nHD(xxDBk7)7e zd1_l*LGep5n@V=r7q`7j9g;|+OIdarU!Lw?GusCru3}N@S^;Cb)J@X zVd1AM_=^+)noCw!yPK^8`E_!24Y%&VVFU9XxB z(=pMke&$%IB?cl=3%A*>(>-(gG*?s530LJB{kcLGrTN`Me9t2l+`-uM5PE~<4I_WM ziMxi0p1;$eIVcVx0YI$dj>EyDJO-23Fa7z8Pr(#`*AY#O7VskFg#!y;rU~q&oGw(s zZgd!gT)IWIKW)*%s5Vp@tUG0_d0s`-FO$%LU&m=j2z&&TwR+q+ z$Ij5t_7FPv+R&0$*y?%ou#EE?23b#i+l1`DisKuK`>4zu%|D;h;>RKt3yu^&baO7W zDg7!F3V4IRdwU93TzVnbKtEg=@8_5egHZQoB%NnmGx?iK2=eCoup2yZIm{*p7%m=> zsvfR;C@HD$@R3NEBeELn<&vY`c)-eTzCT^)a&+G=VfPRw3_0-2CdwS?ld{j|)+UOW zu>IxOP!$QmBEQY(S0|?rlQc6w3>z}jHUputB)o6)UuDyE)=S70xYxN|4)-jMkwAQfOMj_KaX1W>^6RRxyf`tdu2~= z68v6N`6pp>Tv_YnYOM~XEZHyGkGsT=&D^HWYlXn$T9?E{b7;-QOrKWm!3C1!2tE@e zRU0+#z}S8~Q&PJJMLNsHDp}`CgwkhnjWMzf8JCG|5eE7ia*u*}>8;ttnEce|)>`Xg zsPfSsCo8q_d1+RsUHA6UuNL8H=ejX5ejdO?6HPx4e)`Hti|#>oyCJxxZSW4NNh9Nw zoRx(vH=Av{*49HW;VR3ol!nrGUTBub5?$2ddUGKUHtO)dx5lCHS_cAvkO-B|Cd7qF$_cCm!M*EdtkUiQvV3UxUh>HI6()X zO0)GBkT#h4p{N6?s7g!i3)Sxe^J@o~xTJe_UtI4kIMEja8U8hfU_1~}ExR3qU9)bD z%c?sV<;omLQ}VH)?*(*J);y+ZpT+wV6&Cs-5X;|Ye*qqQ)Te~y%;ty|+5KcLcYiVf z?eGLc$*QCg)G(UWJ6KgNG=AMuQY6VYPSGLbu_wxdx1X|HT-H-*_-C6CL z+$=6NP{DRXi@Dp8hu8aQu+Um_5YYu)Q^4k=!u=+N%wc!a@5FVew{}{#-e#GGbe%A* zyTYfO=%oc|NV&`%p~h~HALQz(_9@-C!o=`j(QiR3bO52h1^LUnsMvUG8a!t-o;?zn;tKFfAx+QVn1{~2AKen! zyh&$~r}4@=DqlN2!t$8yitbo>{>B~-gE-t5x4n!#)hyh5I@~;z>~!o4z22slJ&PlL zDXh9ty%h{|`if7a+AFsw25*a5o`yUMBdu%437r$8(c3ot)g%pWx*Cx(8%i+=m!0tg z_rVb{PNAkkuR~e{S3>*{an(X{`JhvU`VOblfqksfulFhQ1XNRD(3q3_r|&6C|1x*K<=8m^4C22tAv2CV3p4VO+@^VhokaW zyGtzN?d$b-*Y=t{oTlUzV+aCyi+5Z;LJ6%8D`Umid`s__6hqi*iPF>9IvME45%$l# z+v$%*h`DSw)Lmw~&TXbnjwXwpLHHa#)hLI*2HGCYbJLv~s@?7Ph}SXkXKE9yQ=zRF zhUgOZ`~0EP1iHfP!lF<|hu5kqerXuzm5ff$}tUPBQ9WsYoV54=R`?7kL5g}=tDZs@W#d*u4e1Ky0oKB{#T>B zXSCSkwnyTs=F`@-bdf8s_f#t8e=cF>0}ulDukO~hK3J%T`^G!Lh`{)u8u0F=IUGM- zjn}_Ew!dEaqdtxmz$k$sFX_~Sy34Cj_wAr8W&NTk5*%hf%W^ZiE1sr{{x+lK13_np^WC7Qs? zJmDh8G(G2?CC@@~Pn0cu;i#j>QGLT)$M-sYDzufI^`WqyZa1{F6JIjQg;pK<6+(Iq z#s0b+@|8yPOrM8Zuh(wTySlJZodw1j!mzjM?4MX=9?E(BP)@DhXMTI?BGhVLw)xKa zI%nzztFa?e(n_@ubG7^^ZI!C&+;DN9z)wnb`^7BaR@Tl&Yd#zK(cYwh@9x6hWmV1< z_&}G!m7nXeVsz1s6RJ0}?Zn=b-g@2}S9@EdEWGR{X&UYn0+jo_v08U29k-z2=6r@P zeY~h!TWtYfU*%{jgnl#?o2pi)0*&KrNL5y=ysUvH|J>G$%_-LT;m+ji9>4w4w=^iS z8RODb16?^y8;`AL(!YNTz=yj&cc(FcO8KaN0cbckUysjB`z!wyO`_frUcafDOpAZj zQQzd%o?pxi@B6ja=JwB1--Q!Za*H_=QKhLkoIgXOtzQMdX+|u#^_1?8RIB)5cgCRl zot5&H?ZDNe^R#3tF;Nv&t7R*a=Nk*EUX9sg&Lt5|(>SDk zr}slnc)RCDmNcp)FE|_;w)WwUL*>0=e%zYFvc6l34vVWHgel><2~1fK5uV1=!o)z> z8t^4m4Y7-d^P2Iah@DTie!?~34itf+CyajF_8It{VD?qPu13J*@w~mTwqAU5QIPy1 zLwWl4uS6kyamnc4Tk&-WK2H49BK&r}!$E4s{GheM^GaPuY`~)d9leT_hA20m8pTCfGhK~r zh%h(r-Dwr)dY&~s7s7H+UUvoQ$Kju)rSUwOq_+4Db&KudB^4avv<|>0o)T~W$M|Q_ z#EMq<`AIo{q)w-*26 zb)lXMclp9RYDcNBLk-~HhX_ySFni<@k9TV$8~0@U@=YKs&Kn$k4>-A24pG^*d-!V; z)9(W*x?KV?^}8;oN5>P)vbi4@{gA>)!?4)5~7Gr2(!UXW&$zOQ%`mk&;m`~|0>zXA|cu918pt1Tt?7ekZlw12YOiGD@ zfCz|S(4o@OE!`;%A{~-S52cbSCEeWvGjvG_NW;*fC_QxNw`TwL^SpcW?C-z#J-#@e ze>g@)?sebmUUi+n7_VrmyX)W3BVt^$(z7s`6M5&(&%Z{Fp}O!#E6TF8@S)=Jg9mWO^WUiDf*P_)q2&2 z9jU?eGn`2b5=U{XgVrpj*oD4Y%k@6TJavXMUS}+mW#>(Lm1Q{7_av=G<3@LNwT*x0 zy5t_BvKT|R^4IE==ernES@8q4wYrhC&3eyoJo`oD05zI)i=uAaFXqR4Bq0Cc!u#|M zokST~Zq`w4bt{El^_arXH`4Yvkl2Wx!GMU$;yymX`@$_1)33GX{s+PR^C!^l0;Ijc z>mj`hT;^Mhf*&VO!ERFcXS31K&$!0^DG3Q`L&eD@ zQ)$4O)*W*C+wcCJCoek%iM(;|V0^KEC2uDF_*rc9U%$zSMtf{9z9_x(Mdvc3 zld-owCDFFqyztcvPkVG0i~qSk-L1TBPKr0YZ%PFA1%-wA57l?At7T@IQdfrHqYd`* z_MlHyRE!gO9iaYS#X}=Q=ju!t4<8U+gA|49Dyhv~)V4mU--O5cf5ixOU5b1}M~8IN z$-7+(S9Wu_E(eWaf!Za`zNnp2C}j3SmKH0*mFzwjGRp)#{4+hODwfs}bG>kAY`kcq zA`?T&>VqxNcYCtwzhqM+<%c+Bc-kc=;nK3%u}#-DL~!)PfGwFZt{G9>R2lE&trh;R zFPUF6{%fso+%>ng8moifzxLtW&tC)=Fs0Ajq${xxBixu8IdLm!I)yM{{h2j={{Z9Z zR8nEr-Yo-#XaZqwAqv7=uLn;Re`M+T6r`Zpv07RT&LRp@BE(4{pYhi5ycsj!M&B%@ zBabNgx1on58H7@sLz0Y|PV1ju1g$N`C-YqzH|THGfBbp~WMqh}=rZ>Ho!k9iA4H>X z0Rn{_fl}B1V!ix3&kK=x2nZBeG4F@|)q{T>C#rHF+04ksN2m7>tv(1xrIxiG$VL8B zYMBX@TCSXrw*9k{{%T6|o(q*)e(`}(=&z0a`I#sim0BM1SBL(4y8W*?LHpkaMVY+( z_l<%9{Qt9|`#yBYWy}&f%(;tYj3SDo#DN-3u#k=vVMmbfO2jz1DwA{$_clV%L z#V?R9O_emj1P5gev)QDSyqv$A<=OR_POHhs+hziq-QLj=8!Kf%4mdemU{}*oQ~OLv z$z@qJ^f5KnVP$1StJF})W+ZRWK1eg_ro$AxmxGhj4&(t5B?bb(Y6kY@%a;TN|DU%Y zYIq7Mf)n(L2`_=!&8)JiX$CdGWf2XY?kskKZJR(FX-CcPQbxdDoi9uve{wNaRr-BlwNd*ZchdB6L0KzWT8_?3G#yx z;uZO%!6rLw_kz18<(Ojz>IC2TFrkr>k<^^PZLqWU~ zkO7bZ9<<=lth?!OQ#6ANqwV(3_F!6=*Rc~|oX9ImmsU%rDPW-d8&H-qV}ro?W*i8^ zZGmtt#T&?KvxTc%*R&D^+)JL`)(*-vK$%E!aB!Se=ad&Q=P_R=&eO$!zRbgzJ=Uw5 z_>#&qdGwa5IHtmyuSJ))(yDr<)}*M8XMf4o=C@C(m#9P`6_wd=tbO@t;!ZesN6pGJ zH;pRN#L72O$s={;4MJAMbRC`5o8hc6}FA)E?gz#Ho14l$0_MLCvz(* zY!zRnxY{OIid0+FZ1}@JRd5u)4d4^_?I&$-{?c=?qngXB$+5!KI@Y*zU!nLT_g7zG zeS3#6eUl*UDcd8Qp7rJ%T|5Ip_Zct%Gm(z2o#5^}YBwF#A)O!kO+F1q++mTCRX_!d z&=YW^9s~Mos;8!b_lvk3W~YO%Q~YE&->lz_P>knjH}p9+p924}$oKL>%7gPONNfy~4ny$LyzM z5^!qS|M6JAdO-OX7{BwWOPuelN!(p?~wk2dE+T?Y_Vj5(Uq;TF-@2P-YL_U;@rk;unSj*>dN*@R;G{ z!Av_;zTCTLN2_hLSZ^#h&e3uK%2$9ZbX<&wQCW&oc?a*HwWHhJFmHX`NxjsmLF%dV z`1)PG0l=H8m;bEd!FkVAxARVjris+u1#&YAGaadpmXNmlC0EikzTb!i%Lcg`&0CKhFXn67mK%SCgF{VTx8=pdu;Hoc4wctR|lKQ+7s#ytZ;fH z)p%_f8_%FY9Wu-VJLeI!?(Z5JhxhKip@GB4AHlnF3xfFIZh(0lTMc@{h}$ME)C1mp+eiXTVr!JpYlp_PB8`qgb!5ugZP$ zhl zui^cB;|I7(SGaD0TfjlV(YS_FA8?FuaLZPx~? zWd%OoMV}!Byg)v1cw2w=c98^!u8bHK( z;5OU*%B`}(kI{vs@Uew$SyiJ_6?R_%`q+@JqL6!TXyDrkM;S6`@MjHWmjq)+D#>p*X@i_DeTy?PsPQ9Y5(l9m_GPr%sBsb$BKv z(~ppiO$a;>O2U=AhaUJI1#uIQDX3iR<%+~qd-$vP&-bTnt(7*-ZgMtkOs=usf7(*( zwz689iTef^e5?aktvv*KdX2eejO=Y+ux`QU-i2hKIS&D4{$zE(9IHPOeJUU6tt1MF z?jA@~+Qk|9)cip*Y{tC#Mha_+%D(aUD~I1(aZP9KU|+Q(H+++BqS}(1gu70NrBW{h zfL@=Tn}u1po0U|o5tdcCNlqbEca38zfQtL#J1J>U({ZoE^_He;iF7}-K#IdnpF@GB z{(AS#*#ls%@+KV*J;Y7slrS(0>|c{U`-Miwjacn8dZogbXU-@#UBfXS#_*82JTL{> zELWlyc#Dl~CAjnLVZ+z7H70`pVd16>0?WZ>z$%ancI>WH3M1pU z6F2!0ao#&|JXO@3{9(A!#2>bzIkw2wQ~q2;&uT&sAE{$8IM9!Gdc09r!Bt_=sB3p@ zUne%k&II@JPKkV-8Y_!<^47w}-=3{%lR{as*rxn-xp1F*@@M0uDR$LTpCqzh4z90n zZ5}uiuksN-)?=Y$=uBUC#Si6cqRvzemDH=Wtgv%jVk**4wb@NJ$Fds#Ts6wiBfI7E zwF`dsUX+X27<)H_rDThKuxQncSmpCV*frAYd^hquR`YYumjLWxx7P366Go%7PKzGv#zNWM4qmp(gNBx%R=tgz_&vJ%9k zW%L?XOFxbT(h!8!&3(hNJp*zOmNuL%Pd?Yz$G33ZLDnyx2O(6cK542OaS)t&ZnEOH zL)7yq+LIBpoN=cx8`%F8e>mv9Uv6vkb(_^>D-o$2WWRN}+EZ%lHS}gKxF#1)iJ@wg znS$1hM(WOZXZ&m6PWaOp9!gt*ub&}ol8kr7sr$=%{q6j1nM`e^WLt8hucv428DJgQ zf>|A?+Q_s&{qhm_>@0N?B4Rujl3jtJ3uU&5!-H*aEgI_a2+Yd7Zt= z^=GrU8tZIwGSS+i=-A!iA!xEcnv_I>0J+I00g3x}tJ;}3QYc}QN^VoEeS$?F*m}Of zx;S|Qck>R1zcJBF@+g(j6aMt3#8-*tJD04>nd%S46`*}f3Sp%6Yi`lSv)ZT+aHM8Q z{vwk}BwF2BUm-{*VP#^b>1NGJ8=I;*!>| zB=2>F4SWM1A`jf>N>~{|Xt#0hvUkF-MWf*|@-+^#=PlSiHP##aQf_9ug%{8OEqQRf zP(*f7_E|JtRIm8`+F<582eYC9Cy?T-{J|O>f~(9(ubTCdW|GMcE*K;$HN@ETpsWr{ zCCx0k_3cAjnl_F|_*Y#*^<2e$*SOlqQ4B4q^avjR1x1odP13g`4)E*2YnJ3;J%XLp zIc!otK1YcVzrXuOr{7MVxgBgoJvx-)W08+MouMowAvbeP!kC8>2%O;c5g27pEu@x| z`D~PR#BY4;OxbX3Yn`6J9!njay|@_h5-=Z>o?0xzrR0&VRJ|N)UFGYQxSPv$@pF|= z{zG*&H46tL{m+^6TJ{tPz*u1*;i$)MsvY@3lV^Loh=}eHZ;aPO~%RPx8 z^3+Nx-myer>c=cO`Q#gmiI1CoHp|Gbb*KJHKJqv|za@>8Y5g?O3M1(hM*=*(9ux)M zgSz#wRUNhd;#(&~evqUuHbLCQghGFrEvF-Dx9l>uGVidzZXu-grhd>4KbK%NxByc1=}ta-YFKDr04&Ecj>;dU7*p~yolxF^!|5nXVMR6=fJV)P@Kdc zLC8C;l77BeblurnID&Vc$iXo-uqn^?fl4Tr_?j@nEl%I*!{tKS=&-WLOykKai$kv; z9p(b*RHWmGSxR>JjL#`@mia@#XA1eqX9ssYkJ8_@_K{hnq`ho5K^MCu3QIJ>c@POs zYA`w44yz&eb@aT^EJ>eVB)2W5c%7$@@$5OA>OEV(Uf4x_@7j%BflGw}09xTVo|o^@ zb=wWS)!hA29h4ouJYx$@2EJ}YvcdLWzv zpS^Zy{VN7Xq}t_`?f1ielKD68w_Cr|P!;RSB@s^Ywl{prFny=sU1uCs(Whftl{7OO z;SzfnyeliLFM(yeR8il5PCWvCx92U6j0AjGx1y|++B&wAM=8#X`FpAE6K$yyg;DYL zrLsmEMH4|5>an%#kIZ)VK=gW4LG$DXJ*$?n45oM+@H9Mo6gGNKOL)azmLd0bmCaaR zqT$0|jypUgJXkkK$#5kY?~cD9E_rCHRJc?oNbgznAf5G(*v)Sz%!6{Z3~T<^$wY<4 z)Y0~W_o*>=47|DPfRWfzfNzpRONOscCQWfjOT1r-_HHy@eIw5@O__c(|NDb)Qw)48 z*y*?gjFSXkN!}?8kvrbQJjX!B4)4G1!Ey*Tp~C+0S__k<7W3rh)=J^~QKEB#kZZG+ zVvV{#=%_(A;9=ciD1VT#ffKR&&Qe5&XQ0IYbqLSkAhp}~qkwkI{do8XXkt~%0K(EJ z0Ow}Oz?;Aw|81h@^oq43*+Bx&#c@Osaukee#JKO! zNQCGuimu_D4z|8kLa~0WW6{L#*pdByBq#O^KqJ&xcY0J7)4iggUO435BoNC70OjL~ zi5+786DR!>Z2JQTCRQ-+6hk9UlA%-l2bfQib_b=-IyTQD{Wn_q7jRJnrOsjwk^KOU zhJVHQP@f^+7(lbX;y$S{{zJ2>D4*dxMtVB6e`xhVDhgaQl9o&VCvXuP1ukZMiI(|~ zCc*jC3oNRVJ`_wre|^S3KNFFmz{L<#wFmzMF8+V+L0`wnn*PN{``@Vkzdooc(LQ}z zK5|z>uB!~#^{Qcx5dK4t5djUPXq_8Uu2=s%&yrk2A=N+h9JnMAJ$iB%5k2GEKbg3H zf8mUY;EkE@4jTMp3r|pE=)Zpok`4durvDi?kSBO;K`W)w^=w9sQ1R`N!Z=$(svNCl zinsrirm&<#DGjH9^aB_J>-va6fEBlnliKIFaNy@{Eu=@B{qoIuaaFNJ_>M@A*+x#tT&0f$H#M9 z3IVgp%{b*gm$Kl{z}%ibvxB>-r`yfIo##V2oI=_4ciIHkPOEgKn)Xp<_D)#94$>Yu z39C5Ws4I0s*OwL7T*6=!s{Z1b>N6m+20a?Lw-)}I-=?Zuz-o7 zZ26wvXL5nS9)I)KO%l}$M;5kdeObL(cxJ<=S4 zipT%PN&xof$e-rFzJ2NtLhVom54R@BjV9uZd@G{-?{m3yeAirAIauE!XZ7B{@!Kaa zHQ|VzPJ&zIV!rPB>7jr;8?Ud8>T*?M&B(nATg}1E(>~=2-qN)fdwND-`(su@gZ!Mk zyr?4A0(PAig<42UGqRh->kPp22)piQ2ut^YM&As|_?o?O`KOcD3LJO+E!Hg(669i{ zgBuI$%7w$mbPb|FlLhDim%{K=b#GvQ1vaNzzp#HSrCy%pHthBZT%c^BkpLL4q;jYq zG&M5|JzEI{*_hvWMfx>EdNeSr;STArJF3Tids!$aeqx;p`qmu9R1+P}=p{t4k%M}U z5=*}xY$h@fd22Mb=Lx1^e7wU$i&v8&qt9Yh7&@-)+rPwdmw_w3)w4T03)kZ1>F7BB zVt(pg%kqWaxx0+{>;+=w#G-Z4Q=FJfUM* zLGA#b{#txcU56;O!JLX#w|wdf*PRM>ZBKT?57#_heO!~y4Z^mlLF)`+5o#8O6lHK0EJ~%q&wQ&>{+SS!1{RMcQmK)W8 z1VAY22!ESh-x8o`u&}V}O)o-LEP#W!Er4uiB}vmQ00vqGETBd^74I1N9A|?mKkae2 zVF#@1Ik3_Ji-`(x{YwgTmD6hI5zVX42x?@KbKg^iZy?rip#Avyv#Iy#0c?Arlj@!J zUt$4*_JN~39m8*Bw=hUw0R>%&TJa!Qc3eigx@`MkQRpx$#g~x;yiN< zz-OJ>A)7La9!U2VkboF{)`NQm&8+dyA5~hrNs1r0D%EAbLGzL^6kPd`>hAmr13z1X62LC5EP2l~nw07%O?fRr* z_%`pKIWHc)0kW9|Z6{Sx;~$QIL#@qqy7L%ARUYm4EZ}Jr%SO=~T8hdNk&|1XSgKVr zwMh7sm!2s`yKCUBNkdDjBE71TCarm;(Tn+joAn$|ts#s*9uS#eU8FXg`juwLo$_5| z;fQahljqBQdY~oR7LvwzV5mI?J%Fy&me}YSOcmPbNwF3(x=EoE7^9nV=M z^r(Kxcbtm-Ddf@gbfBm2(k@=jVc++7fw>;knBnof{wF;Cg+?hm`BR`2EUUo_OJwUOVcXi=W0!6IUe_1ka?1JwsmF7`cr zeFx+xC^Pk@BW-Dae*%28q_>Oljl$-S8M#j*ea|v)%ISM;w+qvzhx*P4X~J89Tb%_E<_kJGiv66Ag^70?M_&#z995OH88Hh!0smv@GCDu8* z!;VGaf6zRVJ!KFKr2V)=he?B3e0ni(&$NzTa<4ZwewR?ku3->})NYV0EZUdz#U+2N zXeBKB0lfoml}H#p8EfUMI0I{KWcPQKbHs!JsgCWd%6iTAR zXA;T3mv=wBws^nbG4Pbw9vbPe| zG}N5@@vHxJNIciKq?5O+S+eAUwWs+Tx4JD=YW5L&cIEOjh`d9}#QW8WQfW8NeRg|! z&$|$cyfeSzM(+@o%VlaIRC*J*&lwLuj;5hsNhLPG-;WfF2)D^E!n~VQEe_Y5 z2s^}oEAx3Ik2xjNw~_@%)$@Ke|hIO9T)6yNof=qO1WOCBQCW(HQ#!lw3XYR~ft#ABAa#WDR;H8)!JHMlx|{*W-H*mduxY=zfx*$N>=a-JDl zzoa?PA^7ZR8_!o4w}3cxxrX&9*v1r;aj8yM~ zco8=%*39N|%_bn!^b*kz3n;D~r*^ds@x@=UJPITLP zsE!Sq!@q{;Z@*>8nG9%+)wyI?oa}XYTydBDQQms3O!dZ)mOwk<*(w6g<(yO4-OnMhZ|ibYC+Cdy#tk>9*$Pq zsYA!{WuSgIRgkoscp_^m$x1I#U!l9Q^r=lA^jrsb}uro^(M<9>jXa8%P+!aubT68 znq0M^vJ2Af332g6qG;$?fBlhWi_NU@G2NCoW|H_nzGKC{6P3>Dnosrjzd?gA-lF|R zBqfljD0TI6tk}35(b?>-5C3+jU>lIaz@o^ow5Z6x@({pI2~8{*I+AVwxBCLu&5-e< zl^{jh@{nC^9!g64O(nK=B>btHgrlyVc@quRi4a2k8FS zRVyndG_E$^tp=LcT~*`#OOO9?@2ZbPKi&^<<4Fv;+Pp44(Mv{ogH`D(y+Rv;h07s1 z*!N2PYV(Zppn3Cr`I)P|BKZmQ3ghI2E%Vjp&6z|${_lG!+u0L{u&}Tp)((!Vw?OOY zN1#!nU-ONPQS;+{A)$tE`0-b|75wuPOIa1I#CIj_2kyfY99KJ$2saMRj3ZD!VgK(# z7Yv9W;Eg=+!l`(BwcS8@(DBt-x;L*rIWarv_^<9y65n0z_-fF<|83+-NB%$FT|Zl4 z89$3G4%UrAIrT0C0hOe3WaQfM=3B_s_xFfkeY$2CaO*4zXO!Kva!`oZzV#@mGN(}p z>R;73cm&~dcyYTWQ6n-v%T+kw=Hm_owb4hJ{UWs3sZu6qIzk&F@|GBq$)7egF zu})1l++ohXyu4hiXU5NA0vfcBoavc_*{GK!>ZJQ$A1%-*Zw;k*sng^W5T5#^Tz^~V z*`H%?UC$U0-{97x1!oklaoNsGeaD-JOHzi0Dd3RQ6FoBy4%+AWA?C!Q&3Foc+T}t_ z!U*)SP#`3`HXHn_>f)XkAgF)}H*vbArlvBhS+}q<#6<0OVZ5_YSrNtZq@<)XMMSyd zc`zZ|WI_7WwNJn9pn#=F;+@;^aO=7MQ;Kd;LAHluw~KqB)Ah;6FbAhpMZMThmlw*V zY|tp0)Tql8a$H&!eh7dJ>hkR5J(ttpDfk-q6rgZ9pOcl`^MZ?Ivj{lv>>IPDRbvyZ z8WCgO)pl^>#fCm%Kw2{tW)dS0P~-V04|+vAXVSrc!?kLlN|o06b|hLknk zQ1|s49B0mLa#SZX8zOzaB6azM4F#GMMaj&>@9&{YIl68;0V-SKMs$ges++IEobRP~ z9iTTI5FBRS6oESTJ+t`Nb7o~dI$fZ-T;{wH?YadFFHYv?^&A>t{qys{k;eoeWF8l< zEX>W1)4l@t(;p)0*IJt{_8-?RCfKmDv$OjO8-g8W1v~f#HF!ocNX&TGXYFJOo7c73JRWBGV;ku&c~vX# z492S7Xo033fHDparW_JCBHWe-TIRe*JzT^pJoc^ss0srDGiZvC=OmzeuCMid`?j?i zbC93GFMP86kmiUFumSDnn<0oaF-$5Uk4lt#)nuiW8K?{hMR6m2JKd0z#{^}}B|Uer zA(lh~m;3Anwc~+!Bns{ZBORpQhG%+5Ml@*nY^UJG|1Nvim}BjUDuj6R31hMzq#adA z4wPrI1k(`)ay=+0ITI3TCrMj`u{!JW>(lee&vf!<9x)S#fa!XAJu~2@@OVk}PkfPq zq>YpUg0?K#C{|9+VG!2apoFGO-|{jJO3d;xfwQYS8f%#E zT+Y6j)Qzob#~%--)+aM3U%n;dpz!~tonx+>QHQsadqIfY3MibwY>ge4Bd1k4eCOV1 zWK$ZZYev4GZ}YAWt@XrCphzi(?{un0u~@O98vtC|<3ohHh)S&t?$W52u7&hEaXCzg zZw(IWk0%N=mCGncql)A!NNwhGyq~emG`fj^5R9>*8Cdfg10d1q;R+8&^71kk{YUm< z0&j!pkwWc$``Q(HU*g?vmeQjbCclqM{yy3UZ(~rk5f6aT?l>s#^gDOPsfW#N+~nN^ z$om^p88q>@cB)Y08$M!SIQ993enDVUxK1==C?~G-!ZahIZ$YM+svy*>l%Ho%6 zJOwJ$S;R1FLc!b{t)j#J1uO{He7S-HCXE65>0PF>@OZf*5X)WRe5c3MwSlks%TX<^ z1}-`^XJ;7oLZ`XPln^MI=|y{iDnbRhhE1WtH3xui?=gJreb?`rakw!wK1VGV$Nj!1 zuhk=j4s?83HR^J~)ZNDBsIoW~y9aBt!c>CUls0Azqyi7S+f73p2~i5cwBrw7Xu^2% zW7s$a#yqMDU(tl?+C-7tSCsUsvySb1RU?0CtgS%FG`MEP*F0onf7eRhU4T+|C{9{Z zu-_T1u`*9wT8wq-~(UhAe>pXUOkzl^~Ya&g;X5+=I=5k*lfIn2^(D%C#QjN7w z81=-&{6OS44XRwd0+VdlU51oZCD^|kRB(I+Ou)8FT|3p?RihzkL%GT{z6=^Ok4g76 z5dk@Kzya#m-58~|N(2=<`{lL>YJ5p02V`T^D#_N5j-n#Zu|LxM7-K69O$EkQV@6yn z;&SL8MTvgFr|fq@maQKlHsY`#9ySzB}b_;;s-ix0spVZWi==!PsT?AM16QK498gpBZZ0X|Q z&leU0O7m}?vxXlzqOB;AF84<58v7hQc)x3r+1;!|uXT{Mkpl2lXKXT3+`B0JX?CSL zcL4~zMX04Rz!~@SG0_?aaun`TKO9~*s;P!w%q3tc`c2#dxAh%BpFTFFR7^JswMHf7`@fa{rmFC_{z(TGYnUGg zx{Jv+K>JTZ-)0*zht5~@3-3-~V~>>%7Vf{(H3#hK99CS{NEOvfp8Yi~Z!l3{Oe|t6 z$+ejUC8XVQj5?swY#;W$ ze<4QOjXk0j!<1! zxvpRsWqvOtkWiI^1Ooi*xH)j6V7V_JN{m6yO{p!$AOZ1f2V|CY%v;!N!|D%6Wwj>r zYQ^&$s@Xr2e&+-D?mt|n;Qp2i)RH}+kZqG3M5KNe5X%#QG)8sHwpW4{B= z3x!PW;n_oNu!SAwI{USlu}PM3k6nG<4{sQTs--j9>s+GOse?prJnra?ii)xsWog%K zu*TD3d)CJRJ@B$c9IOk@wLf@6!fSEc&}d7rUK$PJO~6PZ8qi4X{QYSoP1T9h+VgV> z?!1Nf#1BJ?BpC$x2UW|&$c1W~@mvyBR74}cYVf`TDV5{(Yv!C-A!?GCQjj32Q1at# zTCOiyLlT-J;Sy$o%YpK9vQ3!77(Ld4IOgn@Gqq8L9-1^@=OiAe#<#|)9nQ}f!=KDo zwES2z&yxvHZ#Heo=(ps>)_fBwx{N)-_8M%y!quhCwb!gQd2!9}F@1RgGD)fDxd(Y@ z4G}ioH#F)HoHx%l~Aqtxef!xPgOZywK3 zMZ?P8Tlxy~GKYKbPV5Idg4p%a3#kBkjewc4S|!&bqv7PYHPus_ig2T{QR_$T6R(r4 zb$XfEpS4LbH(t`s%-(J2hEB_THb~GoXnF5IUsP2!W5()UZBLG8pOV0|ji{i@>>{_A zdvX(_1*28~iV*5}?k+|~1ZEEr>67u4ni)!7>-7p!7t_p)D2n7W#$B<1-h90v=^8Cx z2kcu3Ja((<&$#a-LrM2)?tRFAO&)!c|CHtr8?>e~s7St==t3u+$Vn8-jO%F7C-#pT zO-d;!N<7FbZ=T`B+?&Z2dow~~@Nkud59&B5PPNlbWR%;Oib3N+%76_Z)-NKyjSe*U z?0I<&dBp|c`7EC%I{{aD73ah7@FT7-HfaJulR4~4Tm}a0q$9)aVJR4y_Rp==DQ%T> ziGa`F;r=d&g#kJ>;REtB86sK*x2g3;J4nxlqhh#_fDgZmDX_{{@HF&>(4NYr3prs% z(Z>7k0re+h@kG`=ASHM}5zy_Xayu$w2DtuK`JA5|E4&->)r*y+t;GV%njP?>fnjf0 zDd_6fVeFx!!@%w|Gn~lpT3J|H&E>0K-n{2{5%C&l72o;PnCV*Qzq|m}yDmNZ_^F*7 z%i$u!29m#O2Y=xF-$PX)WQFVGahjw_=7r^|ly$V;xe+*VHhyAoGUjh(ZTnX2viYCY zMrQ`c_bY=xvv$^BiDm&}-~j=c7Vi0?>cgwZ04~4`AT+IXy&iwXwoqZ=dq)7U5H|)) z2wsIIzS03SL4T+S0GEIJBM2@z0JnY!eCd_A5?9jd1IXg*e;fIKq`Tff?mfuP&W?HY zG*;}&dqIO>>ZeJEQ%c#3<@;Y3a9d}Diql`koBTvw@Y~j1==gb)i?8ByXb=_rC-f4G z-1!7o2Lt16Fc{heQWc+HX_Ph^3WDQl2cs0idWEv z0MJx&yU$|yDm;jb!bVlB)yS`2N|hCbLAnbkMqGtb7{Rpy)uL+u_L5K&!4w5_*6&WZ z{C&8iV~GPm@qTo|NYK?wkrDtj^=EQ_&VPWpa^PC&BU$WMFO{K#f^0Xvc&M-TqY8jV zakJ@N?_WU>iP}LoX1rOdSh?Ddfhhd-|NJX?f79IjRB!Aii4UtP8R6 zt7Bwdl+M6P^ndRu2&Mt8fWTC6!ztvP-#x-^x~vNdtzf06?!uK37~KNu6dDx0jmP`5i@TMLFV2qPrtKkTk5T*)5UYLj zZ4!Cx!fOH6cCF5t7Ar*2#DzvX6j!oAR9ZS1CI58W8+^WL@(B=LP+4ve6}^C2)78l* z0QXK*bdU^?BP}346I%TZ@@vrwqUgaEslHvWE;xGJHm2j7$_#-hvHkM7afdmt_^IC&r>bNRCM%xVL2#6e>g6 zYri#~?Z{v50#%jnyBpMJGriu}*MOMR6lerg8zUyq0XxfNp(A`ZRkPG^W~1TI(8#xF zElLiglrhzfL1g0w(oy%m7^OIbi{8P~DmJvKc-8MV_C?^J241-b3_^o%GnT5Q*UCqe ziyJrWj=h@b0j_Un?NdcgUSY{&RgKBh`N4LRDK<1`m4lyenXWJ3trctMCLl_^xjlc^ zxw5<37HZ0VsFIIxF!r9(tf*orsk*G~=e6rCCo5%x57#0p>8POVrxb~-Q|3JSr;|!l zBbl+;c0I81aY3r-VmIUcGjMXgh5!A_(^ek)>E*}lD82?N%!OH&+dLTuw5vg!d(j!i zp~MCQ%SzS&ds1+J8QjD>-FSRLHJlv9*(ccA_w4j8uZoguqTpMo(B;`KO4}@eBKJ(% zlI3h<){sYjf-V1k*5`;S{5x9tYV9cGYHgw-cg__1#;Ekh^JOWMVKy$WR7 ziR?i)tQh%|L>>9bJ#KtxUl%@#O(C3I-x91mZX9ViK7Gx-)Jm_yxma(v`=G^udVS=f z^^^onX<57d>;_wbPQR9>zEGDN!UR?%KP=}~L?Fy%A8o3r&N5|us4KvWeD^ld?yG}d z0HRMG;9MX4PEwepPQWsC2o}n`NWkK8a&q$GuGJa*AREVSpx`dfYdbz)agR%tfydQ) ztQ-Ys8+vU^0Uy}M3i0^N_>%gLE!Y>k0018TpbY5VMK)t4jgMC-ERswBgjTkhkbaTR zKd2TQuu<6vJpbST+H6{{SxT`<^*PCJGEU&M(0-9vtB0jlAA{l3%RP29qJqs;X^4;U zi8YN&Ia_}1IiwM#z@_B2{)JOGS^u^o*{Pf908lJW{pNvQa(!wDl^71d!Kckb=083{ zK|EmQ@1X}ZTc9X;Z2HwtJ|BE`2WxGz(AQ#b_C|MvOAsK=*j$7t{e6miX7l$qe)}V9pf%e)BLsaQ#7vj>;n=>;bUC)t| zbsT{a{Hu(qCTXz!&AB-os=XKPav;j~hWjg7Mh9&rQayT(9#UP+*ZVVfNyAflfa5ONYk*9+_CM zF}30I1i#5xjJFWlM2x zHN0TA1Z>}P3w%m7B2n6U4B!h<1}GlWI5^QvdA6CLgm||FEu){~{;2kn#S_NiEl_!( z@6sQ|mnnV$>!xw&7uXc^UDp~g;3d#FIosqb+|b-z?5k*+-PP^^Ht@Qk{CzA>(y6YK zVr3}O3JW8usIZYs7d&%t+Zmx*qp~2VOHUm*Elpy!=GsD^+6xBp5PoJG3#W!T4R4@Z z--`_kMX_EI4FbKb;sH~#?f?MXV^J64 zOnZXOrprc=8m4NWeY~4{V+h$0C@t5mh9Q3L&u#^wC##VMKxVeO*|8ib9iT}F7D<#U)@fImowNLP)K}>mS?)p3z#FaTd zd6eFX|3bYW9m=YM21?i)D8zS;g+Rt2al|M3hMeaX>sf&4IJS$Rx{7|MDE46&#*%x8oIA*sNS9fQgsKL+4>q0dX_sz8la3!t2E&`VNkPR zx7$1(89JfQ^F&d?Lzmb|nDxYqIKra~oVreC=I>%i4#$O}vK8}l3bA0?ALUVqDLRI% z7kY6YK2|*Jr@yR!YPq@*$D*(DBZ|Vx?d|3XqQaa^Z?!}J3wQ38%+N;m8*n5}zcrBD z1K69#0x8|3xG(YUI(@r;0DKDy2WTaXXYd~_v*&ln3@jh4x}`@kxjL(;GQ&GmRhkBg zA4(scM6;=uS$@afe@-~9sCrU$61Iwk%!*Rlp&&d-p{x+goMqYPREef9DSrg;aDN(n zmFunn;%Y?iHPB(I=2T4Y(=r6oR(*@zas@JgOKF8gsyD(tRpszL3=ia?0*ypl+`|pB zas~CBy6BFLL=A|@zKvy?65OVK9ut&>77$?t#^X*4R=9{p8Dp~w@T9B*e(&eo+7mY+ zV)_Jqg`1zt*;;W{19GjX!B|vH`#?|&G9W{UnnX=&cTbk?HoL+4iP=|7!&Y@p$2~4& zI7+`iye@~ovCFynq)2AUB(aKI*vSIgV=3dJI>AO!Q$iqFV)uRAgla}K5^@VzcC3`F zwK0>2jVJt8d!R4t5+H3qUf+tyLSC>w5orRvsFxVsSUQbfHOf6B*db8#y-WfhY~tu% z6^+662!Jx$=li4aTd7Uvgw*n#ftn7xUO%ylo<`264$%R$c58Rk*;wdg zIaAoLnxGmHVUMKi+N!}urZVkLgd@57HU{M1_6IxKBHf`N3XT-y1M6$ z8&4RI(%{9m66b^iv_1Gr)@P5fX}C)QA~x|C)rsCqqbG~po2!Z>I6$7apz}AntxON{ zy9#DafBCuLoIQ;&G;SZx>35{9J24~svR79{K`~`Fu|sC>vWjR*)SA3WIa>Pi;j|e& z16xlS_8LdAQZ|-Ay@6(-Hrp(Z`7hlidM2$pxq3~-h>DTPE*|UdlDA8xUC<8hTS!aZ zS#enRcIyrNw*q+-=x*c(wJ`+La8AXb9b z8rvl!J@gZxU$&%v#F9ADP$plt*-(4x3eAMjXp*W7w7>6AP%-`q@@qTsIIGO*evplN zmo-GM(eiR%i0~Lk)r?!BNSD=6)(4tjtGoRohie+55>}~Z2JVDHqZ#(ewSbJPp_J{r z8YtgSqCGF}Zs%&6%^QkCjnumC>{fCi)Lpa0$kbbSxv)rYW@O*|gS~qVY*MF-!P{l3XpN#UIn66)e zo^rg(6MuhEF8sXYO6!TQ*jrk4yCXMkE-ZF+?y!~03=-@-MnBN24*!UzQJ-iFK;@A{J!}13*32j-D zg>79@^Os~`ZvPuDVRVL^<8eeE$C+V+bl3(4fcsQ7D7~%#1mK^HFf>e&*n|k|zx?t4 zT!t0}E0kR;{f{rwMgV#sr`YL#6+ECNLIET?0b~F1g^D%6F0ymX#Qq+UgZ6PzP)|{d z&3}9WZcHp#ZMdBFDhh*(1rQQk#60(Zd=dQu+;RSj4$*%+ra!=Euxz#{|KkfeHo)r| zabbOb1%^Qbm^Mg(alg6Bc0KyO+%{^ph2L&tg`lV;f9A!V-! zkUSu~k<$8x^&ju!y%OY0a*5LMlNCV?3sl)PX9bC>jq)K9nIr1mu%|K(M|P8XgY2&GzCb6~v$G z&J#d=G!C4$kQt%uP(bt9aQ1m63W6_^0lZI>C?TLVm`Dgzy(;zM7u@Qn-sk&cNPwMH zfz;9zfD1OJmM)4vWfO}U`(gof&kDFERiMxTAndX76xh_7x(8gs3_Ovrc0*L5Lc6y2 z#5|7kqJnT&Qf2g?+#cl$i(lF(?4c z+EJVNrQhP>;t6931`g0peKj1*h{^$yI9(;BKEoxm2VifLo;SKYiR(>zHe97WV>Q?w z*vj%ag8Uf?{Au5EPZ$Ww10V*F0v;@SB;{dY_ zNirM{bX`MC+!Q%8HmG$e2Xu|wJOrut{Y23rw(hehT}uxQct2oc=bT+2PieeuT~|lo zaJv_mKBjks?|&;x{YFl$ItF$7Rr7zf_myE$wQajfNDE4XgbGrEfq;aRAPPt~4&B2L z(!v0Pf*=A0p#l=p-7!NdC7lBdUDBa+fA@Ocy^r_#c;3Hz?;rc~m%5I1&x(7kbzkRo z2Kj}i?om}mI)%grJklFS+xdQ_4GnUBM6N4Cg*$`Z9@Xo8>8c^i=HMMjTwV8Pu5A_Y zls2SLWVDvT?MIer+PMY;`5ORJ*v(IW8cTO@OF9gEK+VpVj*!O$A%X4h2+%j!LcueN z&zi^S)OMx-C*$3zh>7FoU5fJr?j$)zr8rB1&O2jzBf3viC+urGKP$X5HCCJYBI#SN z<2~I+Tjr%%7VqtYA7fY4m;a*U*A;KGemmyFRfFucB3gkZ&8|*&7)RQz39AHE)#BQO z@%yKIzT%Fd$3;FEn6ENSNhSDqg4BJpsBH($#wD$%1w@kiwq(Cj4ee9!W2PIlFh>jM z{kA`U@dXo0mVxT$(^`ha;Yu+!L;X5r7)L2fVAmfQt`g@u&!OgeITe!w@x(xWr1)w{ z-wLoY+g^-~koXe}1xx~<25wm8S(twsF1|*5v(*u-7=rv&f+D{&veh2e1Qju$J(j=L zmY|h@pi*>yUANM;3y9lBK$*HYQgp)yNPvXB{#Zctz9Saw`|An@Ao&v1u3hCW8hM-h zo~SxsG9S;z{690upPk`oNc;p7J z0t`xI;xwSjiCEqmb^1Y31IstA9slZkV9QnomV8SZUI2j20o)LGuot8zy_69>LCii$ zfJM6ld|k}3r`lU2`=nMHDH8`e=|e$b%=~+JSy>E6>`NQQW>RX<&6WGgx@+y+Q31UVwLK}l+N|15`Moo0j9gZfBHMZZ%7ab z`Y>hj#*DXsj7tu$_1*!1fs|41EK&n1+b02J0xeQ1Sx+T_;v77}!fdl-1O)xj1Ma}W z%Zp8`Vks+o<&#nK9-3xWw>t?qjn!Zx!+V$tRFLfurE~(z1uQ*pf%ZS;N%o2Kj%J9k z|L04=YN)ssmb`4ovI#Wx!H!#VzfPdTV(S3h=Uq~Fi0Q#Qz_{76rWP3*IA$J}SLpwp zAnER|d1$u9dn(2$iJjhTuQ_rn7Rrb=iDA(m{BRPJN~IO(H(YNic2Dt1LB3V$$XxZ( za69Knj?yNL(vvr(Sy7kyuVadydYlik`{|5rBBbvXeAWo~EX0}qy#)jD(*S!a0|7i; zuDXwgFCxd-<^ai3*bfHOa##?U_?JX*QFJa+^Jvl)#pvoxbN^>g38XHT3G%;Kdx`sN z9Z-H-F~MbIY5{F`e<$NSyPL-rP@tZ`IKMzmqC-qgVOVWh#14B z%UR&g;w@zK!Iy@?ek3io%To>e5Og+HzJp8iyVyVIVqc9GJ&ukDur>*?sL52WSS3Z*Vcr4@}r*Tt= zA*D&P8`gOW0tQbwo_I)+(S?lwxcotsth`@j)Y=p1tcX+6(+~3$n8dHDxt>?Ae&IGD zfS*8nlq*0@N-lZqr)v1I>8TIhhbEXOaFWddxSM+;t?r)uRzE4>_5US&Ra2vqHs@II zWZ))US-ca~ItxkQ*Qco%DuixRLa35G;g@?37P*vmmhjR0H%L!bbG5Q{20BKbI~GSS z-U}d?sj}X6$M2{rY%v>?gpaTf->5940noXAUg!m%dqN$;pnb*4pN)Gi!ab`Z;%`1j z(_HSRphkf)Zfa~A3gi-THJ7+qNOph~A>`3o(o!vWLMAxc$32b6Zi~~a5^h`B{hW;9 zo2G@7i6Gj?&M!ujr%&+*)_mgvC2N&JQ^3~|WD}$H9x6>kQ`1j3Mp9EO;~<##wv7Lj zhn$apoiE8kfLy&4tSSTk0gn*mfNmgG0U38}MmBBo&Ap4QnyONkg$fP7tYu0Z#&C}! zSXY<3O+GN4`v9ih;jKjTOx(xt&xC?hTc6QvH*Rp^_UYr+iA+HIQ|Xw2$Fcf2TF7q5 z=Ct;l5OAI|B8;TLUJD=~=g{2rPmJ)hw-q+tM+#Hrv;OUlD3V$}tG@w#s^t#{4w*QWB z8zIY$Tf=sIy~VkHplw(!|Awc@u=;sbo7Q9Jh@yM&iHxGstk~J|B<*U8(BZQ z!enYFzuXW1y0|L3M}#sX;a-+D=gB~3#%G3p_&WO>tQZf4Gk3Cp)K(_5A1&^(QrLWe z7CMpLW*R{H%K$do7ZFE?)^N26C%5v288j=nHMXv%JOQ- z?3Ph^Z_u6^gFGy;+afZ303$xR04Bdlxj)V-JDUGy$Ae^zmuYrBLDD_at(gL$<&FvC z!W;b`kM}50wo@l8Gfcn7UHHH6g|T|AIA@>M%2@1_;;wiUO(Ys2neR_VB0HFR7h_>N zG8)h9-@lZkv$hl!5R_dCaVAv@YFl6n)$T`w*WkyD$#e4Rn}7bd zLI&d5%yAZYZPFReu#&~Df?i9q%aEPJ1VvQef&cCQ{@TjU zOJMfRjBohy--i3wj@&K)39(dZC;j_4&_2Nz!G@l~gkixy;R<-Z>`7#MXIP{!1LHRSyJQ2+He)B+VK`&kY@=bnXV65F9u5xv9rk5dtYw1UmP zR(xmCuKzzy1+~{}{|nS%ip30V|KM=dofqf^;*D$N>{jV~@+$mhvN}3Cmo)bn&fE}y zBk8mA@MN}h@M*tGYQcRv!#V>0ROhSvM6Qlm9(Fo=n`QX`bXfAWKj^=g`u@`WfWs7w zgLMU+#mJ}(z+6h&P7yvkR5CQB``2CwUpaGtsBVZ4mCIH74@{&K;zPL)7EhgpEnY!m z**wSurQLs<%cu-mFvm(UUn%}UQ7Zvs$u2)ZoE=Io7hruEW83!k&jOc8&{zhlcK@J0 z+JWgRx0+}D__+-0iyGX~(W(}CK+k;JD zt;e_9k)l*yAdm~KX2C2rhN8tUs=FITlpR>`i%gCEam#3cyW(| zr+{>f4mvFx7>I5>G@bYKu3bZAmk{;48_sFMFX$-&>0z94(DoYY>ISM$1GIt zyU*}sV!wGsUB1-@j#teG1z|YAy4h$J5A>gr@B4OyO(EJ3;JoMquFA?6cwQkWVtOHZ z$TF^Ud7RFi36FT?;UsVwC}kA1n(c}LlraTXuar`op6|dUYE$ago-CJ|$i(wvQMOnSCbYt*FeNQ4j7NGG%-7l&M-eX82X0&{Vv=cXvo%o zi#meu#}*!ZFt?cgbTf-zuVTX^YIbgJ7y^(Mm`_Vr;;x8e37K}V?tZ#5#8MgHT2eXa zW@f+GKHSD;Bea1FL}i0p&as0J7`^xjG@6#>qt8PJ@{K1Hf+@uo5@mnQUWe!;br8r?dvcHsB{TRI0(gO0$Sx8F z3|~(0IGV;RxN~w~7_61@1WO#ZJ*(tHuuLUONlhJw$W^3N?o0hY4S=?BRuK^IY_oOq zPsX+a$>C?Li0@CIO;^H#Rm-0c4W)F%(swM#fY+0#qym zfJ&7_y0yB;ODvaO*Q%Y32ya7c(}y6VI%3O*j=VP8_*?4wI0JUNje&PKneT}GV0BrG z0MaXo|5=QsNL6J6>#ywilT_t8h%GE;b7{P7VU+z%*pHxQbu2@bfti*y*?FKCNc z7GxJW{4||sr(G9*^cpTQKg1R!P;?7$PyPao?{q=#w9oM&MzqX_-h3L<5#XN7hhM7za#QrK`Tdks}8@jg30yK^V z>2(n1rNHJ=0<=dh7ZQYqes#t#TX&E3Pdd;QTX&TK=%*Rj(XC??jsV07k?H`SZ_^$y zAx~14fZS-ijn_>J#sk6y>>(H3W1tT$0MEjhsyA?hUj?|u&(q6Lt9?GfPtvc_V*)vN zQGDfg0@KM*E$e2c{l4HP9*Ubr?Z66n^*m*y-~}#V+pAe17Q80(L%?fP@|G%W))iH*2|Fa0Lu5}cZzKJStNTV%+XnVPJB ziHv!36hdf}qgVL-0{N9m!K;wrnWph`S{BVHo+Ka%VDJGeSY_B%eEG3#$QK6+GJyi$ zfwOP68BC8bdR_$YJ$HdG==GI5R8N$AixBI*uq9R5o$z99FUvj4-`9&4luv(juV@Y~ z39A$`tJ;}(?5LIo30=0w{vkiCCnRF6u8M-W$!2jjG!G5G9w>Se;ZeHlfN*3(VwxUx zD!1iZwoa2DPIm6>^ll?oO1pH1W*ZIp`8?8d4Wmt|VeC_{55;k`_{_sB`L~LUbuBA* z<5!MmG*?lSG4q2xx%!sO?k?homMv4*B%m)jJQizf9kyleb+I2#=6ByDp|Bs-gykAV zd3yiajEbIF0y@Athx1{X8`EUBaB-q8IrF9uRw4FAt&Q5?8(?TS*Ol?{MtsXXK=*?L zl<>Z^u-b0G1oJ*3c^^P+@Q6!(Nj7N^$ny%pOR~Xuf&17{o?g5@6VgZTCM1%TGF z4xq!@ztotm*9`V*Ptwrl-yD=7iWrZR6Nn+l0W&v1E<{LD%c4+_`YQAi;km=@<#{Px z_8GwZ+o^)8f5FcCKL&q;xR#-qZ#7%T9oppwAG0U+fnw7U@|{_4N?Ly;!)`!WW`FQphxZ$Rv9}5P*MVx!?=4@eV)M||eDXrziv|?LACDZ5A3)Xk z(Zsninr_dFt#O{eK3n?;F|j>q9tQ9cwj4poytVss(pbIKhm{{s+b$O;HJ4cCJs*{9=hy4h<0o#i#w%wJd8t@uXT7JWB6cdvxU3S z1_n(RvzM_DFag7ZSadM(Xse|K6kX~s$7Cc6T!PFa!FNR;Xl_?PAvA8TL&#TBg-58J zwZb%{O)rSX6qhGYzFO$7TWnsX)BKwQ(qxweQ*!X?j{xCIW$Ra7!Ob zg})Iv5@LOc=hMo10B+vV^3p(d?3%xw+G9d8S1!%DrR@A24=McBO4A=fX=`-6K!Nx+ z5X4?~*6K=61ETO&KCNetZvI!wyIP<(Q2wNJ8EB_JT}YAul$2vvx5nP(r)wlSfXbsS zJ?z0Gp>p1}^dSpd6iJ3~>LeP^Wob>T*y^cj3hD#x0$+|zw}K`AVmf%zA?j*^&^-W1 ziG$D(B=&u@rjvTTFD>EqsE`bkAiP%xhrp!jyi0Qff#0)!(OKO!PtpB(p1>l4@b`f# z6yZ4@03~3wHi0{XQ#fvKQHe0CKzMtOF?i+jR7T^6d zsu!&t5Hk=MvgX74DJj`M$cT%JEB4^iaxy@NRepO|;I~B$UJpE?pJgNXelu|=4YZ*y zWjtF#OK=w?il=srNoSkGnj(srBb~cz{roVK0c5f}6hewM2QJ1S>JvhnCxoLKp|hPJ z%gE(sG5kzgFF!^fnVVRa%^r`Y(m)FYq(VsUx^sFfiFX7Nu7mh z65Gv?w3J?`$lTu&;kf^!%^4z6#^lsXFi!m-|V2!P|KCmY%BuhuXcm$fk$g-+0{yIh(C zxhxdtC0n<@i33LjQTS@{x@jM$fJT1&r+nVzZO*Je8ITz!3I8gqk?@?3eXFd8^& zgdV(FHk0?TpyxNR0E&Xqi7b6qRPbfBLpAXp_AJ8Ca1@G5`3N*Wwq|Hp{EmK0dgjk0 z;k|0)N7Z3)RlR6hS6vZOAZ6|;G>E7`0{9+DPZst$**qH!=iua|^$==*E!{w}kfu{x zQw^gZ)fQeJOnx~>I=lU#th^M_haM%L9<^EAjT*s`<#>MargI0%PEsF(y^*(XSIgQd z60v0no8JalrLwTtA_Qi+raR5l60UZ=2Q8#pcrf4|=G9$l`(}}ZDt1&gnXG5aPIv60 zTf%@dBhi4ybgoM%ja1&t8aF^TbGZ4M2EKxR(4Y6!W|c|1Xs?1H)Wt({8GV4OoRrJK zV5ryc(k-F47myUXs!_9^hX`0&Lb=zN#qzHu`z5q;3P+>9;BJlnu|>pXw)G|Fwo+TY zpO>FiLo7yV`aeq;Mg|3$<7~J34rPaSIn7gjjmf{ zF9>A+(%ij38&ABrKPgt(BXRI87n7_g)i(yT_XAHN)*9?sb2UAz|58@=0aAfqdb(w3 z^hsWXfc+MWmhNPDp7A9>v7W+H4AmLYOnfR&PfB_(gN1wB043q3L^_(QMWu3{h+;1z zPti^jmF#ed^oE^&DF&PC5l@r>%Q}%hMZhW+s+X`5O!rjuAK78D43B@5RxR~;kXi6# z#`EDnVN?cZ^@RPMc4EEeOU&IC=~dZPc^MgsmW)L))+L6V^VdL*+uX?Wf*4!|&MuMP zma$a6)Qg3US;%2FBFruZD(={fCNn78ZeCEMa)s*=ToH zHNLjiPUpd#*gu$=)@#C87$3T@_CnnA50rPgxz&w0n^HwrQ(dGzYq9v0Qg+%J?i5*N z`9kx*<_zGwM2Ja$%^e|lNcuclhAv7xag8*PHe{~SZ~DzAqoKQ;$2Ug^MK6I8(hM_72~P2YLh;pZS8&g z4v7`&CpURSe`cYj$HqGq@Hb=T)=39o(_b;`+-(Sc_c2n|75=puhXB2}6P_Qk{z>G{ znPR*IJ9O3BlSu`W51*L0zJ3I|7SL96l1ObrjGg{BaZB2kAo7yldkm#^hH_BvxnlCa z9l^iqc`b5O?>wpQ+x4#UvzXmE6zMfU-U?VDuDNh_KO!{1NAQu9e~!;0CmHMjp5bi? zpM;;qxttOKOY463F8c)AS+M7Seh5NV|8HKp->LhoWb^_zJIrprJc9_H!y~GY25F9= zqXB@BMk87pOp>aX68z;q;E@+OP#`k$TCGFoUB(?9)Aw*Qyt6ZfSgr(krO9orSHhoCjiHc~d%G9WKywxBcyz30H^WeFxkqq@L~%`^McH7DnJ~K$0VNDNzSI|LI$f z^S4L;;;y}mi?fI^@-Zz`!{$$z%y22+#8ypxv4!h=)m98HtQh*_l>1?uohk|sczc(C zg$7OKDhUaRyvoQ3!2kvr`%5W-W*LxW4g$j;e&Ohp{OZa(6euJKK=Af=Xg;YT6uIOM zdHML<=FgoYOL`(Fqh;U#s#G1I9zb@q0);X0dti*;$tskY8+33#qYQFZ2Npm$b66Ev zA0o-@6_tv)zMhl=O_Y7aXL^1kvkBkB>Uhb-+IUadS~C{w-3<0ujzCU(_0@>>QdW_R z#}`=w5@$|InrHjp9o_rPn=k98 zUO}Bh#qSHu`;9HhR#PtEd7K z9(MV)=2BDi1*IH>R|L|_hr`8!m0#iAtddWZB>^4wj+2eoQhV!Lz;SqZsL*_KV!ewV z27*y*4f}u@m~jWBZo??8sK(z6J%{&e-Io`BwcEkXGzs&G`i61=z%Z^`cqkMCIcPUA zIm7L`@cYxm#-DN70T;!vyqE~k+n&2#;M1rWJ747ghB{7DxHlk>EEgW5GWW-lMzZ6~eV9;paAs#7d`_j^(rRab^3yjQ+OX|oTYGJe$w*P)1 zX6LfCU>c&t3Xz2yOBXKceELR3hyo{Jbd5>~&#~$^;D`vf>*VN}B5-AehK`j3M3j)*JK57kCOKSwCT$WIzycG%zmtVC#A6jZAgN?7$gK6fGbTR}uWiZ1$;CCo;xTI4 z%;FIze({!Ys$03^#JC4S5#WkIXW89<;?`XHo_SBv4V$Ui+`sx)Y=z^@NV#LVj5T=? z{KP5D_K2?3*iz@MiwCm5b{m+TZ`M;03PMn?68r6-H+Lhfy1pU+)?e%Rl11#u&)b?= z(~`02ckLS@N~Z?=D-AI>1BlyVLf()W*U=~1yZ921l-?@!PO*>{!7nwsD{EB^2DezY0*eP@8?-g>!`-~S)2HhvUIyXmmy*k%d zaL1#khv>X{Li^E*OCcd4iE65TSs57xGv;=7S=+i1&G-jnNe^GEX=s>zuUN;G$eqN1 zw27_wy6v518w8@5B3i)6zDdL*BFt~E@bXjL{Anpc-;2>au?Owi zyztDvvs7$Z6WIxzzEmwrsQ;ox8SvD!S!7;edL$xkrmc!V>r;r|e!d8@`rg<=!}-mV zc}J<&X4mQBvM_{LFB|wuT@)6>eQ3h_+$cVHwQ+lIxf2`e@7iPiH zNWb1NbGTZ38(u_hIj~ycvMz}W)9bO)5FLlnz3h!hziwJZO;iIfYWAT|gp!jQnwoIT zETnj!89$eG;yF5fiH+?U8k&PQG+c`i5)u;Ti2TSzN(9UyO$hLp@2WYOFlW`ria3_3 zQ+V&J$jHc`R@3wHI(r>#Y^0TxLL+=MHL3Sl9h*Ld8o^*;paV7yV#+GUoSBIzSxd5I zYcAlZP*+v_vjvo6?!VZ|K&s!HlzYXOO2f=OiWo!FYHMo)cg_b3)<&nvZ0ZfbMz1!d#ba$$R5@0d0{DYqt-XBq|IBd<2n3C+HbwIr-qa_(09r<}`ud;a7ve;b z7eJCjX(RV%W)-%1A@#VFS`DXnA$3kS)b#ZiHwR%D_s_gL1CN!I9wHFAv{O)A=`oL> zAT@BloUBl|8endtuKs@eXSUUchsG(VeF|L(QW4-uG~HbrbC0u9*ZmCAP$o!R-A|VJ zk|iA1u2g;a+%wb0a;axmg=j%p{_Y+M*}5vNx53@;h;T-~>*X~@7wV>KcdXv7E2oE; z+}9Wj3hri?U|W`L%O%qGG>&CPwG^oTq7C4_4r-bi$A%YZT#Z9`Wz*XR3F>$%TT-Q( z(iZ&m5dX&^E>qe0m>ZILlkc5g6C_Q42#LIHn>A(5Qg*7c#^5d1i=yXp+ZEx$OJL1x z4hvV2iydHq4wfFZx92I2nxvp`d{cqfN*n!}QTGmy@~faAoVMy4ij$_FmmGr_eoyAU zsy3;|!(5y3Gny0*q)o=ZE)b8-J~uzV zJf_v=CDd=rm991EKQAxy#y2PQG98^&Ai?V&ri~9C33P)b_X3Y~qv8*JcDvuceM6jo zvUa=baJn&6_#pUDA|~*e?bOs1_LN)8DHf05+YP<;a7pl;wYbi`Cm9$KE5de@b?D-j z0znYtdD4|D%#4@x^z>}{V(+irc@exWsJK4+^CLE&d+*M6NRQ7}5|FVD1?1%{)^T4W zJ-B(KD02`krXjHeIbcXGsw*o6C(T^NG@mmPy9>Hmnfn}xMmX+nP0k2sQT!SG_|3(LZ`1LQ6SNdwrP|$D*#%*jh zb93Cj5=qTH?%89-)zNemxqrNtEtQb%B0u@$dL6BbPMUGKLwxnUH@-BaRjSdGOENbFcuK;3-Xz4%k%A>A!GiXm$r`4H*8#N_o z()flJt8m|4L)kG4Z;J*MkR?BEpfn6E(nttMgMf4l-3Strf|SGnDxq`^oifrO-Q67n z3^BYX*L6S7t(VvPSrQ`Ib^rs&Z0Nw5m?_=9V^QXlT#E;uCRH)z?Ykjb4LNk_xyo z@@q;l%4h*W{TQtE@8q9h1QDCi=8h2(X*oQ~4K

yKz@9o<98cbDQutYWaAjtgM?R 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 F{RiLy=Y9YH literal 0 HcmV?d00001 diff --git a/docs/docsite/rst/common/images/rbac_team_access_add-roles-success.png b/docs/docsite/rst/common/images/rbac_team_access_add-roles-success.png new file mode 100644 index 0000000000000000000000000000000000000000..633dbbd960862ae9f875471a3a89f710c93bbcad GIT binary patch literal 96851 zcmdqIgoTsIQKQV5sAuZXb-;H%E@Uc$;mNlxH?(c+FPQby^Ksv!`0B*rWok*o0NO5jQ8Rd zTs1)rE#w0fgQFwr)k};Iq-Koevkyshou8IRn7p9Geq@l!98L7vJ~~wMD?uFx$F5m- z_;4HKq38KeKd0pkF}FZ5v6VZCm7J@e*#v0%iCh$AJyB@IkpiqtQVhs)IF+~~UOqaZ zGXy`OrJazUi-QA=QJCz`5!i|@M182QVs~?OcQ>7K5lYB_rtz@n_?pm&rpKBjXoyT% z09}UuIRZ5z*q6K*#it!A6ybdMm|g8;@bQ2;KaI-thmSQRr5M5NE-gv&XfNF#tB(OEE)c8XE`iQgGmqqsGUt1jZU2Z+%c4BBdYqoYC48qkaeDwBo0+!3U|8Kjn=E=Ne?|tkZv-5@eb} zaJCuAUGVe6&^FT8(K15GGm>#l%c6B(eTY=baHnwX#d42}uog>mG`BRzg=_@m1kx{$ zmmV<5w~1wq1OS4$g3QbceS|4Z&>(ZMXbs&kLa9OwqgXbLb8H zHPVZax+s!=K-63`okNA$N{kJt74sP~Wm;v{$`_Un(Bu9T{;XfzETDFL)?)9;9>rdu z{nInjMofud`rcLN9Z|PzE}n-2eU@to7e5zvXG)ixX4ysdb~+0oaQR1AqR` z2SMm7kwGCqb`DZP{Lv3G7d;8sS*A1x@J`T8O^zR&%ZJmu}ZOZ zCMPFPCc7mOd?Fj(984X|NowPn;XThNRuN#SRjPf=Y0I@uwN1K>zD=K@BFro}#J{0} z&~atGd~nHmnGAZdteML)mS)Uu%x28H!g=>LB;TYQqN!JyP^t0NSz$Gg{XR9;=awH|WeoeIO3_4F$*)5-Z5M3HY?tbJ zzr6jztsSysHBB?EHXU#0HkEFN(SXtLwt=UCcgNj>_w>;z{SMKNx)7Acil&=pt-FeeTG9 z*#2N4$)UqxW=?p%vsSQ)Y?i)ex1ns7w`Rp+#q929_qgnu#ya1g4UB55d8`&JYl+3* z$X6v{nhnbSCH71rz|+%t)Lm--x-9yma+GrF2-JW5GW1e$3(&mN;%W$JJ8KzVT3aM; zIc?c(bz2%)%Hu!H@VCM~!5&FT{UP4%yKlP5bXI%7DkJ=foK8=_p_J#6c30vGkIx;zmp3XBVUfBp7G>9*ju2gYM` zRRS1xAb}^3cRSR%z{XUL5pM(QfY6`Rg07w$o@Qe2tl_MGbn|v?tt~Jd$MK$mp1gsH(%jDEkMNBiH?uurC1h=7J5Zrhb5(z$Mnh8fv7|?2sB;K6S-KRflukM0Wvoi2 zYHwLzNe&rjw5ELJ_WU;2gv9sXYyK`P0$Y}CX|b=aS;?t2`qyImRh|e?Eq%&-De~&- z+hW~toz&BKy+Esv6A#9t7Px-uI2v6tFGyUKX~VVKwVzga=vEsqb%i(lK6-28a6SLY z1GS6zMfCe(gU(ppAg}Ir#-L)caPgd$Swv{lc5`!s$R0VTm&2RKJ9Id=QMU~Pz=0p6 z>zx{x+PzNBF#@}PKCkFm>#2U{{m${$=Ei&|p6nf2W22MXtfz^DXPb@RIq^CDKy*q} zT{Mm-&#Zn|bZ3Rh%+->z*9u1?msB&jS+TXO`f+}-OLh11zF*fTwp7nlpOoo?$;Bq0 zm4Tb88SFpJI(-T^vZ7#-)Cry~E9(W+NAJn=@ zxUTY3zxUhK4u=Q!sm9GPYCOC+yt5zPALkah^)}NWb0@I&dQd6AnL*5O; z^yc&cm%XHmiNVe9BbZN>-!T|Qwz_sbYi_vtayPv@(_U?8Yu;eke&E}28GaJ$CPFPj z>nOJPrOP3x?Lha+cz2$De)mvx*|vSxz3H=g;J`<<^U;xW(4k@5SW8gFdsAISFK;g= zb$fMV-Yze=>`s^n;2T4n^srBikHL}rvC^^D&$7*~j9ZWBv!ALDNDE3e1w>vW%=|BP7iW^3N>?&FuY!U= ziNgF$zh5KF0rOV;<#W{!ba> z>Aeh1R!dGv>He%`?rLf2=w{>OZp$V!eqVv_{8HZy4UK~J&w;L_`S{P;d(QTap1Yo^ zinzIx1CObNlbI!tw}bPacF;iH;`c=dOLtR7ZwGrvH*s%C=D#$=@5_Ixd6^miQgMGT z$*iZU!6@hCYRM?f!^gwNEcJkqkrCu-VI{69ukdg9{gouMjk~+EI4`f4mluzhAdi!) zH7~!Im>4gg0Iz_+(|e7lZa$9grru8--B|wB$p5q>Z|P?4YU}K7>*UDzr(IJsCl7Z? zX68SJ{`2`)KP|m&|F-1l_HVcD9pwEZ;pOMy&e?X+z<7A*Q5ma#6&@V+4+Bo{_g3&pxSPhu5wNe_egiCzbEV8;C~ALFYqs$ z`hVNx=NIPtr_Fze{sH~tgt(@qo0GlApFz}iv~`yf0P+5}>i>(<{~IR7FC_GD&_8Sc zFGBCXBK}$Xe-Y}gw)X>J`loAB{QtJ_&$@r>gLwZ;{y&1@Uy=4#?R}_8Jpl3kCv2r2 zw6ZtNqoK*5Dap&e@kZb4(08*K@Mvt~SC4hf-->*k`RIjResRLr^$6Y`U~9d3Fp zzp}V=e-Hnde^x_XFo+S4iHX^_zt9wIe7#25w>gqVuqG@lTxZbv3_}5eM}^K}f)x`? z!uOvG&u9n?X41V?QQi3@@N&eMLi+An`%mTnAVYc;A;j0KO(9}` zxAY$&I!g@;h9}JH!yxrP>i)J%Cv`6cbP@ds`A0wgqZvX^QiIvWB0u^!;2*t)9ojZ3CxLnX;Y9+B$WnTkCRhqM*jd%xWQA^Xx7vmL%FEq-|JM zjB6Z_se^;V%vzH>j9S|Vox5j4e{m3l@7+~Mn2hyIw*PQN>0L&rQ+nzEY?IC!CoICc4Zj{M#; z1zi%UNWC}wgL!V*uJ(v4P}gBBL`lZvT^C-6*8))76Q+)l{%IT!$%S#T!yu?hH=%<(s=x0fryxp;(cr$$w{Qqq6vW8t<9!3=b$WY6V43V5o}h5T zc;{XC&++874VNF{1k%8PM57q@w_bD64N^W(eY1ZitB?oxS%WM1;xFT(f!kz+j%>U& z#$wNm#V|JcKO?*!z2_zV-zmm!oLJ^{ z!qt{)%BtJ;=hNzTI4KxOVKPI`+|YxO)9bddu*}yQy{U`o5!dPFQFA5W&~p zs6GAT{Yr_x)Azd3Txq-w?k0TlnR<9Sgwsby&Y3oJA0|8M>NLMrRZ*<3uYbgmnD&6` zZs@;z_g?7}dbT=m@7kJKkE^?TJv*^IuVqC=#XJs7warZuFW^gsmdlrS$-gfzFW;Qc z&ZgHPf09dnTxpd7V)3fd>>!X&9NpYl<{boAXDh_xUrE*j^Te|zcq+3H;{(vFKxmHR za0}&pPx#rFGs(Hq*2cYho__IuwejHJ6}r_E=|cSci4>D1ytqqJ(7w{aGmqhu*F)^> z>B@{;V;ceL3 z>Gg9O6Fw=^upT@I+y}kigU?{yVM2JO=@tp)lIl8;r-2mfh$9=)A~P zJ`B`A@yEsw)%Sy2`l8wKEUP!cP1>W=`Y}1cRZaBjH%V-UR{o3zo&GJRV7MP#Z2b|LTfgMoLCyv4JDZd4#NJ<% z%lAKB>NlTU4tawvm1z{We*e~#95Zvt@$1(?kz1aVawy*T*VInMcN95#i#noj0YG`u zLAmlpv2b1Mv0T;0+$|mSukeZ(qLs^0FHJEDiL)rlQw<85pxcwY=oTAw)H_3C7B%tk zr=0+{VfiDs$79eObk5T7)rKy zw_SZSwisHQ(b5;_Uas8{CxW2>vtfq%KC)v0C}3^fD2X0=9V`R3Y@gESIX(J(tj!sQ zIaVCjt;#j2g2Pvz7j)|*V5E(y9pE@s>rv!hMdj`(o1xc8K(jGh+3B{WR$#bmM?GHE zznkHXqY%TLTz7YwcgLB|$E4MUZnWvu>Zuu|CWm;xDW+aAvDV_g=g7Xi+9J-VGg4oa zR(86>5a?2Pchz+#am<4o8hLUUc-~g5Tgj%TJ!ey0azTTKLR+#EADb3Mj2{rzFBU1n zy&1k(BI=En0fZ^OQQ5GQnXcPsLzq@h$Fo*(#$Y)f#X@qu&DEzUAG1Y*!^_K=zPwvx z8C)@l)2P#5e`wm@en75zGSk50oH(W?OT=keh3xW4DR0vRRC?lr?K9(1%QhiI!z*1- z@^;WwQxI3@HDbg<8Txdm5@)lz|D-*yUdQrX-^}z%rRW~<`h0cJ&8`p=rMf{arPtxI zDsdsjj}KW0?aQ7Fi(2{Z4KOiqtwyf+oA>1^7+8ug4ax@;=1e}y0mb>Pod_*W0;w1B zOGLfP0#lVm-U+!IdudrwQ{>rr< z(#u))`q-nrQCTYx0RakqY?=6?i_kpm=`pJ#_t>5mmDFk^3ry)s)BWvf9B`_=ZXs*r zHTrF1z{rkgR;|j@)`xzn9gO=MXdHCokr|fnG!HfV%9iUxf7wdis@j#+p4CVqYKyn$ zime0?1umt=vhey4MeL_+Fxwi^6+w z$6o0ge&15y=+b4VB7{(RQ4j~BCi6d8v?c4Y<9)4jQq`fox)Lh1JCZOO9)*vH8;Ot*-76VS~>>zeyg{z{adr?z-~vo4IHK{p^u z`YTuaruT+Fhw8PPw%-%AOP|q&7h|tx8gdp@*j!-JmmA3+sL%HvZF)pj!Qs@X))j}h zqgikcHQn*46H`ME(ufz7D8`G22|35$iqM0;wj-vTX2p00)-9rU3L1Hzq+WW>F_jt( zn^VqJK2}ey$|bAa0~})=7d=)Zg#H#U2_x!08%or^$$^vmy#1(i+B6&lJsyk=8(kZT z4@4FT>At3q0f-*MIH{-9Jp2jd5Qy<_@a->+51;n!>TgC;E)P#0MEOon>+9z2Xug$b z1*hV~HxP_=YtXN3q+Rzsylx)Dw8S6IDmz6Ou}e{dT()&9A9(u4)^85MIuGpr_|)snc*Bilp7ZY6-Y%kX28~Qq^=-U zu92@sf~JrmlL^SH$H^pY0D(HuAA6!TyF(UhJs$~Tk=a1DPoiH$KSN)4&}o@9rHWI3 zrLHfKWw80CJf!X6_PC8ko}f2sbv>g32MZwx7rYDwSXexNCMcMU!@LnD66#5pNal0# z1>NP_w{NzIXi_i|aTP{Bcrgp5zm@{J6a-(9f^G;mPGwKe*@T(%lCyV*2NX%{0TFE1 z!U0p@=Ai*#qp4btrU3D{$C6!dWWhnt!)U~&i4APkojYCD73Ndd(%G+>1gV0>(u0ZG z!$kJMDkgCUtKW;>7vsxquo7iMFx$r^)zA`j5xtlp*L!v2YDX;DLaUB^D`B+9^4elL zi^@(d))=yF)m2s4R)R{mwYA3_-$CTO1Yw@O`<^BhmI@gO%aOs*(d9Z;ITQ z(iTI2?8@|+ofk6qY?EqdB&|Ah)aFST-Qe^XdV2y`YE-f{{>Pv^n(oB7;ogJ&(8YR8 zs#-T?v4i`^!rVBbEkr19V^%w_kDt34pit(*Bx&fW+%d z@00}s?_E!g02Wi-gp6SSS)OdpiB#N07;Q!(W(MRPdb_Ozk`9ze*}*u-lNd*Y$-6Cf zS}*_?+nypSol>T0m7&@HB5)*Q^mKm1|4RUj{9W{NdBDr$O1a!G4yj+0wri@A6tDAp zVbPU3M|@i|0O#Sx5&j=@RBz~LhT5gJgjs`di;o9GH7bctd9mAGgZnt8lbAWlr(? z>NJA|93%>alYv-x5qvTs(#Z@K=4rV_NpLU{N96vp$` z?FK`a?`j<=t=dY^FupP)UquQIJv&@FdrQT1z)_DNn_F2s0Hl|f53n=BI8 z&~UMH`K-o{`~zz%jvAaRS=bqieZp1|CF07T@dE5qnwLC!s@sLqp3r5B)oxQ_a1LYB z6R<61I2H7kTPf&0_ZTPH1MJJJ6gdU&TjvFQawnw40nGB(u;yKa2s=bqg-D{)KNsFG z3ss`kBKFA-;Thk;LwdoxPfW4A6+M?D(esYff`Jzsm^v_Y;aPG%#h^re@mCC+W$n3^ ze%?ASg_<@jW2W_Fvt)sbpMjlvgkiiBA0(UC7DGxde%iXJ2f(6Yi*kH^KX8CUgT@8p z@|6$aT5xhu4n8%0j0Z@LK>reoyD2KO&NmI4bG(2ubH(^DX6tm1)dAlk}O0dRd2%Q3(G$}gDJkCK|lD=LAMBeqkKCWy;A8To8 z`7YRR5QdrjKu6scGe*QGU7nFETz?aij?cQHTpF9M_xe*~0Z)vy5G3a+5Fd^vOUCXV zjvmoXQzpaPfDWLrPRNNQa3EmVdy<5+On5L@X}#vS)SBaYF-|u9B4AMtv%}2nM{VGI zDk?HeAvaF|OrM}ghL(Lo>*9(orKs?V-OS2LczdfvInA8JhGiSaVf1n2Ew&>TGQkf; z(stf;d+L;A6#T4JF?lgQ&9HE{p5XKWFPvXVVFnV9vSnxc29@u6Y0aL^vQST%t{K;U zQ79)yC!Ku>kh)-;R;AhR-@ul#b+UhmQUL`#V#rrDhNbh_)&o6~ zJj)%Znj0P*#rq?*y|D;T0(wzSHHCmX_<%Hzm0VLdsUqzF<`pKlu0Y;%OQ2rr>cy$1c)99 zPwFf1*OVeh`=|c=aS$(Qq?mJ_Y3zuCR#1L(i-CUReVMM-z zIP^OuLCk6Mv`cc;j{SOILM(6dMOmi=GTP9+BY@<*Hfa2?mUe2q0*!bI7iZ<>z$;m; z8`wzK-knmb+lV2F!)2j>bA(QSq`JaX8;n*NjB<;})nsT3s}dBE(HVZt(1ztH>Nxw< zMlMXIOJjwCZGuITB@DwB)GKiV_Ujj(k8HFu0ORi92HA_Y5C2l4bj>Ubpwhj_53s>K zvOu@xL6QY10>**vac2{vGC?dk*c2AMiP!|WiPYQ?Wj{IG^gziN#*u-)6pm5R9Lrh@)LI^=$i7h|0*V28?h)sozAKclN` z=l$7Eqhr9JKbMKYp}$;gnKR>LQ#}WQUXA@+HM3?C%I9M;DeRL8S?zq>#%kO-D&?-%_Irwwn2KdkB# zv)1$X5Xz_=&~ieO6Df6j6@O+?_RUaa+6P!SG}$Qxh2l%t4|R>&R_!zAp_ zkcH=uA5?#aJ0eU;hc|FCrqe#e;yAj;-QwlKdcnalNV!}w&meJBB$A4ITLfC_ z|BRdG(@k7sH!+mBo;)B?37af(AU>}YpYR4Vd;B%Lmwn$&3Kc@J3JXCE%H<3~aIHh@ zRH8J22Kq$v){~)x$@(Z7kPI&Mwk{lXa^ZM8_4)Hu_@K9*tR4&Ixqq{6{t_fkDr#{r z_!R>mmft+}ocH!pGE~(burKFFnjd)$58ZhfU|7p(f}g`HHy}o=N_R$*MeuYlagW3Q zH9*K#*PdW@_)}6iy@~_1FfhheOKJxUE#>W{CHOKJK6)I(Ad_Xy28Uu}PH+V2$?jtT zL``QOA|q=78Tck21qU|h0=uKGMae|nU{OZH!rPf6@{-@R;sEDZ^YK#oQW#Q4vP<8O zzxsm&`Ejl1F$_Z}WZgYdN0sj2?*@Yn>JpD)Fhk@%s$i|<{}l8eVhZL#MRpV$p+wI} z--5}ZOc#A~;q2a4M%_cwNV4VbXdk>*#e8XqU~>Xc-;ykmq`g)G3x$qSkuKm(RD%eQ zk<+wvZ<(dU+9qCOy;S{<%mXEJjrUTFZ&V-)M339ZJWrYN|N64Wn>6t#T|7k+ZJ zAEV1nj|rY1$%8xz#UQvX5k;zS#bOtAqxpwF@al2*ho~^mHo8BVTy3$P)N9 zu0V`J_hrtJGuAWc8!FAgJ5odgtR^!zt|ep$KjNtuZ02M8(RWRgWA6A9hf35rei4py z_7YJP_9Gy}JPDTu=>~TlYk1praWsub6>`7=t;MWXlN1-HP)HzO6%9r5#f5_TsTTz} za_YD+!)hUTM=^4PxZtcvTAXNn^e~n&s!h9uW7rdOKwc)DX>d6vCQnh?q=wI0^NgYo zN0Dn`b$x3E;oO`m4*i$UWA zaA;KIt6$M(rdX1O%2EPVz9)t?LJG{B^CfL~ab!Fv(w$|ib(!8)yiVO%9R`QIBh#D* zb|%u!HCqSE3nLJP%<~@-)@5nJIFjL%1wn^>5L`pPpSWWDvicLroqbptIpjyC6Qkeb zBPA+!JNlKbH+5I1+;1}X)fTEM`MbH2PQVO&pVuSk#YZ4Sp9WuKVtBtYhJ+R&>O-Du zi+GnxLZXeoL5uZTN91wn&3n+)<>WTa-l>66-a1)YX}K*sm@D-XE9NV+vnu;z#UDhG zs4eL`A3Ls%@2iZl^rG_#QkXy$lXRG!BH?@oVNxP~n!D~k9*K^os$|{ag!slu`P^G#;+|ZP2ZBWX2L6ngO~^*to_1`LGcqkw3i(K09VIInHL2|o zxz-H~cykR^n=0_WuvAGn=@ktzT&u_0e*sF?X4^qJAOyddpWZ~^i#0O= z{g2UcF?0iJ*dOd)P{x9);ha?=PVx~_E{o<{qVy1QdZye!cCf6_7v1UCmBz4ON=5EFKMPD7*fiX8syb)VK3*t?54R)OcQt681HSVtd%M= z{&a9)JZJv9x-Rc%M3BJ8-$u^6I?#1=>hQa?P`X!*OLrkzGzOf(oO@qYDt8>$9_=!t zBA*h|Y?k_h8 zWR-TmaE_OQ9mizsgGs?o}YBQYMbW0eSOS9(gsF zZJ{h(Ke=KQ{ZmhjlsI&&8cdcA6NFm8#z62Ex(|mUbxko5#|YL(YaDVu#@zNP^##5Q zCIVxJU|gMV3&*)LKG5?u< ze4y0cBa%04lJ++RbV7sd+0et4+p{Jm$_SSI8qD@yTwUZN`q+ipj~re+I=UlTKqNlh z%;sjvM*D;`#rhSzUM!gb8@}N^CCOD?wNXH%_b4OlJ;NoLu}LIL|Kp@35|p4s(-A@c z+Z0nxIcaMIc4zUpC!E{WrQ|S4LBQQc|}8;1NI`od_+F%nyqrD+k~H&<%uonW|lJY6OkSF~swXFGCZwZeF1at7Tj_*KvOY>lCF(mr+Dt>?r@kN^bMyh1#Sg)XYgEtP@hJu<+f?B41(%xgF zW4{%yXUZk%{=$e%788Lv4eG-U3-MxP(S9QB5+gA^D=@@;8+&Yme|VR{7s>jY2ab7e z!kEK&{~|zudziN~b2k2h>{1NN)W~QDN`4q7{b#-Q0He(lX88twJ+ro{_JLhJm$itr z)LfB?uaMC95)$&-5@ONx!8-=qCnY|Ts9!&vYAYK(TsBvhf}Ofm^gR{xLF9mq%SLQ> zu%S(^CA8V>E1%|96|;Jd-d93xkD*p3ABEd(T^H44c0Td|oz0F+d&YQp$dswg39E4& zhB-9UFx40aie-fg3Rqx^f^&UGnw02r@!W(m-@7G%8(7xP#dPRjyWfOobQ4}?m>@!F z>DVuNZ3H>NxE2Nb84oonT{Wv_a3o9lYDeA;hxo(Lj0vsksl%A+jzSdYbB6>+sS$#; z7Cl2I;n{Eu0Y)w#DE5)88k=UOP%x7#*_4qTZ3FAYw<}!L`3!AG@BGm~ym~PKhzPL8 zbi(cAx5)P8)aUJD_M##d661p(HEywFFI!Sm5li?qux_%&{1O?d9VSKzALzP5geYQC z4c5Cs(*7b$;QKhhR((DKwVj1zMd9y*pPC3fr`?_+7OJ5ilm{{bh0&IRt@rRp@ApxQ zJ0g}kvkKT}ipB`b`XuIK=!4wprSe1k>$w7AW$24fnadea)WUI{kyQmnCQ((US`dQH zba<#fr{bj%u6z8gECY(b+r|_Fk>*nf-R6 z?iT{|1bhCHz_tNS3~{yYH6v8$>m|r+FAC@!2+ve%3b4D1$4iS{uyNTgVDb!AgG7fD z6<-XN{KKV@xu}*M@xu9N*T&>#2|&y4sMUr) zX={XwwgScmA$m$O>_gRzKN8|Ql_VwrpD`~v)Iq>`g6z{mF}{AC35ArhzzI(Eto-v# z!Ov2N*X&f9xDM)p^R&z5PCOJmUUT2^JG3)L)Gh zF1?%BvL%?uk+PLWQF}8{3cq#(tR-YsV4TCzHgDIZZ;OuA*xbX!;cYP*5^k zS1u?m8#$cTwvEEU!RrmFwWu*?3?U9jFZx)}U2Z~`K4p>~F$qyHBGNk=%vXRMnO)#h zxzO4F!cgP{gyC- zuxc=-n-$Qm5#BzfWW@K80yOe6l$;E2V_Y75{k<1>?A8s$6h)^=nPz+`#b;JiO1zN| zIz9OTMh_lhtbapnU!Z>VZ~(VG9~=T^(`CaUB&^?c#BhDC?EC%15z~@Goz@P4^=q&n zGKQtxiWLMK6but@eQ`5EW)a5F+Rvyi11up(Vd`=xAWY3W!%)Q7m!U=on8Gl_UD-Zc zi-d?n$u^$jT&%ngra=EutQ8ez?d*qyyVUz#o|p>?T0dV#_*D$X-gA_-TN1*rkxErZ zxRR=T`2fRh{&RR$4W{|AY#YCr$(oGRFoG$V6GQ}vFZL{~grF~BYV8zpyLacf){~Km zUxc#xGtt^2W#_|CN&}vKi03<3aLhQV)72m9s;P$%MronXfPkLSy%#gx5mMnQJs|v+ zXlXccIx|-zmgEbaiyqD&4rpft%Zd2@Q0b~V>A^5TwA&O+z=RF^k!BF3-%*U3Ir}zc zJ+{E-4j7R^LcFgHrFWk~F!0i%=rdSoCsc9%5iN%5aFT+gibZ0wFjK56R91MSN8xom zO6-DEJphVDHoRXl)=ewh`h-rr3#Pq_QBJC*wEXxY6zCJVySbJJot#{uqWI*ped$z&ldCi7(1i!3o$2aT;dE z^dQAGM6qAGSZs$qQI?v=%qSLFf9)u4^ROJae320ESwd_<7RYIyCB4BSEf73sgmM2@ z{!t{iN!(}%_Q@mEgAo7k1ru=slZ3%#!P_L?DW$WK&Jn|qABjsfaI{-ar%KxMH9I1WyQBmLpTqwc`-VMm> zz?y#)IF5fI>p74AF8FCtM*$HO@PI>;FM;X|1if$cI2-FVsVSa3>zxbfW);4Z;lG`+m5zSo*j{8(7Ij|E#l z;I=FXRVEZcI*_kNIhci0<1L5Wuuv&B*O=#g^TmFMLje=z?taWV|J|JLnG1Cc{kK+3 zw2g{i>@=-rkB8X91|;42g#-pA#vfMhXxAgeBNiMWF#ZCm6&>Z#Q`Y7M8H9H3~c* zEXx;~j}Er~&RIT2lDF`*#LM+#u4ik#+~Q)Hg#TH7gAt<4%P z12i->xKBF7r*hF;*BfV;ts6{?(mp~)WrM@OAy`_rgAw)6|N3157Qr0Lon#{g;o~1ulP@0QDCjOYjM0dN#FVf#!h`u z>C=isZN-TJQ&9C!R zG1vd$me^RYq(U%&NqvO&(}mp}D94WwUsk9Yqnt2iO{-KmXlI9{(w{kfvf|TcDcy=* z8?q_*^!4Sp9X2P1H(e9YpudmB8mH@uwMRC5d;^SA<57tp0p1%SqB!4o^29! z`5+LNs*6FzY}bqTX`_B>SNVr-MlasY+8}gN`P0AxO4*QPjS}5}s*%Yjao*VQhp>-; zv7-V;h7lp1)2!A`$x8p-x(iJj_lOaP*okIM-KyPJS~p-9a^se;q?0~L0i}V}+RQj& zFS}${>W{_@X)DQU`BKNP%hX?Kz4IIfUVgcGZTYz^+h17Sd!zkyGJsrhwLo}X-xYuC z%hHz#xV+Rbp4+%~%abZ?lnDVqd-+YSx6RS#iQg@+crE8qMRhdMk)xYqC!SYEh;FAU zbq`SFp?Jpb#?N(*5x0uKa(4vB#%AQYzIYKRrGj8h`;2?6Na%PXO2Mc!>wJpW{;^vs zyX2(8gQ!VC-Qg3aHX@M@sb4Q2Eiz**9wfR1cT|F%An)>afn-Spr3)fhY z=){S4?z?*;!A3nt)}J}+O%#mYy6Ilju5d9UH`s+0yq1#D#f}UCyfelAkalru`h*w2i8MuXsGktLy#KGL-k2m*HIrS7%)KaARv@MNuXW(R5$O1i9`EdrH|` zolWgeTXusZNxyiU8unzRwL}owSI_Cy#a4X8k1qv5I_rqK;+akMT$#7zCw4xY%Gh?d zZ%(#OH*J0Vi86BKH?K8J>D8x&zx_`^(WwzH*N-YzgI{^*;aMM1Eu~={XA|3mo zdB~P@tng9UgNrZZ#y??PPem`G&i7>WdxE$qA8x1CYjJ)7=+0x~3efb$j$7(XE3&oO zu4TuqLZEVubA?A0ITSW-aY`mPEZDD8aXYN5=){hyoOsu_LB?(MAmi!-iB;<|(EV$Vsb760C5}~=?Q(bA z)Ofp`(yBB2vy;~m?cOP5=zA9nK4as^4iZrVEZ+kNu&P><5xNyvWg(S)$X z%bdijD;hl__y@Q5F9tqDmQh3UZO}s@)!zH<%^MaI+`v1k%+Nn#!dx(n|6FZe$w{T593+e3#+TSJ;LMoQ5}+mjXIhVdk#+*tOC z0D{ZWT@PB1xKuY9be1Dwj7Fx@^fxY_rMe7<`R`q*f1bauyyv~%5*P66#zLQ~@SdM1 z!4!Ode@K4g52Af$iu8;!kE6k`Q?4SuiI%Yg?rGJ)8+TSbzpSeS#0WJR(m!8{CA)3) zY-+sC&mH;VHbHzwK<8*i@0hHx*7Cl*}UZ&R6*%>G>-Y{h- zi&pZY%)58{o(0#cIdIH4oZpn_EY5()HsFMw_w`|eq6=q1EuwVTL(i7CeII!X$CpeV zxNIH?$*zl^DE6NvKl=6SSJDqIE-oU))5k+ApTEN7ftydjos)H=)>v`07~E$>L`3BA z43hH=Kkc_6<`L{{Y!3o)lhhQji4|asBr;8Oo(gEjP8RN8sXT#;)L*53gxq|8sji|z zvCIFNo(4(m4-`(;7iYusHV|732YtRc@!KYrU@^(|>>m_UBgXhaba_t*Jw#3EDdRF+ zu(7j8*Vfi}LKGm;~yv%C&DSb0qby*=BjQv{GU^dw6e@(Dx3m%_{McDP! zXD!@a$Tzo?AT^%vpBDdwR*f;%u-l2L4*mb4TdVP5h#n!nc@fABI2EisHQl=BQ~n)^ zf45Ccd>=Wu;1W*izXRoOJvhxhQM?VO5})?JNkv7m_8>sIPo)-r-wc22<-f<94LPK# zXS4rrQW)&v{mUoLdYMW8l>=VIWP;oI9-3M6U!`Gk_ixN}o7%qmuP(KQ2h(3v$-eLT zuhRctIt+21;mG>uShWU2ep^vSWNs8N@$os^Xt({v|A!?08Mwsv0)qcDCol^ZJ|R&J z7;<$$H=wJv+0&+B*f_RYO77b|joEO2_>HXxf>3hE_^0sz7y*BqRz@?!<5W?va|~ru zMPC*%e`duJ|E93|Un8v%4GEDrqFPISh!Y2KJ?`&UP;nNj&-}Cb^Gc>qEx?U!VPcvzSLw;{T;$IEwlir;nm?9g zn)=T!v7DTPih3kB>d8I*H1?>#b?q}x7lJc>xZ>;1kM#;4rIC}0n6)=x8~EE;O2gyN z{)Jjn->3w|LFoz0xrNuA_j&C;Im*QS=_BF%?(Tcr7Y(P7v~SKS2O_;p4BX-2;c-}# zcG2rI#EKZV=EEDmbyniG4Mh>ZD$VDAQ0zvqXq-(>M1CThR!)2ccJRJ{lu`A)O$a>l z|9pY!)~s!AH>!!TDXUI%@N@Pk$e^vI@kdR)JYG#5T3bX8c9V#({*|2h6<{#MnWr_g zIka?iPuQgzICleZ_0j~QoCu4_eQJNMa{CAl%u-2l%U}MVedV>n)byn8Bxw+?NpL=#WXYbG60GH*ezKe?YDL*P!66*xNi0RfWKi6XW?)GW2 zKdkFkJw(y09rt=VPGyo%yhnp;)>1;QYgJ&m2&uqOZsi;P*;&&dZ^f!t`$y5oO4zG$ zS4Pd|!KT5vGvi)WmHMBoe6s2j?@VBUUUq#QjI!RZz;Xsbt>3?Vf9Stynb?$`x`W++ zK3c)JEVF!(Tu%OaAOfV^MKNNaJR6_=|c)L1(4u#Qjl>DR5twu!rKXi-i zDeFBku7xL39n>9|pg{$K>H-bi&>vid&2j24iP<>ywUe#u!7WeEKFPn%rFRiAl#f@1 zSVOGUzGuN5^j4k$DZTr!8nFdbTJf4}0itOrq#)pgdDM4Q?Wn;krjwRYIP5g3$vh@r zlY3_vYod({TKRYz*Gs}@4_&y=nz;%}I1)dU)N0qbd~|=mLGi_HvCgrVv!$@A)|J&{ z(;`)0#91f2d}kg6(SN zO2TTuKO(k_h@inY1We9Q|M|aY#s#^oMxUB+?@LWy>5QAV_<)XoY|e9>KzZO&5#PiH zRoH@mQmOXPaVELiaS&-TgN@N~%+DwPvP^BG0~n6I?s=Ef_}3j!XJ?-ZH6^Y$-~2vh zOrjcWmxPKxK?d{r_~@ zc1>Y6AVmVXb1g=8aT{gVbkFZ20F0NUoOX7~1Jn3+S7Ci`Q;hetJj)HGIGSlRX(4uG5H`c%6n`K0sh$;B={Kyx*gy^*R__1nA zt$)s8hE-9u@269jMr2pM%TZqziyZ%ko>;g$^sP}c6K}U7q1I&)tQy&lNkr~qRxGTngm$|3Cc`K7f03l~KiGJEVJDldAm4Q^ zNAKS;zAfRVwb=4>LmqExb#2%*w|?Wr`sGoPUIh04d%8<*%F_+}F`bl%12ZJ{?d^+T zQ|q=a-6<>-$;*}J1-;;g#cy*$f%@+vWe*WK%TueAOY9*S$WLUQjQ61}-1mKtfSD}& z>!1gq6N+&{s!;Xsy7FPq^$iVQHC|z{W*bzY2cTWX>!Fvt%=D5`?eqXf*sGJ6m>}X3vd-*+>{pw z)L>3xNXz}B!BkOLCDy=C5fXwO9u~A9ce0Y(Y0BQV+-rS&(_BOeYoz6fC@9aicr-Ln zNg1P(nZfYvS;m8>B^iuQuh$mj(5EXO1n5^5YCfa`73%Zyg$j8rqRrs^R1bg)ljV8t ziHb9}`;8BTdn*?yh|W8tyO9b%Z!_T3Usa499Za#P^S2o2b2%T3V%#`tB+!HJQ&skRdV16iV%*N>=8VuM{x>Hu z=Ii}1;c^=-(^|@5Gkm7pn?tA!ZwWTwoFRTrV4Z-NW5CXQxjond1QMebbKlefB053| zrEG&_HasnzFV440BmW>%>h5#0GQF(d*x2}TbA5GD;P=e9;mbj7k*RhR+3GXi-l_zV zU*s*bVgwTh6Kfw}JEH|_Sdbd5(q>pIef{_kUXAICM1eUFSe~5ek9o_y_}Kt4FtCx^ zI*ebNB4mRE=XxEUst5>3)7S|a2S+`pXL|5#=w*8Fz`_ZgK5=qpW(;G!eqF@1fQVR} z`jAdo zk#id5fZv(7LJct{03#${zh?BQzi2M*1DZE`T_df4m{|$f^z<1r-LoWgNc}`&s}ad} z1Rih&^enbM*KG0!RV33hz9KD;?B9YR`xF6a1>gr?uAOdF8+lmHB{UTS8=JK6JzI!$ zXC_`dy>iWgd9ODR&a(U~7rIjvp}YdKxbNnY|RLk5lJtT?G&k(USCP&>7$> zq3sML2fcsx?$;+1b^D?jc31QA2aVEn(b5!Tl9300p|RLie-@NXQZb{fr96Y<_@^6p ziQ4paQefy_MtQLA+JNJ5^Hq2_ai%!uE3~`aY{B6!N!Vrc$%5)7HbSA5m&QUNxz0B% z&4wZ87odF~#e4(cc)OQ-F>ev9V}|1Z*{#fxmXJubRk08&Ubg&xwNtzJJk7ID3VfL# zcXPBgX(y}b);o;E8Pcb=6jPt@no#u%dApHoM^ybx;XtNPgKi%Y(|Gd6yb(KV@; z9Hr4Mx7@9GR?UP~FxWM1@lK=>z|Lv9p$naIdhZUs&m7-dS!XY|L<{KAg3OH>13%&n zfJphOtz|90;!zj+;sZB1W^vFNcT8c}mil2<@Rg0gfk-4P&`NW157VQ$y(2|Nce{s- z=iGbsuwVqURxk)e4tK>NQTLq#On*AkEH+BMovRZzP=j0?tys$eV_vA9eg9 zI+ur6V+xP*oS&r4!io}Lm!bkK#UYhp=^GTWr-pv$kD$ua{SIYxdt$G}lcqh`O0@nR zGo@BBQsh^UHlq>(87Swcc=;=S2wFSEMN#YA@%k*T-C}iI8M}i*g;=BonwdS+SPoO7 z!Ot43+%&_*VF2816Yy=>lVLnc7ZWj(^i>}cOmE=QVBy34M!=+kN)gUTq^b2|e&4BR z$$GT8qME%oe1Djd66H6H6=K*8xcBT>b!U=8uCyYMwo!?H9qs!LA5J!{El5V7gagbk z3=C%%p58%BiJx3iETw1@y1KfCr~e6|wF@JMrVFR3rO9*D%UnzkYe^rVD0XxS%gj2| z(kDxdRgdIU=}2ffrkC80HYeOx&Jr}hU;4O9y#n-FZsQySD%=mViY~tlXO77aGOr9L zylm7x^VpF1Rg{x5M#;SIzdKidXdafNeY@i&h-|B`mxRHbhOY3SgZ;eoNkp*p{C(~( z{B8G|!8;#Z^j)Q>A$vxxG^C8|z2T&UXPI~)W=UTLB|2yVd%I9Jckp^{*DG%k-eGp} zhE?(W3rT+wi17JfDCt_{#+0ton!;1QS_X;3?J{;EQgQiTP76LK-W{pZAx$e#qeCyO z8&f=;z>&aPu_a&DNVlZP#MZ%`v*JfAlrr0j|E%-1u0P#0=Xr_bQ8MDh zZP?P>s~=;BrLI&vdk|T8`sI2Fwth{s5$oQsW?&N14f9`=>{gdLj3$mmIIznz+*jr# z+!ViKpbBL$e0S-yN*GySs{7_SqTGGuF<4Xg_A+}bUL=lvTuOz$ zojS2H&C15f(;E9ns|K{s(o~P*#As0#j$`M>gcuv_o?x$hRxR1(@2p<|V$t^p+q3SK zJ|rYO*k>i!}kB`=5CFCwVjp{BA6^q}_H))Aj8{VRauj z9O%t)uI5qiLU6(iO#AMY2iNF~7 zBwKlp;wPPC&v%r>T2fW%1LqywzU0KHs`jgd*)BGT**@^_&V=hoWcZlCva*+_7?O4qWQmi&2{VG+}h*& zE=40(@3Fh}yT4)Krfy#yDpz`{X;hX}I;snnISD`28!6 z-T~E@?CA$RJ=CiG;dmaVzwWI=y>p)NuPjrM(i~EI85$ymNA$mo-q-&@@sg{vMEp3M zlKfD?s}%X@4pE0I(qyIo;mr;;Uhh@moysaAQVpJpUn!SRX4{Fp%v^mAEjGbXiwOIRo;Rv%rf_w_CIdFA(yermSC+w zF*D5QAp{lylr{X2jm2=(ekG6x%w5xOV*$Td=`aj*ZKglE3x9hBw}D5Zr;hz8-)*8a zId|}5#=}roL({)3oY>C#5zPem{CSaYIIWjIOMb~-kGAwfK^=DcgP z(jcdMUFAM4dm4-#OVwY)c0*Y8x^1XvI zPey0N5$T(hx@Yf?85Fg{DQj!oh5Je=e65#)Vs8Ki03FHBlYxPhs@OKqQy|iSXG!^R zQ2%4VVvNi+038)f8UuLMgR=)XT&E(tgWuwI+hD?De4Y;%dNuF4_&Sneo!ncYEzS}h z>iM7tcb9N@Daciv!P^KKn)6ot7|@SJ>pxvx@Z&^08Rx<;E!f^PBZyrG z()RjFdAf6j7=|O0o;`xzCpt85T6eKcPI?oaD;;lLt>##Z=k6Sp@iffTRKZ6M^@<_= zr4my&*=gvAl*cDKxahjh`cAdIUIf~VfoXhoM44|Rvd)3EkbTj`x7J7Gbdeb9Dd1&! z&+M`7T#V@wfZ1DAyb2*`r1oCUC-6>(JS5<4?Z`G9|G&HS{IA&H+fvFj)3@0T3vJ zt*3VsQ`k4-ZCCVHpS0zq@-0Z`CHhDkHVyc3vpr~dE-gVyz-*N987!&81Gnh#`XW)e zl3zI%{iG3S-UJY(w0t{rzACX_PC;>&saSC??I6Ou;?#97@aY<-MUu?IM0qu@^ur@R z&gC{0V{-a2*&B)SQ_>QCC08< zq?vtY6-*}pP#-xc&U-TPkpec=yoFJ z+*`L!A45>vz9Ip*^aDfb5~c%#&9v$}J}_t|_#5QBlB;7Fmc`e4K5>&|a$EU&w$_RB ztmUM6@A1G{Z;Q;;hK5Q^mI~wTxviYK&X&>VORymPCUoK*{`Vc~7c3`OMI6uT1P@+% z=$%zbN47uTc%r%{au^Cra9tbX06T+0W9YY(Ne&F;Jo#x7KF?@W7jkKB&{t?PnVt2D z&#ofi1tOa-$4#DlV8gVw`dsNWXLa`_9u6iheG=_#+N5VD%96aW+#N8WO{24*nGV=l z-hV8fM%A)K`nFVVcsd%MQgSyIN=734NZseP$EC-Ra*?!s7nl_+GWH^6?Xvf!q z{eed=JZ{V;-E1<)n|3`>ohBI;22Xckc65-L$~OvT0`@KkAA5|z#(A=$^I!O^@5@X< z%F)nXk{sUH1O+CKdEn}=$al4SR8Giwf?fpq^U2N>F)79?_L=A;v>(QS=RM5vhDdZ( zD+P-OdKAVu`*O)DWZT3TLTfl0v&8HhmpOkSnNPk_+~xO4J!AgF_yvy7K7UJz3+H(| z7pd>)iY>86mR$}SP%Qh7>?OSfdTdNP+aEFTX=s1zA?U(A5QfkB1iaB8c&`Xiw+KlZ ziw>;pZUD1)c34e^>wVI>yGigz`3iYSKIG?D8AQ=R9Pf%Ypf^q^Gb<<}SPR zbuO7Ys>Y+WzCHUfx5M;D+NZOZj#7NtK8%9C4DTaizD)$ga7l*MmmY3QY2M}lOs_l;@3^-LxE#L^6i@8uHB2SDKj)IT@-d@ zV-E{W(O3Jdw-r3Exz!m(8^yeZHo%`^hfY+L1Y`G>I7tE*#P_8tHorAhc5-uGW0Q#3 zyKwpM2QwcQV=|sH2`;@{4zxk~JV?Go=?K)rahD7dXpLdy!^>6{@Q@f4i9kkQ4_riM zC=OnNe44Mv_7i+#h>P}*FFuWFU5Kuwd`jmA(5GW;V!fQ9?5yf<3{JLjrd3O~7R2sH zFpNWC6usSYc+lrplIU2Q-$X={Fa(OoV19n3A(rUr?Z&b79>uhw#!5T?uyF@81^E{f+WoLWJe zEz?yDD|y~J*-sTG+Q!!nbfEE{3RHZ{p!L2>pnz1oPhBdFP?mgj=pq!eac0m|6*=$f zkq`I^W<}aK-QT#c-sE$x>L2l^-Q9 zqh8AR(3nbvN67~>)cd8=@COw*pTd!k^|VP+Z?{4B zwur#vf!nm$uxmLS zsb(*Yd{6Hc2^PZV=k+`;fJRv%x4?WMnhcW(5f$}wt01_s6&iwSE!+lPxDfzf+5n!_j7 zs7XE!Yyvb@dzcTcWmII)<;%H&DP^FyBn_EZ4Ko=AKeS z(s$g4vxF9^H>!T6oGU^Q(IJG^4g3YLqsTEr1Gk&lN#U0u(3%>L?Eum6+VGv+TLFA60u|gcP~COVM^ld%K-xyfD!N3u3d_UFkP%ud*t3} zu5S(TwWj!&F`p_$L`5g{eGxjhAW1VT*uX=^7ip3nbDRbCs$qS{dEG8Z9X-(@d{(E2 z8A?iul*ixOp9#x2g(k#|rO!O$NdAJ-o|OKsLa$D2IvgNsi!{T6`2~5Gax*C-!nq}) zmmfDnn0zNfyX50@dNq`*s4JTZM!%k+=syg44vIeS5FYa=r@NI&kcJ2k??rC@hMaw9 z(qn@;^`iwgOS?>i6~{PULW z4!)YkEIqwb#b?TljqLzN1YMIw&ztG9N92&v;krdiM_9H%V0)5h4`BAq(IU7^bWar+O-M&rz?3>1)1$YW0_Ym;Z zHA~vmdCZ^}hEZK;hk}EPh8{WPqtnyTo^mSSUATI2wsTGe1K~@9Ebp@2+Qua%_>#^b zSF&C#>nfb_m~2J=&HGEJIzP(ioV1*!Ei5#YmXuX>WO-du#vQIPGC2H`+O&%#C|bWN zU@eEjmbM$*A`RPMZ8;B~m+2D+Q#p&qU>!HxxOl6`p4mFkN8fsYhu}T=no! zydTMe_l`E{bGm!S~ z`o{!OO)k(I#&5K5J_RPF`nTd})ue+1AfC@bARWAZij+YU<1urNZJ>;$B=O@wOf zbCe}_M2;ovBkJVIiM|{xTh=yw9Q)FIOuq3YVPW4wK|R3vC7p5JvvrJ|7+&jqi9(uT zki^Tcm%^BUNqg`q%MU(MF6LbvDq}IPY z^>LP@aP&%z)J>gUK+hH={e)6Bdx+i^w9%N?g>hszv1l)c8+FuCDccXWnySpy7k>BW9`?0><|&r z`S$cQ9f1b4S8~yQwRU+$Qb50gbO7zSe!%1HK8Mm-da;HJXM}%g0B$-@O@pA|Q&i4# zPCkiZg3+SGp0FqY|32(wPUhwkQ?=d(^jomd5GRvrASfLX_a*p1SW$EXECS?TvGSh*ro)Sw_pZlm6y{kcxu*R1fk)%_;SpUN<}&LblVb&wB`T+* z81_rR?e#jmj|VQzAsaL#Wn5@3&U3)p{tE_&F3+vA8ItW0kETcDL~)+6J%o`hrB{JC zJE=tH_pg>$i)S~Csn$9mtruhxd(il2%b=J|Dfae)kzcbKsN!vHRhSR(Wg6r9`tgR9 z>&;dtV489mu#+#X=dslas?%?ZO1~)gT7Vedy&o62;Qu_$1+pP?ckSj(ZmP~Xq`+T* z;q(GA?(gPT2zaP2a?)hBd-E+d0&Oj_0AN;{ums|+0lxO+Tz&uot((2pMqi%$d9{cR zldb}!_IZ*+IGTP?xQ}IyvDv+h?WUdCg+JWq>tbtLY>}ak%&;#nGXaa zg75+s^JQq2P-g|rZVl4v%u(H{m{nSoF~cxQ4~V?D*fWaKCPgO(O$PQl`@#YeNYdU0 z#4w`6CBqm;{60#0Y8)KZX%$v)#!?!2cl!Kx>*9Xlm3y(Ayms`P7-~=u_bpYSH zF@agCl&e5dLhVuNtagZmRrs3277v9lW^fX%Ed|^bK!OSwB{AmN0RX(i(`zBCJkw=_ zJq<&4@bkqm_!ES~(f(_3z6NbDn$`PEL}Lr;v%KrUwY6m9#c`W#h3pf1NnMlUxeTP; zd0mupPzZ5a;CCi{TJKtksFQxO3wyUz8p1wUZfp{D{K$l($Mbi}ctx4+u3A#7oj*5+ zaTTT3?+x^Z&{#RKPD^vfo|V!Mq?Ru{J*T~ckZH*QVEuDF4C%V?@I>d!xjNg#=ijfz zLHFU;2C+EaUa8Ryr_ANJFar9yMig2fQS(g^s`vwF7|2&?H)> zFiVW?OKtxs$IAPS3z=}$sS%j{HWU2ess`NgtiV|ZKVtdw0nIrr8l2d=U`iY&1J-0i zdX8#ai!{~waR^rWTraP3tloNYN2CI z6io;o%-hQ{mU8(PO<#~xc5>w2NTviOU*(G1i~VDs7SS7ymKdhJ)~D&Gu~z&Zzo%uH z>7q<)iLDD_?jAZ>cxF1YwqX1@AT1cC08YNIkxrRBv}2mq(hTUqKra=?5tbcm9{qie zbJEHtcGmsd*#K#crO>pdIIu7`nGls#1k%x0CNt?U^bDXCc+BT7@`wgtIX7u>5O=?0 z02_$r{GEbWg7NE9Qc1xAxj#hXG@I($^2 zHI}C8v17x%w>~YYr{3>@CeV$S9(TOm&s=*OxCf3`X^Nw5FoHcj2W=hCR|Uj+oXdZb zj7=J&<6$$3P~ctYF=nD%%!m=RlH#!t2KB8B?CBB-%Ey>mn6U=LRz znB#84rmrJl`emO%ouq#2W%@YOtK@as`dWe416sNtmKfgkIM>JLh;EwK=0;k!*Lh@z z!I?Lxi<6Bm`TZ9X;@jl73>4esmb#77Q-w5O0fa&tfjPpBZxN*A*>_&+?_Qwmm)@1r zc&UC~97YPe!N`=}1`Ed-AB-1Eu)awks_t%QysdON@0zn=<>AYjVQjmOEIX)ls_g)- zi4KX0XArj28@g+bK=4@v=tB^r8Oey?JfvBQ#e+go1R$I%4&Ix(2)`qnLat&T7RRXj*0oDG3KtAq}EF-G$!*#a++~_R6YA5!?qi@JC^Yo z2fn1|_A7i11PeN8^i%pgu9-*%5^02f;A=M!|#tM&u)$0T*NSkYmqM<2?7 zcsalBAnxtmGj(C{pU2~grDo1AGaHV&-9`(qa2pOE`YGVUlUlUzy3;*M>7Dw%sO;a* zv%-@iSU>P8m@ns40e)HRdsP|cw=E@jqfe9_`_>NM{p!cG(L(PKk1I)G zVHuLp(&~_em`eQ`C9(Tov=aIeWfV)3gr^jR4xajw?l~rIcTK(6CD&T#Ou1X`DqX2< zbH#je?N{!PTs5goV05K0;Ym6K(MG+H(P>IUyp(ch{XK@vmx{Dld3Lrn)vgY4mBbvM z>8!n-o~qJNKS`yt{tP!zf6hXTNh4aB%wAaVRK4tM%&TCyTGNf6ztNqCMx z(Z*QZ3l7j_p6>`;3vF|-wC@RiZl}bXwK$~*1};l>bIaMq^j+j|#GGO48!WwN8F3|I zg73Qc9@9fG@4rWoRJg<=KKlCmdemIL_S%`z#q6%6$VTW|t1C1?_8z)^G$Qjg(O_H! z8G-qvKNodR!tyScb=zqsxp^yeQ8T=bI+(YaTkCPqXQo4JbO+R7SO;NVq7Mykd;*P? zhRYO5E%$=c3i;_~6|&g(LC?iEl` zt%ugV(i^`;C@yFJaaY1E{3l-nrnj+6r1>C8Cx1I#bjFc81bsK z7rr(Z)z^xc7oa8Js7fY@b`7t+&fh=qlqQw%h|<4LN9w{=II6h&7mD==yR~s(e?Hlvw65;p#F}WZz%F(fW-1 z1{D!xjC*)!e0=?A{py|_O{E>y#3B7_;t)Mhk@EDG3Rlz>WIlX<+a`(eBgAJ*LEl9# z-?h|X-0mGylCN1P{s}e}FGpNk?7V*UEHDlO=$Czm_=52}7(>@Mn0%Y$j!k;L3iCXC zcpiTKx=0YR2npE7u4Z|>ZiSG@@9a7a??5}{mL5&>i)8_d(2w`;92`hv3@WRJ96h-o za#+2a>Luau&+Dl<@U~5Y>M|Cn<(V~}A5^^(C~+HQlx9 zKekO7xjW!2?QBSupY!nO^EI#;8M5z47{o6OQ7Wq5AWb9FdRT*EQql$`4)5?j5^r4f zDhd&xy5GV?i03}EWxkIP|5ura?U1%?z`lgQ%Haa*$nF$quMRLH(rYI(qm_Iy4~T8- zwST{LkN?wA9iWC0PsuN{HvOYIc0KbKm@x7HkAcqhpLi{LT#Y=>yD6l9U=TJ0Wq>pE zu@t3CyR>YZrE;cl$Tap>OFdX9!F7*F%7f>4en9~?rhcOo)}6#@1O(0tsfuBx<^To< zf>wCx0a!Hl`GhOHuT=4z-Dk+?8CC%fWK z{kr6PD)e6I3rX3tIz1EP@FG$Jg9wba_IdFk_~x2p2dA}kZEb9?tNZ7N#<}=fGTKqW z=|uN;MiAqp7EKmMm-C3yRlN*J6Hq<3$)sel_u?~+#wu<`siLTo2`{NNy_Jc?s!J14 z5pZIy!FQRJxbmRj@|_;I6j-R_ZsqoS5+xdl61#d1(k<=M`zm8oPD3QPj71H9ra75# z8$IxSHC+0o-){e>+q^3^?+UeX9#FTZpE7A}#h~iog4EjfnnIvCIg)w33M7(&E#e{^b*p!EVtC^(16coE~(Y z_G9z6u2s=+#NtMwOXAasQg~_9$B-*%OhxR_Y=<^WdA$FuP|5)oa4B;VlJ@q&*Z`$n za!B;yH_V`gx3_l@m|82a**MNp91Oks6H8`8o)jti)LrIzz39xd2_J$tA{}oo+pgx$ z$}QNy%zT5zU*2$Bl>QTFt3v^BCSpSM@0|~|wgV+iI1feTYBn|kEei20TBR%SE9&Z! z89+Nahfhmax3D!kWLWM9YX5OsW%1pMwV$=0BCozDuO~l`{?b2xu}NfLSomtV;YSbtrM6NMHrLjtiw55vq>6Taj*=nc;0L3k zitqEghIx77^{W*Vuv+eSywD1Fw2#WH$^y6Pgw5vMlkHkH&Xrzy+fdb%nQLqi;b!Hl z+6X;w@Xn*s1GBXp(;vnI#TOMZU(sUIpPp%dEu*d1byCT`Ohw}`hbKv1;q2N#KSfR# z#NlVXzwa0PVl=S@(H-?S>iC>K5Z`lYW$HtGX6__`>>HiT@I7I5;bTJ%KGQr+c<_OZ zUA(5sm3Qdy4rM+CTLOx=Woxt$IAL>d+M%hhx?%P3-~ziF?G4Uc8$@9)B!|^c+*dY{ zhQBmTjC+$7Yq=+%nL<|-8g;MVqCxYClCy3_@xNn>==r3NeH;{O=}rCKHWwokOYDtX zuDZe<8|@kW4m|IhCPAN-Fg7>;s4j#;Od6Y(OGz(y$L!A3F%xKIqiWwHgB3&AQ>j*u zcs-DF7PSkJc#Ko-7DtIeIgiKMY=eut`;J-xisA8DLxcneBI+MmVMYMq!Gmu_UBo#M z3OW975UVUHEO=`ch0^&GIQ>6L5BUXRQBp0oghJ;3%SFZiyZ7H9IDWkU8>|1su>KKf z|2MDx%W(YvZ*H_Xn>qtOtKGTtQMbjw+~O&?lHRm;Rq4M-8J&A{wrcz}(2pK~@+@B{ zsCIIR^PkIrA>uTBBBmQ(2g^!*qiL?+zdKcv*fXn-r}oVRqy_b>=z=TLnp^Mwj%=sT z@EH!b$40^a2Pe7^I5*MK(njXxo+O7&>_N*8=qmZVRuPyYnqr9v?P(;Ehi5A%!JO z7Qt-2I-?VJ=_IGU{7*XcfvAp`0^&x}lXq*PfnvAcD9Q%A($`c58lG+07JQtwr|>%V z&Z|3mUr;9~^wuct0q-f(OB2u81NDFZ=Y}90%;!I4!~EQ<>-;V}&@fulkQ_!uI#W({6hNe69RP>rn<$+EuHrc~{ci<-Fe9UcVV$@WGkV)xEf}?@M3cOc}^M zA+rmK9f^Yw6CYfTh1kg7^7}j-akPP1^C5rA{hdwW&6|5~@Z`9%35hpIHkxe&6M7H7 z-JF+5*t@K*4!!5-J={~@O>^~-$#gvy^Vz9BmX1%QfzF)LO*cWc_~;!bdBY%Qq;=cbbWZ#r4Z$HH>{{w-oj4&`LM_3 zbZN&$VX;+3NtxoOuj73x@z!#_mcMp-!ZlR_2$cUebz$757}lty#KaV5Nvl38$~j;Y z_{!U$%C>wUMF>vdN<2=HTOO_5&MX7S4f{RMNZ@%ovDt=m%<9&d3AgK~`R2Kd8|K2= zqaENxD%Q=4d!Hq6S3XPQ_I?|AyLc|g!C4{f(+lI`Aa`^-y~>+}v-Yd;uGgI`pObXP z`$~}Xq{FYmg^E30EYiluMS3#uC$x-8_)Mac(kP08al_-Y<@|vL(c9v0*bBnTwRA}l zH?enm)cDQ)om8Tm-H6sWURl4I-M2p$2LuEX@K)9tK2IF)ZOhf4HZkwkbv;BO?cOn1bAXeCwxO zUL!?zur^DC?9Dgo9q%pjWxr8AKiT6GD@=*8A4mE$q9HvsyAsaTVE{Lygo{+bTglJ)4L+@xLqw)}E)jZukp=?)ddE%+XJriXG&j$_V#x}VLvz%%o~@=<}KmNlID{u51S5xf5y}JRh9$y(z9Xa5_$~ru-leQ-_zPW*m)&vh{EVZ+N&$1-3eeii%3y z#u!vJ;`R{;1UfjLbyAg;l|{*UB5J)sp!%OC&4z?z3|yUId&-NqIe;LM>-NsFVaV_jZZ|@6U!>EMiEAG5s3d-5DTcJL?+y) zN6-%@td*LJ{_qiEc%zd zB`bL0SUS=@z+xnNB`o8G3SFRMfE0n@$e#?C<>{8afp-6h7VM{Z1diI`*yyMm>0_mQ7K{kk8dSAKS zI-??$pQ~w*7iS5m_{kS^{rP%^=d!k%qMQ963f^ET2&_fV>y`3V)X5_>x@EoK-`y_y zxANrH%2C=by4d=}xsemHMUef05W07E(u#TOS&H)&+U<)q+-8PbJEo}La$+%8{Y3~`cVwzuDU#LjL+ zc;K{EdEd}?yf?FSN@;!_+qpL_t|aAtQ`S~G!FdzT-tCXcJvMov<6VqM08Mpd93J%& zE0>dw_StKq%}2(!j?Kjo)bEIF^+Uf6JB{fgDBV}i-6lpjH=dgay;A#}00fb&#x!Y( zd`t$Hkb#h<-##3MZ2~QNRGk(FZ%sF=`%jI=y>NPj|_b) zJY*aebo+r0ehxmmC~{Enwq48C^j}HT+omdy*=u2E8fB5H>A^RrxWsxy3?fqtie`tu zxfk9>?re2~GNdVDYvpTbNY=Nr8I?yaguTMm{$ zbyge)D)CykKjL9To1{&R$q&c2?w3*`XsfBrf6dVWN4J%IC0Jo#EO!;G!t(nNv@zy?o%^2$(i_h)M_#LY1 zOo$c@8FI&%FMn!(UonrA#glXjK4JUbb|z*h9qk_C&u=QzO;ch(FI96uf5kq=3Ko*# zMZIAsYUNAaoJ&@EPp`39H6fW6bHVz0q@8(ai(Xp%vNq4{Gs3*(?zzJ5g+R^glDM3t z9e3Hfbo&6;{W+;-XlFSzdNs^hgL$rF(fIv&PI>}d$(wKNGHBr8Nic{N%+Cu!1c6S$ zW6cjBqlc4YL@0y_aLp&~2)*BPUMEx2&c9geD}3kdTpHOx`js|{a$^r2{BD>}&@tDa zKYC9Tra1(@&d{g?L{H-G_)J$=9V{nk%$Q|qtf1@Wj(xGR4u6sY@43vAE9+z*Cls{=BD$#mDMt)l8yC4?;s->bqFaznnoTVmCe8unC zzK0@oQ0WFd8eu1X@i{D|BvFue$I#B&=iCSMSgYE;(Uwhx}SyPZvpZ! zbd1BtOMlBx$`(k*K_M)2l=Oy(ws-qKZP356Rf(c)ZD`}q;s0n2Vz{^W=P&Wfz5nw; z|31LM4ho3*4>|v>y&z|Kf!ji4^yqEe|I#N{8-@w1#{YW@WO@;R=SyCv{FCi}bcjRp zCSXnKuZ{8V$NxQ*3c&I{lXIND!vE+H7dr;SKL3NHKN24YOO@0TTY1EP>+SC;ZpO)f zqv!AG;U98++7v9P|39PmAN}J=?)^`au(k>=fg$aZrG=h~}18vQh4jAFnJd zzDF;NPfn&jP)uNAVi$AV+`MRsmJH025p?dq%#s-;hiI5orA(6SlPiY|a3D zB3)%X4&PlfkDKi|Y)37%KwVqE)_h(wFWw>Q3_>HwiDUpWi?+ZHfe!y*M7v*z;_3=G z!xIp;Z>q6n7i2ya^OZwT-5#DHgCsb4r|K$c-Y&>C+&c_IogUk+C`8f@lAjS=m6OvN zJi82X=!HzM8%l=UGUoR`eRnU~j=hsdGS-Pd6Z_c6x29bf?^|PLtpbeS1PSl5wxQ}N zvFq*!3Gjc9#tS?L796o>Qc}kE@89RCu>&>pt@_oNNz1k_f%zeOIg50=b)kNersO9M z_Gc_S>yj}&!oMJKLnuobL9)xBlDvIoe;G1IsZ2A_?i$*8`(mmABh0jVJyn=VvOlvp z*w;5YMsmBl{qwJrcT`LF7(0A?eVg2WY7+mI3~rBbC7t%?5SSnAPlTNQHJbw$)Nt^! z`+-&8cQf>62)q(Y6XN65f8s0(d&()1lr1j&3fv8kti~FZ>ktBd@u*B0&jw3I1I~Zl zkNuv6ph?DR<|N|{LRts@!~Q!yZ<&izsePqkdWVnCFF|RnH1Z+de=x>m_@9RSpD`Js zB*_}Kmnr^#Y`q0kn_IUv3^YJ+3dM>9iWPTvD8&mch2jo{0tJe@leRb%FIFh-4yCxe zL($;wPLTh}Iq$je`Tl#y$jC^@AbDi%z1Ny^uDNf^&wS@tTP|M+7V-Qa1M`d+rFh8y zkd3kT6fvUxr@(dB;wx8qme$9*n5SN|vEAj_R{6IVSFRfM`}wr1G5;D{yo|s!A%sU2 zjf8Q=g@SKJi~8}>n&WGa*KEwa)>sw&QdTEGi9~90gv9>qzp9k|7|z%#jlsZyL#UCY zh#_}*0D|yvFM1o%Y22C!Y%$Kn#%4SHgDx&E?t*e8Ir%9I@gp$hzS>J!*7i%fOUR1t zeBJAVCiq9~3bQX(XSCx#Izqafz%sKylYM_}^PTNA&};WhLqj9JW7h&b+!wZa-&(Q2 zK{~w~WXm~!x!i2hiFv(@=W7{=d+zMqx>n;`#T|eV;6U4t-L(ot%-=I2Kg#{b&Z)~g ziUp3brS^Jye^VMs^MRWI-df}>vQvDs*V|1{T;0zmsDetTO{=fk!i`B02IE2-p zQHzse^I(+Damb<-LU)(|HoOILy1Ph zW2%k|zb5j8QaySR*Zm!~+`-c=G>{;A6S*Ep(;tWwYjDfj-FpdmlGhq7Bs!G6L7v}k zkrfe|>($c+u|RaIhTu}p(oHn9VEU6-Tpl(*G<8BH#m#!~g@7$m&4K0=x$j^Dn7xS0 zUK9>Fe`4<~VCG>CYw_p)v?l+j-Z3{=U_3@3=a@V6f~0r1wE1J`s!9^K?ANbfZHB}_ zH%jdFFLG2;EGUD;JTJf-6Im4B5|WddXvcupqa(`Y+BK3;6PA}wA-)PlnLTAcT$%&eszIFKTRamY_W4`9;xE0c<( zgB`vH@ASu)eq&7cc?p=}tW|_^ya{c<;IGrqJ3L4*OyaO>I2S0U}zy`t|2-YhrVSPO5|Hf}w= zV`5_mKOy_y!7me_R37r;NB(&7&!3n;f0C(C{b$S)qpTCHzsY-aD%&Z05?Md|C&BjQ zW4P=oOlLeortHEV+;4cd^Ui$e zyHJ48B`NPrllL!-`R3boj^bC-`mU)@o;*q93}-^VoOg`0nshKIW3^Wi=?gwOKoKQg zf=5rCANoH0ns=GA{d7>j3E&PD2z=?F@$}X8RkuV*AjxIFiok-Ud)7f7-bv>Z&;+_B zverAK&=V=oL-#D!zcMcb)`SDP75mzHx2>Mr)AT#|WQW|YfNz%JdUO8$;8WYdPy(aN++OKj=L%9)(N6INF4!C0Ikg;V->1m<2c<<^i zajOW53!pCi%24X>DM1=(b{czmC|z=72Pp}Zqu&1YjuGa7?% z9E%WK<3HoZO;=l^S!@&xX9spvkA1z$>$NK_w7tbh9k&c8ThRn97mq1DNfwu(H*I^A z0k=ToyXT*3YuCz8YV0ekw+3FvvUHEN=q4tnW+;5GOr=BzPUY?e({aeB=59Tdx%_0V z51Zg6#fy=hUPgmR@s=3>K&wm`Vg!V}Bz3jbDb z=CBys%>W3Sr&mMkgJVE>&=7?LvIXz3XtE}BLwx6nwF8eZ96i82_^|TMz7u#o-)jhJM-Xlq{ zTB0kVSRx%U2?u3iuSLEJ#$%UcdDuv?o2_pfpgbL0pn*&fS))i=-vjwgqE&NgPNeA3 zT#aoCeSTiv6D^kSzW3J_4R|bQ-&gA#RtJ6{Q6}}qs&Y$AgaWwJU*(Voi~rHDIxWqu z52sr!9bFweo`Sc3W}J8Vw|3WTYW$ydMU@eDSZzWyYv^@N;l;NBi3)R1+H-Ex8zRP1ABH7OP2@N#zi5R-;o(UY6Vv!tVrL z3Kly{cFp~u?sxd=U7$1YJzMxF?1CoZ@Z0NCmBio6A>=lhd=GRwX03^r;dUJoz+}z8 zNm9Kn6{!$J*EWz1U2N*K7oB zIX$vf%R2Cx`GK8S+OC6ywXr<63z&<1WPX(1-)`dg9Fp$Te%-(sewHT@bkZ6K?a_Xp zYKtO@CRBz`IrN7z=(Mj7GZmg@VUWi%aKNSJC*_}!FFLdi$xfTg&Z5py&F`gSs-Lv}gi?Mew_h@PL=j_^ z519nURZBT*EC|lidV$OST+kH4VPF||9PZ-vW*@M~s;obJr{@riadega6l6Mp3_Zdu z<^g#>@QaSE%HmWyC&Fa>x?CrX<9+t4r~B;nM=57*EjJc|B($Jq{!JRi+p0<^qJ1qW z&RQ7v0ODQiSby9_Zd-;Fh?zL;(@p8BC4NOL3i1N)r9oJ6Ij1QLmkoWc*J}vwNScmN zQx5S?U9sP0ENWFh{JtaxYIg_^Km0tURxlT+2<8Bz%hJu@;ZZ5K=Bt&^l&W(T#x}}- zCaqiQ{Q2L5B85j(!7&pV_C(G`K-R65GpW9W)oWS_;!dZsMc6RKZ{X&B?!`W)fxQ8e< znvsF9-pCo{vD#&S2#8iN2ZRwec4i)%agc9O(7!YMbEe-7l99cc70B7^tGZf~ZX+vqsleflVC5n~$5?)-1C(-)*e0cYLUFI+6< zk|T~0Ghzz{Ob8_$e)%?$DkohtumyvJN$chm!?FLkUJ(52sGV3^O#Ej^6&4A573zl! z1?aV46>Oxj`Fu+Ef=L*gpvCxDnDqh;=Q8e{fmO0?Vw$PAsAjmF5Q}C}nXQLM!_Q;k z8UOoMQ4h=EXsYz!Q~RZrSibjZ?m60zB;}zPqD45=BQkRmjvLB_glJ^23?_(#aR3gh za;k8(mA}};d5jV}*S^@0Dd68?VdZ!I@SJ2EIf{fLLgKQ1csuvlB9GW7aIhJwc|WCV zM+Dv{t&{Y!|7>PMc7*p&AxMDY_E5W~gJsQxbElBOhr%^mj_FHU&lT@l#hGwgGxxFb z6~pbTiAEeJLE5i2#XiI?tX2`pOte5w>Lh}K&&qX4Aq@i-^O^k}5^WlAFaDmf#X;eO zTvzKOeoF$panHmWQ}za|iD;{c%etznx6RJYODEkeQo5-U4`SkIPookzx;F1eYo6n6 zq8T^;bVmL0GYj$$Skdx@-p|U;b-_7-?Gu*lBmGNSKYz+wPNK*0scKO#F4}bpoO7*U zG(lNex^F&}x!cwJOsNoqj!+6^Y|Mhu z6`kZR6j2J6Iym#e!8GYlh025OCR;mE%M<++h%&k8rschh&+{q=JrZK9(J720uK@QJ zMWkl?llIj%s%exf=)d4mSRL4@04ef1Mz&yddz#}NE?;p6>4XaEFuiDPr4-y>R<<*y zC@16NSF$kYMtHe!8PYlF2Zy?TvK|ewa!%QIf`vMO86b&9Cd;c7^B76!V|(Q4pSiEh zT1SIaFWc2=p11H{n~ppV7igXY&sYGz%{LDU{=L5w$jHUBpx6*vu1vZ;oVoE(`q?DqSM z-eJY6EBw93eq|Yz4eqE}L9`{^$3E2%xqdtcwb}*r4^=*s+;ogmXqT$-4IY*k0rZaP z|N8B}5#Nkb{4FPBDx=I|q2w;Kr*4t&9rPu|k>7Qh84cpip3k3w6qLev*x)sQa6lvY zy7y5mJuQ>L)3omKtyQwKQCr@|WKCCP^s6*`k&1-2B&9b$fnAoY#-UD6hkq&_pZRG zxw@>cYV$n#uxU)VS1B~%dsx(yglPpU+23!i8_H38xv6{@= zD(cI0_|~|%B{#dEZ&SntMP75qtWNM6m<^VE`qNnZW-MkBWEgoFKc(1&xtGQ3ZQxPN zx?n)lsd3KjVDvR?3&LXUs@YSZ|J0E0lq?>t9n+XiRQZbQv*bS@)qgLQBnorQm@I+c z0CsDWW`h4xK34C~yg#CvKT0)mM3mQP)(%++N;bu z615qB#Y@O$xQKg-Z_rpGMZk$}a2H|!WIj?-8t-2-$KMg!4{9hzvSS%`J&E%l2pjIN zpCa$ZABdIa;#QMj%>C8C{aZM$b9FMPsi47NYQJP+cp#Wpv{y5B$stdXBu{-(lmM=} z3je6pNkzQT4k?rgc}?+y`Z0g_+|lEQ|9ywgWKmwfZKe#9LRHu0EqRT#Fm;Dp5@(JV zUawP;5{7dvlr8O1!Y*8ESiLvNcb>x*IqJu}AV-(N2)M)k%uDjBC=tcm7v|l`v6Vx+ z2}FbZI#7F=#l6_LC%Cozn7kI;9r;he^LN>Uc+RjR+;YP4Dw<8`?%WnV$ts9DrgYN} z)?e-1iP@72_i3Dd9c;#G29@3BEI`gAL-FuuSA(jyuq|k^lYQWibU*sSTogH|T0Y!M zlxN|y+Vp{GqH|}3mrYzY0UYZ#nVdB=0z==WDrENY@?E6VOp$A77pEdveD;Cb<5Us> zr2ie<|MPrChLXzMA``8ruFj&NFxG(!U8g|+@NWpHL$g{g?`97(*kbzuf7HDwg&V(X za$dR|%>S{}#HFdH&wo-_*PV#3S1K)9b;17Dwc}@i8jkTo|BJza?N>jB{8bg@Een|1 zaP$9&)GU{&36?xe`jxd%(bDKUP;o^|Q1tnonu6h4nc}3~&v@^-Y<;MGIK$xo9$$aI z_J&I4VjeLuF>!lcmAPQ8aAqA^B2y($Nb`ZV<=@}4JIH}*o?5u`0}L^FPz^ulV={>! zZdCtAkUd68WoQXx1RbkAm)2c)IZ}*KZ#xh$`rnZFd%Zx!MmNFMTV$$l&p%(zcm@+O z{#|zc>v>BWWf|j-z7{g^zj(g?9=g2~z(7V0edhYVzPlfhbDt=+VE)go|L=>IQQ$)f zVcIUl8vg%#__HHOn0^jqmcQVd|Nh!vZ{>gw@3+E81OM0h2b4Sr*#&zusOWzM9q`K( zslbO>JSN!x>j!}%@e^kdCUNi zBf;Fj!0}UgC<80YNER3*a>nJKYzkTMav4HVt5X8-wU)Ubp-X}vEiEnIk%xcz^7wWO z@lT*bv3XTNmu91;1IU?m|9yFmwbEjk4Dcl)7#){G<15;T0n-u<(%Bu*6-ZCLqs<(J zf*Sn%YBflj9rk?i-MpHy3c8-bN&R8Jq3V~O!?h8#Co^}tb0&T}T4UAmjP1a!@Q?sN zxVT>bN9fs2O^aUC;1+juW*Qo)PoF-~0sICp>w#S~BJ0ag2WO-b z6A>kKiShGK)hzM@tX}f$?kIDm?HoSm&O$c5dJK9y%`WUjS6y9Q zVLi>BpPyfTDWj)NpIedXJ?Z?zb)rIBw73k&xJ05RBg2ctMmmRux14W#W61<0LbVeyu?p62Q^PnC9m}3M> zPc23beXo%?FVnZH4F%dDSxOtnX2n2PMsqLV?OZD_txK1cuWL&Q{f?Rd7Rw#Lf7gp^ z7YVT0%`GhYV6B0+MwWoyncH%NI=`SGMkR%xtR$LNjvoi5z5P(c7aepc&=@*ZqHpc~ zj1n!udj#|N*5JZ5bg5MEf zU^2`sEb9QP7e`{~HOk8bT*;~J6I^u!5&OAOJqVY8A|g5x4xbWHr$T#x4U3o_Y^DnJ zfAf{kh$OUp(jMLh$Q##z@;d=2n{{>jdx!YLZ{IbY5M8g3G(Z1~Lb0C8c^XVv#bH;d z!3=Ejg(dVku*$N~1}Y^zZ})8Esu5~*50sm|z)3Ur_V#{Sbl>b9$&^|5T!N?l#nfCb zdJe}($~>H=-zlegohY>aYENBy?=yT&ybNyt4eZ8ye^~KpT~TS}GP_w0M&?y}b@#P+ zGq^anQu(jQnkE#y{kDCc+eb(T47E_u$f&;z(5?|c436?L3oJOrdRT8q&+wdX zqR__xW$rW@?k7jbl#>^t!US8ppn#l?#5NSyLB8o8ppJX~k^f;d(&ueS!w$1{lUMzB zuajP4^!c)#`xXo@{JyD8nu&$2+T}~l?s1?){ZcmuZ<7GO z%?!1w=y8x2Kvl*+Ttz-a1q7gY-Y?m=<5ZaU6O#$rXIRZ-{!GseY$egN_qSPW{Ijy` z9pDODBy*FKgMmBY?J!^e%vX8EO5ka z`pJ%Wm|?O`E^M)zn)h&B+-K?J42}&9YVVCVr!Rm(O$~;jzcVoSNXh0DLT&UbaOzOi z_a?RxfWnF2cZHD!49x;8u{{a|`>cAo@iE`3m6p(W4aXlX+X~+nBdIoT8{huGxCfiH81<3pgoXcbA z#}m8CVAU#XnPr$=0bEdREiNp5Ep?@8Jug2>kpow@x&Qu7cFKk8xjitNQt;Q3hm4*c zT-u`4O&b*lOX-6r-O^-QsQ5m7yOZ1mxRwM2uVT@Pn(4Fijp-x>eo*Oo94@fPsZVC=&+OTVPv*TIa)dEC9c=*IZ6Ce` zW0B~H4x?{$T5f7RprDNYOfaxu7y%T_pXO|9dsjL`*_u)qFU>DRFE>D(&x2Yu!CS!G zQm$=%2M|s1e*yRM9Xz5E5{3vi^zaGz%1}*3Y@@S126Rb<_M!PGm}BOKdH7-1@C4X` z!S|xc?}F-*ePFXYjLdZ7TB|VgE#E$#=#k$w9F94A4WN~?)B&noOe`#MrQZA2nvC=P zt*+Ykii)Q^D~|!n#Ex1L$IGtnt0+2v=<+|7L|;(Zp{?E8Yv;Z6z2Ed*D>S?-Fp{E4 zq-&!iNcCY22_BjUU^2SZ?#SAc49cLFYk(hOVUom;N;I5)07RvqHt;OAx}dRa1o)Qt zgFwh_20*0EZYY?PA)!dTYqT5xX__rA599E?4rQ}|@}ulC1|_C%i<)Y+8~HaJdTolF zz?U8j#DN$}LAwJuA_0HP#YT7T_d^t4Mq9aDE$t8lUIvczDuzFL8(|O^_!hs#=&Q0E ziWEv6Bj+II>7wxguDa{IA!)YQY8a0VH5iNK-gjF6{jf7@kOzQ!f^!sFmkol~!MxXQ z0+Mx5`w0JKbycy+MM6>}ae!{M%^*P$Ik44AZ{Z$TQm3hY?k}U;0^{MmBbDzG!F0&Y zn}P(Q9hi7Q0gT7rKmlOwN*jwrnSeLM7#+Navvu*f8#N=a*48KY)6MtQR=W$>B#!ui zE%sa8+8*USu1jJ{Ztz-SE*RQ)lq5VbEZdED`ZzP%ziVW(wLJ)uD$;RY!N&Cf>jht`Ws<+scm3RdwAu!IC=fLbZr9)s>@x@vGU!EF+=bQ`p${-WTw zCFL*3T`Rr%Kl{p4OCk;1X}xa!MkRrb_WSN)F6VXk4`)v2?%%<1@UT~UPHzy)3&A(0p)hhNo6X17v-N z@Q_W@fa_ra(rfo;6E?OVsWOdtCj1Uz+A$km zZ9P3B9u%bNy`w(W8t~Ghl3i!*`pU$0%QBNT1eY|(mxOp8vhMbD55cSiVYvPB(|jC& z0U}Fe_TAt~=VE()t1lm5bbxzoWDq9QMis$XrQlUbo~&fKC~;99nY2M*d#q%56N6JC z6>C-UdJ7M7WM|0e=ZW^zK})x_k@6A)&GfjWWtNdtK7gJG?Zaad3D-my=gm~ZH!5&t z9>?yxFWcH>XKi8_nnfB`yr=CZL^jfHrzalU3rUI^tYH)(Q?@!b1 zo~qRZ-6E1iTMUXwf?N$`Dm*;j=o@*L+Mg|7fpa)>LvdRKrHfF0l4gFp8#tI|%NcqW z@wl(^@+ZgdJ?<1w@VI-BDHXf$>yhNuQ;m^4$qXnNd)3j%eSJpcjmc}#wJ(mXcoyd) z*!*Hj)f7$_jq|udau$j}^6OVi^ic6=_DRquOOo%wm^OUAN--7-7+y z{JdFPqiZcV1C(IaV-#GMs+Ra={@mMdy{USQT_k@42J@6lez^rufwez`j7o=yvqNH<|u z#(iPUyR$m|q~6AvsRBa2Urf?)si-)~gCWAHUio~+E4aXNfQ~OI2i5^ORh6&U$qD*) zEmul%ng?Cr6oP(9q&e?S6~}56YW|^SAg&}Y*@z8vTBH%S(h>zHKL+2A6v?CnK(_?% z!tm0fwYI<;arlDNFd5pSgTHiVtL^C~tY9uuhe$V@NtSDGW|{H#|WLv|p0cjqt?9O|_d(cguyP1$Kgqs*d)c z5D=5-L%9LwB9|KkmewJDpswX&3<+g=hJA=p^ePhB*7D-`RqHHn-MeVKoj|8N!kkbe zd$L2qboHftKP|0VIzLZL^&tDZj|;V3atE%#NAZ-TAe@rH5rLs5csl*8$Sj*r*-qnR60dLD ziwLNdK!=jW<ITmZF1IzF#T}#561B%(Fe@+uPc7Jxn0V*9=+W-5H zY0@yTQVD4^>7CqPKy#`rklnnGwh;QCLk3=m#rRWGu=kboPy)Mq1(|RkLnZ?(Yb_bgCTE{uA4=T1x#236d*{Ez2=6j)$p(R@fbi z)V-y&=wW4=>z%eK!PZCM{<+q&wA}!A+ldtUP_X)~q;+al{FqaA^R&Q>)Lzd?q5y^y zI*QLy7JJju$cT}KjP6$6#mMVx7xbK>_Hx3bOX<5zk3aRkDK>MzH2PTjvZc@y3C;+O z?kZ2suKc27;=dWU{pyZn9X&K}9=6{@X@<+@EOQ}X&=<6l$l_0lZV*d0u(;;OPP}0( zduKqJ_5=e2eqo&})g1AGoHEW1Hc>bC2m`jr!o(4eT4pVnz(8c}K*+ zPl$+m7k_*HEGanvvr!2jUcK`2@oA!?qjRzf){lD5&K?g4mA99go9P%BT&z;H1Tmb5 zk>^i;x$MXE^(lS{4UN6I@j&!U0$`Hjr%xx3Z7_ngxid#1Bj+Gb( zx>L~Ky7_LTGOlshJ-j;XWmfpo|E+PvEjmi)^H#I1Cx7qkf|6PL!Q_eE`$g8+khG#p z)xF2kUhSD^Ow0=$b6BXg%Fd452EG(r>>neO}* z(|wdI6%GNeY>KzjOS@f}3toI+SL(rv_7^LkgG?+j(F~@6)1z-Aa1vj#udo z=GZ7PCZ?wQ8ao}=%TX^C^lS^v7_w6`D2NwiS4>vuqzLFs#OY2o#h5ee08Z~!!m@kT zUfQW=rPSadNwMMc!y;e(_HovQwsSkhPtpb7J;#DS1V~_N&q}+!qC~0Z2!(pV^dv7Y zzbL|7AnlMgH$keYh|d~?zTOGEr+BrGN@Ht@AWtOkFCwIKc0|)C5v;mo?K5CF>+0ib zh5+-;3&NvUm^JD-%F&2AmjD9cAm_J<0_wEglRv{gnnS|7th+tAIUms3c*YCxC)zya zx`YM7+dRtBs15>r%t&tojT1%4$mt;@i<$0T_t%^2fXiK@p7W%}8x0MjffzX5NFfG= zpnVJ4T+>B$Y42KZY_V-T0AscTTGnQus9!@Q;Mh-jdxCJVqfk?8WveE8I6DBHfrkj* zww?v}xNK-tzLm9p35sF!=a-a$-bEPi+wL8TOS632y!e|CVZ!27)0Lk&HHrkWv1YeX zz1(Xs@bxtOvOk+f-CXSepKybQLZ*j)`I4fo!(U1;)Gn{qP+GL8yDdO8$n{%f1!ox&$v|OF$?UDS8q)G- z!`wh(jN>m;O)$j#H*yE2@Ii5EZYY0=nE&kCz!|ZR)SYf&8U7HVPuW)UU4>n2fXC6d z`1s@uz?6s$7<|OoJi3-UG|F|FoQ#a!AXKL?YBxAB+{(%-;EWR^WD0^3yh*YrPvc0k zSpIOL#L=J3XT1g#yiHcHW#BixgC&b<2(`W|Cvr*wK+(9<&J%lqq{F z1#f#23upwbE9L!azVe50?q#uAn4ABROs@S6cx5rMvf3o45eSV;z2$VhVfB~YU5ZQX zOrd8I*XXlWq<{6Wd>>@5zhp1ZZ1Qz&Hp#VbJ9aoe*qXR!cX*MbUw|zdaNPaoFC@cC z1&h;&QMz$)%89J|KA( zZ)8>YPnmUi(YT?a^w%(U(o%Pvit7uJ(3<`oTbAsCnl~@8Noi?M<&@q?SM0I;2v=zPt||iE{Le<`8@(FCkTu+CuP%9u z51Q$}FGL*(wub(q^g~azD1?{(S_FZ0M^7!J21i=Z=K#M3=tywiEAKV~R4P+{5Vp&M zHY5R;K(@IK>=hIj$MQ~5LE#Gyiq}~d#C8k@z$p(g3%^`-(-Kv6)zqk`^&GO6S0g3p@MLoJ$m2X*u5)?((cLvG~7>Nz1Rp_tTJLj%CkTA9Kt z2D|6!^DoS1n-*uW-#VWB@il@rHm(?QzuyyLXn8lO*&o1Hs`PM%%zU(&cYXKkr0vAF zr$b6TyR1PE^?LHt^?-GDMUM3S%@l;5gVylVIK2NdhNKR>=?cL$*7+O;`g6TU|1qS>;Y0rW00 z1UEo92xJN3XxMtz`^Nvi)BSV}D2(#MNV(d0;quJ@09?NsA+oj-$`OxnLq3Ts^^$D@ z3}^cUny*!oBz&80VdxT^r%RQ?;&WfmPq<#xyX@-#C74eZ%01NV=4|_<4T~f3Xc_Q4 z2X3()gI^O$PIwU@qkl!1{0Y16?(WdH_WCd}x5;Vy&2=Y-56wG6@9OV><8p=9^(Vp^ zV{J!-klOIk{#Cs0M=t+6aa%y%KgqFv@as@h#j8g2xPuT;SD%zedJmw@#1GIhd%== zqYRHp)J=_C9ssbiUug3?Z6G>cO+E)_79M@j+Bcxvi?aG7KpXM($2Z@fb2OlJ39z#F z$%tE~05hzOq1e@;P_)MMoOBt#w6s^v1f)RSpI<*GEYY|jd#o?;)Am5w#+|==bG*RR ztBDx&HGM3yHtN{JnrOa;quYS?lBZFE==qT0TJ<_de-VabRb-*z zQ>je**-MG1MA+s+jZPf%ca*2Y+l1 zzovH1nahpxGZizxF81~R3Ej9`U)eOf|4QP7dizAkYUXVF>5qh04B6=YI9t`Lyj4C!Nn9O(6meubSJOLR?>PCy zrx}|x%C1c)%{p=JATV_6s6No@V@dKsLkrIM;_IxJ+6JFj+eCQ7R3+~4u6N!we{l4A z0CG!hiI2xC^cFREu|A0pZFEbgC){=P{);b>_MeG~Ehw+33avk>{LtF)gp@Pz;tpUH zEs6tB8Ou$?a(-vvnCM`F+_66w1Jbp3aSyqe$G!ZMsNI(RpP~G^8XC5N9~RR_J|3UC z%+DpBc+ILx+P*ZG{MdA+M9DrkF>xDNdaLdQi}I^%k5mJ4dWQyLdJD{_>EE)ccvBTrnN2VH%hIDqn9}4!E7ikhwZ;UDHRA)r84Q6aKdQRmWwLx$fOV`ji)MaPVAe_!~Z}zO2^l zkH8Yqt1T&%=}x6|WbiLx#NUfDf~3NcZ!i#T6G<;AHD!*V+U&w_wNS}tFLq0Qy?R$T z{DyETj{iO7`vAAA<)n4oQ~}XTz2`@4E2xxXVN_y@2h9&Y{f|&E(eTv7+c?Yc2Lux1 zffRHOV8mOJ0Yi`FDXLM1(G8=_048P`C@!BAr7b9)ORELf>L?ID@|?-&S>!6P`QL{* zlc+sFSIzU*a?^77D2o67Q>ANC*95{7)IyK`C=5}XfoTO=t|yIrZMALFp~QzjMGeiQS?+2@W}yW-q- z+-tKS$@6>3hi0d7>E5A{bIps7lXm^3WA*!}{OMvm^l}?)Nrf9133Jb!Jn{BMy%>6{ z9#^gv^PST3F%1%b*R8busXTEwGEC}O&{*YFvd+Wmo4v<0`}3*KU5CC;NYQ-71aT*M zyo7Wgv!Zz{E_F+hPx^<`SZ8tZAo%?LozqCB@~-cd_taBH1n%U-?*EDimg1^C=kp`} zK#QqMIpi)&vTp_%?n5UE~&J)`A zVWOkpCCL*E1p}8D_Ot+!Rt$~^vKYywXRvVH;aQRn6eOpe%!T%6&H&7hYGb*V5f0@< z=5Z-<6Z5*V4@FBD9(sm_dA2n}gNp_pdR=LF8-Q!k&RN`yQ2sB9 zw&_$*gL8m>(K9C$OHWJkgR(jb*4JgEYe4IjS)k38rs%-VW6hs!S7?)6rsWD=$H<}6 z#=;2tlQq=1Jw)5{Ws}y(0pPiRd$PvRETOTpyW1PLtpz=UOB-lcDqgh zDH@!T-Fx*^SU=uQKRe)@-!n=kBaQDynXf=ibdA*Vux zGxD#!2^%5Pw)&GUcq*=ILLp2f&zO;rM+PgKFG#gCKWPQgHo|LT8QnJS_`vb>sw1v&(*n?j7Qq+~u1k%A&%l7SM6&&H1vK^}z2^OpxBA)MZ8V@e!xxoIAX zWk_p#e=1ev9}tbjk3Mjg%Xzj_xO-6%82bA8;Ym^HxDVLLiAbk(za?h<2(|0;UB)sj z<6}`nBkuYIco+TF`l!$XTEcRQ7V`4g?{$U5)Pe3e`Qmrp z$6TKR+hje@u5OUD1V!3v9pm|zk5~gL3qxB zETvTyZ!69m?X?DQ-Cs=!0T9ofe}##{m=w~0W4G)2rm~>!1tqB+%Ul-KADxh9J2eJ` zb_j5n|{K})GGn*i4Q!c9{D zS*ir|*qt?Td{rpyc3U10>-b@KE&JR#y<0@m&BMJzI+QVz5~-p*!zhzXt(B{ar1_vD zp*F`1a@~AfH8ZxrB~vY3 zy*QfTp`nba76wn^f8TY|&E?-CE-{Ape=zS~{BY+a_nVHfv#-oYL0B=_DB#@xs}5g# zY4lj1u!^JnCvNE*?I^nOy?1YSnX{PvWb~dhmBu}aJLd+(&=nV7AzJ@<0gOpkz$L4F z4ze@}R!z;Fe7+{j=XWiAczpjT)DMJ%m0xA3QA!{R#8?Xk4Ge`x_*P4eed`Dol!2q} zaT_qyencj8`$GtPBL&siI}Vibz*dtab=`D6HeHf-lk~#IN5CZBUyy& zvl*|$+m;x(7+q?=Be6y5N#A{rwL_B^@NAXFHrCopN=r2R3mB^~rY$7q7`%qIeRMm~j$Jjo^( z*Pk8M0XF(XkP2sy3KviXq4SH2sG>P_CfzduAN%BB+d0ExymL1)F6uuUv!#pb^xMhZ zV4{;#Cw!xY5=`ytO1&K1@dzK2#&v&>Wub5ykPU)8c_oUxJ7f7sM)wjoveq-G9y04} zWBT1F*^-jl@+jaMbyZ+J*8xWTt!m@h?#i~VQadN2NBBo|ise4?aih0~=L^xu-DFQr zj2aa?r6a7IcJa1|DSppKrfD*8Fldye#=+;w&172hZC3mHG zdv?x6l+8P2QF@wJfIHWjI@O1ZWTLF6*TN|r>6yuAdUqPr!4m7pJhuVZIOD;a1sZ{o zP-BzluP~ixM6Ysv)<)J5o*EPpcmxeV1jM^)r$<(uZy&1zhE{BzLnqGsk!lIMnvG z{{Z*trTlbS4QWz*w|6Mg0V8h9f@ z!WnUWzzKy~iC>Isd@ddHM;5%2%2OSe-LYBYmw)s+`BE+*bN=6g+bud2{FP%oX|Jp1 z4|B(*qs_4ettJQ}NfJ<~L%0$bnG!HhD&ppVHoiyNqHJ86CmN;^ri8!G3!h?y&z@dt zc&6pB{K1>7-!BVWC_xbg`G(FXM)qxoLyaSk_B^hFBO_f(!v8p7J|dR>4?Tnc9r08B z&KIgoH@xe%%D4XNlc1E#I9Eudd|Q%4-ufi}XL>w2*_4!qRXp3;0nEc;eDcv*#A5l3ye*KM8ZQRYAeMmSoSVr zCuhM4cc2+@{U+l+60o_$2zztR{BY{kCts zuymJnH_|BGA<`+`2q+!W-2&3xB`FQk-7O&9-Q7!bzWaOsZ=5;D*>QGe*`04c&vReb z^|=+5ko)A~SS=B7Jk0l{P^IHw9>M@f>KC%?6qJL#HEeO-N&>>XjbDuWE!y4&bnR@F zf!Mru&ms5MTbJ?an~mquzi0a76-uL zYtX7Xvkc|ab&3zF57jjneylrZRCCS2jBb6ex=H)V7qdQ4vVgYFN{c~o&`;KiBFM7$ zf?abdZ&kS{y|bd_`R0>D7Wo+0d-bcoXH_nj^w;($LtD(FK$Qb^Eivk^c$Gltv&=H&iSB(T=6J*M zEBJ-v6OmUEoyzNXx3wuweTQ4v6?gLd&8OpKC9JZjEcY^4S;vkNJ;=5>QQ}nVLd+6L zyA9;q{avuuFEJnR{o3fTP9> zBYKxSwSnA|$hys-t4jLSXq9aDg8Jx4aQ)0dNU@faY@nwqGHDjb49i^vyv&E7%&vhq z$e9ra>{)isjOY2^uXVFc!i|WfY?ueju(!>jfTP!RyX@D%ZUz`@RLdh6{rO5sCySZQX3s&9fObcEXU6o zt>`|-W{o0ISi3u`)2SkRL?(m2nhjT;PVsjw<9yjhb@A! zR9G-IAr)f+rourDvhcn2-?TwdX*e^N?`) zaNbk=D2F@Vl;|_h45QU9b}(2l&^XpY2>r=hgkbIHyZu_j$f@&3e%O+$^K+HE%DKKM zOb+|;5B*o)0`KqI_i!Sn5)7^)%K`_aNR+1D7`igIa_F<$QYh-_WzduczuU=xdUHya z2w8#@quEX58Q^>bB=UU<5TNAI^_mEw^CI5-&60_%eCau}j6Bi@`TFX@59V<&yG1-F z0hHcBo8MmNKj+5$QlE~vYvkoh9ndA62m&ss#woQ7s>;@dH)~yv-KnyYcYIhf?P46* zSuYX!0b(ND$oUi2=}T9m^w~?-vk}R0!<$((pY^OzytysOq#XzEBQs-SZSWv(R&vqB zmXTmkS5vlKpig@=04;Ra)=F@c5dO>Go5iYy{BvE7^f}NS9vpZfRPhMc zkVV%h>1Ik4)Kk|d^3L{regtW6_Ng-DzTVyZ&ZEh&itO(txE`AjuT^-kX9T%lf-q}! zBz*bfcd0^*eknb{z zr_r2URkN2Uh1E_vn!cy-SE4C9IK23GO;=aQg%_=X#;1D+RcM7>)dNTm>4qi?p)mNP z_yax))n}{(^g8XK0FsHb-M6;9sIHkTGV?1QZ?uuev8OlI%;mIA_)Fyd2f4uOE~hNb zi8XL_FiJntBgq#W7F35;)U^gD9kw&q^)x_PUc?CSG9|9N0}O2{h)&H=O?R3=lfh>!5ZfT)iqg7PKB|C_vP{5{8`l_pt2U3jgotlu#mI z?8x%i(E@m7{w_G|INdHqpF#h9=;SQO%q{C=jGodIa5cCemY77{Q$RY9B(JYn!7 z095mTIUOJs!*WQ1Wi~k^@&Bkt{|p%W=&(*m0=lWA^*Xudn`X`PhP?qlS8^-LV1Sj> z8N~DeHqgx?{GZ2nClbX(t$3sCN$eVY*Jsn1W9nxm(dtnEo&^{z050JRBF!~qcmBZY zq$4P?$lA4E3i4nh;+%hlS`s)5y~XT&24sIG+!%~I%p6AF8A*?jaNMQHP)%GFcq7_or)T$L5i&6ya;s?KRKW^jC`CnkshfX*3YrL+g zv>RV+7Cem_0D_*3<+rp5azX7cQK>A{R|C?j;@^I&b;01D27(RaHADzv|4A#^zX4n| z(V);S;0U#ah)PHVPS4CB)r;+sGJ1Vs6>dKw4`2P&Q{Uchpv+cjQnk8F@4qSy1r|Jg zHU|rJPx$;*jIL7v;vUif$_zNi+GH;*yQ_n7drnk{ByqU9NyFKOo}wCdhICwYGpf3p zCWHfh{UaqO;bt!-d)t?UbHnbR*pfyQtXq7k&y-T;BtqHBc^PidZUDmtUG7~Fj*1wB zS~f9&=;WJ{eAg=i&Ya`v## zAIZK}*{<*}w0Sm+^6tEmQIdOajJRPQjkr{2m0TBA+s|$n5~;w<2s=)`)EACF0!S{~ zX;u}nK|!!qN9@5LwW=aLh)lIA1K}A&YM$Y@KCq-@31xMblrwF=xPu25NttLl=p@BC z!aUY!xH2^(dWToL+b-8j!Sm0K8`mQ=TpZ7clG9s zjoBrKBeYyxl9r{1I~P2!6o9780oBN}yjTjzzXp2znt#sL;-1R7-nG7(zXjy`ulTR} z+$Rf|QRFx1M?%c*5b!&F6cpDveH5)D1XB1NwlxsV{|N#MA}?vOn4&xCh1h^&9mj~{dG!*bX!3xK$>0lUXLBmV4+jAeOVaE;Jt^iqo?-$k0p zr31cA)fk)`DuLGy(}sU7fM{{YwtYnO$4yTjP9q(m<>S^r#0bW(==rrJ`~y(1`nZ;M z5;pdRj>EnV)vxGo`v)5)_8qat$@C~DU7OBS z-+@~0TcxjK{Er2h1>#!6OZHEMJh4zg`w1;E$Awq(-R>c6wYAgo);zY$#X5;UHJ@hss^1<(AXoYFBC)QX85f&IGAZie9H#}ux!Okmsvj~xyF2JV_raj4AO z93xD@eqq>aNF{aE=h4PL2d(Q7FgnFTocAuM{?Mczqv^pD?5?MJ+SQ+fmpzWk=`utf zkAFk>=#VIf+2S)K%Jc1@AQ^xdeP?L~yvxsW0Qis|K+h$^1$@_lFCi(#fyHL7xB-Au zJYpLF*AgcMyXRO$YmcEwk_~&`8P?1JSs0BnA1U?6T`z&9^y>z<_YC(;|`1#>9k! zx_vSS&4>C%G$vcny{1C0+K|laT+FHx(0*`hJyAhMooL;HhvqP_ZKoyIi(|(?9n0U7L<@fxg6n;?cE{~6w zx6O0g+s5zn0gnytF8*LEt>eyQs^;(}+F2UiYxhC&2sGE$xEjmTOW@;naKZTCHszG* zb!RnR7ACUQOAq~ILxs>dprH!@*3gw_z%~cBoiM18pJTC7E(>FCWDFi^lqb#fN+G8)@ zegU!6eY(s0lS8_WAVvoBNS$@3~YPT^X^735!E!Xdag z)al$dN})3Iiq=b6G0%R{r84c`+kiEvxy-P`;7U-UqW!@#eezo$uI3;h4cMHcJDzhO z+5>#Qp}UK%a-r94dXd;thlA_8&)DE6NOfgwNaEpM)7MEbGyy zZa05W-M=n)V%#nyW-gelg8%L{q+Jy)&3&Akn` zKdmukIwnOV5jLKXPj0$}yomU@o%fIiB%; zy!C8!`S)G6ncA(EcD2PX!RBL;4@6Ez)9dUOwo_s%H@x^0hQfOKTln&g^UN!>xC8mJ zQa{-$1TBA(PyIEc`zrH6E1{`o$-%?zgL%;1wPWLTOhW0-^Wg!HtfHlXXU$X@hw)OZ z!lgfw=I}Acm5rxAHzI}L#e?0p>5|Mv@S}BSYhy92c871>Z3|l+-%%-XNq$YeIJQYV z4V!mt&wM(dcO zb;C{No{quDLV8;qAR*=xw)VT9O*GxO_|%5@rOtNO`+s+RP;w*PQihR#w&&({eSqtQ z3le0$M#Mb9gswuTLLC{71J3{SvWrdNi$9c@R+~qiDD1MsK<1Z&Qe{mK)LOVXuf~I) zuP2YG{3D9pA~fuxz%((LLC*h!V6Oo4QN^+mjYmS$ptLVFey`y2jn|9N;f}0j86T)E~Z|}AfsfW-&+S`pMf<9qdT1l7Vd1C{p zL3*NP_7S*AaS13`y_~%W+F_Bv04N_#H#3@#5vt+WDhrKY+nBR_61^Tu*yJQW1)3J^ z4yNlS4N=RzN(2yjGj>uJ)ZT`gN-D-RnqT{Z5%OWMqIs0+Z+!;_f;%dn!pm0LWGf;% zG|pHu-*L{1a7%>V1`Y_Jv-$1r(pmBP9SH?A{B3Bj)(CdZkgG7!o>MM*UcxC8%PDNv zQQcJCStR6<`(|CzeDYN~U*{Dg;W)7-Z7a@&{S%%-tGqDpS@tM@hc3&j-I1-*=$^Ay=W!^7l$i)z3wo7?WbXqqq z{1}1kw}6MR2}MPW>jkU!jh!|VWj^Yq8FK@%I3`yN>H<@*0MXmgIB}r*ti45Xwp`b0 z6OQ!>!AZ0Qf!s^E9o%*%BPQlgIslxkX+*a^TP>~PNREyEqE76eq&rwi4vpn`f}cru zM*3iI_U;4D7?kEipf51bR}%RW6ubs%;{YR+1i$yTqy{(Tlgg>RFl+`4XfzY%=q_gi z-Q9dr_`2(%tPzNZ!aeV;{O1 zN{_i6?Js8TP6y*OjU3_=Go)k`bv9;k+1xts>MHp_K?3lhOy!KRLXrYB0(?rUIFky+ zJQgD0-TE&wp)#s+?)lp7BPGMNYfaMXCrG4s6DL2$6?$ zYu0Btp%?m+OSEhz`P>vAifP-ri7;xWz7^Cyq;TQU^cPB(|8V1^Fly!>H4)+SOIr^m#Ok%;YRN7x%>5IMJTZs>SbM_bK_lP!-*~d~ z0FQI;4^WBU)Q&T0F-CfWJ{-*Ng0Hlce%L1z@~nCKOX z;MLk?#p1NI!WWAGt98px&-E}O5knz#-4HCtTG-SAJkSN0Ls`*SWsHik85ju63|N5t z5m)tTFK2&xXCwC7>kC)_R%n=B&`7UyJ(i&e61!{~?Lm(2uTJGJ5*(%@nqr3{9p)@M zu4;`Yd_OzPS#R7aa9u9N=L9Xjws)m_M>!Eo2P*_$9Aqxld56o}F45VLY?BP192nm@ zjrHCsr}^#WZAC#GgZAa};=l|YYaC`KLv;CFK`mF%zMoweV`N~y;$wAD|IT1->~9(a zuqO=j0m2XP&|;h2NO6P%HYpZ0g50f)*)vj;%kH1JpN`$pLuoXnx6H{J7OLP|*|()E z92e33enU;~x63o-~gnwAo)&-Cr_A4&FGkX=l}!$~|um%>n-lx*Ok2 zH9woBTao4c{Yrx#e*pK}bY<0bTdNO?Fp*CKootP5tA?($-Y!0Q?pd7}Pn?$NtrRip zVIAd9Q7S>|InTkPHRq^?JFt0C}GVRko(n^bzg}SjHtN?dzQ*V$Kj46XhwG z_?0Z#w*1-bB*n+Q0T{ElH(pV1#mh&<((fEA0K|Lo#`w1X88vyRb5XRN)I3V+k+}x6rLFL_e z*m6__d-qitNBaBGeOY=z<6-gfCln9W4@t;dA_rW*%-a4GCAnp5uX2og&J6WjUL1gj zr(@qMxNQmh-H<}({W0V@!dq}QO;@CMl~zn3RrME??Kh21-o8__g1YO>ADQVB(k2F3 znz=0^o}>$RFRyb%E=Q>Ru2Z2{{n#MYamSa3zsO(c_qd*cQ7Z5xY%yuEjTg~03f~nz z{cF1-^ImL0qfuL`N~Ca8=o9igDXWeLX;Lq{pYBjbFYdkRTpsN-CHPRi{Fey$9-*EQ zK4*nhTLJ?J9`FZYM?_Tv6M2eNZ088#?&JphA z&&DVH`?NGNLS=ryq@)Sy8c28R)zpdz!2fbg5!mvJ9UHHrk)GthZFx-{Eyy^M%*Lbi zCLt3N#jVfFprT-XDD5$U({Sa?$sHM)IUEY8U`NE92P!Ms3In013=9n&9{ZF_(VQGa zLcL(}*E^}-^Le8y4g;idJYJ2Y(bpZ<1#G|QM@3A}R@-h#1Gl_)m<*Eu8|zGSL+4@C zRgE=K4do3&(v5{>-AlfgSeDO`%!2(LnuQfAo}qp1*Z>)8V&p)<#x~NBJi*)Sj9acI z%U!fDNCRyLZRF=d+>DCBcY3MI{5_`&=T-JNr@%bXTil(b=jsE{4Mbt$TrB7a~{ zQ6u@~!Xv^3*CHE+%;DSAWu3^2z1$RO$Vtj0DfNVjn#U+SGCGdo@G@PBf-p@?BdFLof)i-hM{@w=*-%x z6;jvM+Pci_NU)B0K!J8F(T56IS4L=sBmjc$5MR#Y!L-%qf z%3hy~+V;xeg>AO(W=h4aGv1E$NEni<*B-B}XHJ1-_fv6iW~}Vio<2I20tH(b-g?ieI|Q#FyzDc z#B>5P-7a06SQ4$-kP|`2_;@ZyIhqSvH*77QdEyV$9|@C0N5kQF2vrtc<~Vy>TaD{V zdA+JU2@E2xH`rOtIZWHbfCnV_96cVbzb%yeg@nI-&|g##MR{7VH`4?YHEHh(%9;Hl<0BSS2ql6pxV$h#68wY4<9 zrmuHO?VtMrY6o!#$qWijTZ_tV1Yhq!OoB%@MGm&ZiH z-T+#~wFiIWPY)tYhE&VPf<+D6%$Kjq9m!f2quf?bdS1pb)~!gJ!|eR&+xljX*=dK0!t+Ay z=If{_b!&SyuxW_BZwO!>F1BkQ8``jHeI0v$BT(71jz`(T36ja4cD>mTTK)*p2=>yU zvibJX8K!`PCj(V+mD$pT`iE&p9Ss&l7reL>%fI@m*@rdp%->#Uv}b~T-AQlYng5#2 z-Cp0=GjF)*Nq7bMu1nItiS}PAG&FW&3}Viv1hg~d^Crxej{7)T#F~E-g};~rh2K|g zrOLfyl2gw##;yMf!=h|BIWdQ3o$^`#2%JWpSUG;=_995{f$-aZG>m`0fd~_?26W5+ zEB*ibGngnSxF^3ynH0+Z>LfsQ?_V!BHeiwnez$@5|MMAvJ!JmBtkjMA->v$$TpiQt ziDXaI^sjIFQ%XZkDFASJ8DNP?Y@myWsqMG1!gm-|NWGm0mKQt~vsb7TJ>O-ED&C&%w>kKC{5e?1%oB(SSnLj}8^7L^eO><0AWI+X#D zNkq*@#sJ}ezg&rFPaTACx&2N>iE@o;Ih6rWpgfV3J3Eq*8X!w4W6&%LnfFHBTI$%s zH_KmhB*mX+=NR~PUE#&E9@3!4r9c$x_>wQ*7V&pF6WiK(@qIUdXDIliD>!(`Rh4s< z(1Y_zm{_kv$Waai6APSO(I@p3paYM;0p)qt51bEz0j!kA<7zU$8{3`+)L}jEIf07^ zUJyC~E|7TID-WBjK%QgUq&>P4`mQ~pdQE!jZ2+>id- z!tlTq0o2vURnFyG)-6}rk-oDG^sz4B0%x}e$|x8aYXN)^<~bawA^@Eu0{H<)Nsw}8 zW`+({TtY%&l&4hGTy4W^h&K73Mjb%g&E(zPFF2E!m=*7$YnKOVN)o+}9TTY=)huC` zcE-ZJS4~2m z^|9lXNh+Eo);?QlqA|F??(L_-K}hyBPu<9K1UE`jv?rM?zcoJntr)UqaO+wov#jr8dZRO5^V;W9^YJ?V;rqM&Uv=F3QT@`JYOl;s`s3$ zjszOl7#U&iJ3^m%D`%jAgg=aZ$Ah$t;su>9`0oHr?rq2H#|+WNv!w>5_M39MAz)$T zu3cs1kMu=rjp7YvtA?BAh8Pj6P_l56Z3mKfPr6c_n0O+^Z#_!PU_&1p?-0EHZ3(NW zTJox5sclcPM^w)0cfr<5)E(qm3lrkEhcxk++TGK+B)&TJe$#(TSV-vmzo6uDP~+Kv z%YFf2$=EDJ(hksy-g!Sb*{S*a@j7e;w zjYK78!R=rntNj20p<#5rlhTrsvIvis4$<12jLpC^p!0cLMuxRBrsnO0enEb|)=1O% zFuCG~zmxvyh6$Z5p0lz;VN|$V#;P;+;^OMHA>YkswAbiu%57B1)fgMb9`I8IUZ}KN zu1MXF2JK9bULxPw9fOAChfnRlh7kKGNJK^qGRI5@Ihmx#CeY$s(#hp2tsPi;n1)3oJpRy2{zcB83@e?;U_o5uwT3Ph^aXJ!R4jwXE^J;?Z9qgut z1z|xSxP&nm5$&s``g{Os`|{~(ytLpfM%Yo@^yTUP+w;wwoT_RZiI9gHB;X1W$;03U zlWkisKK@?cx~~U9jC?M~DFtw@-i_To&(ERFph|$=LIls+a{R98cG2oj$!QSm7)H3j z!=4=g2pkLc0@_w;zq*Ec!ecioBj)He_OqXH<)Qn1`{B2Bh6cq=hr(Gp0jYSMop_T^W&<04 zK;zK#>uo^bl@K!3QOX&I71XIas;eVTTrww(*p+9KZWVDr-Q?otlKt}Wdt1V|)A9n< z$&jbc@4t4G$}bP^MV{dU=;{2gL_77Cmxw`>@aGPAJYH*nx8VuMi^qrI&_l?2X5&na zC*2_5qw<^;)}r7SN3N%AiXNk3q9)a}7cV zJXNW9-SY(++@5t9D!UlEZNO>QStc%fUHys6y$L53a50BjiH+Lp@(bxjz2%3V=Fz=w zFSX#O`c#8I%z`3Jg*&rzDI({~oQT%6tc+oN+BnnLViq3qF` zE#dS7^<-GMY|KcJ0re8PO_BtE=n8sW#2nY0>_@L8n~=$hGN!q%le80{sa@6lfFwiw2XH+W;^vf>KWuQ z;RA-0Lk>eOqou?K(_&YXb-BhJYI7Px`^VMqSj~Ut_ZAFSU*^@^Pw=Pe+E+)6WHJB| z*?0m&YXlQ;*71BN8|0#p%~66AT%KnSVAj*rXYz2>j7EUp#Kk3jkI!3{tpr^;^owcw zmvA}eh`H;4%d=f>`APmQ(_!|kTe8SV%v<-@8?dI&VywLwC@@V_6BkR+%7|;N$uUF^ z2qu-zmu#+0Jh61<&~ zB7h7p7)BvD5slv^G*$Is(eQC`uFTF3fRx;Xg&4W-caX=~SsEHuGF6j~0#d*Sq#VIc zYPd*k%nSQ?->W}73dImKBX{^cARprs`?2!y%pD@xXNM#{9~5QRyHyqUNWdTDtomQJ zc-&Z8p)vqofTFC2@MX_4P2fpUqo>TS3Q$Q+&=B;6sJ}DV8ff6dUUN^|Ds3G0SFoPY zr_%x@royNU@k}hckR;m(6bWhn9sW-ZWe-$&uUoNVS_*#^o7)lGextJ-1Odla3~gz zIDZ$Ma&}6KlpB@IxY@N**V4Erj)k$in-u@8tXakKP)EK=V${Tb0uT3C@%O;McNtar zMw_##f#lu1+$M!##`NjBgZv=#{)rS!Qs`v5>#l$lkz}Sl4jOx7oc$6KiO!xGZYHY~ z5`H!Fz~v3?;5|&nS2wyY7BJA$u$H#WB{Ujiv$02!m9M(SCgapw^KlRu1I>!Eb6oV%q5onh3 zx2hX)Na5q4er$5T`ckS1aT{_c3;^B!QW8nmR8H6R1K&l}AgZj*&znYC&PT6bNu~?{ zpzRnaMDs{xRqRi+T>MH=31kT(ERUI9*KsX7!>Rni8ST=t(+aC>tFNpw#(WQC6J3XC zQA68zgxo?TllJ3Q*8+Ct6>|@!KY}E`8z+aI<`smPy8cB!x>}vkIy*D0e6*h6D@WLL zL*<-Xp3OHXbEdL<<2vr!*l2Rz{KeV#jD+GT{%5?CXq(L8DS5v0ppjKX=xyKN`83u$ zW-iSiOh0PFJE8B74n;=FuTEnmaoE(YUd?|^{5qXIHh3$EE47`L%99%2@kL^M>7H2+ z62@Qyc{8p%#L_zl$+c3f$W01`^22mWpeRY>&uIuq@nanEqV#$cxA%lAa>`ZzxW|&& zz$UH0{g!X%`$T_O`Zbx0wb)!lC-+J`+dOWxk=@fsbEaXi>+&OEHg3UH$Tvmv*UIh= z8hcyV^JB4{6`{bY4?AQ(fnIxjAP%Soed|Cbfm;!vVdJg_Bv&^N-xsiS3K*xXv*i#X zqgdDZzPDeTy6^c`G_@76t=w1RTCBI)qTiiH2?*g<#CERJ`#lPYkB}5PO61`QLz)hj zT^uVL=c0WG>!L6=&gQYp3Uf{1Ncf8Cgy0|jc0>90?@EmTj?^Jyw=HEcI*==_)sfC>JZG!Wo=FswA6k3_bOzP8z zHY-{VRvF}QjKSL;Vp=|a^%~V0+FhfJr>&Kap&7>F4PlH6A-b=v^5famBMohZfwPiw zUQR!)*_NqH9O(ul{{A)_+D*X;DC=lE2ckkGGu~LqXwBjqQpKF#*P`4=0%s`SOW_B5KZnCSzpJ+v99@pE!vY(9HrF<`r_* zV4n!tNYd)b6W;3Ww?QuytCl{+)_k=2Wu3S`h)I~VJ@q2Rc&;uq$6Y^sQ9}2t%qBLz zIV+!v7!8DF*=1*L8IR+^%t%S^ZnCtCP&?oH5 zcpn>m0JD~Yngl}*8%tq&V5pQ|-#F85HCmv9JIt+X6wYU~(A?QQa6z(RoC;i?{HV!? z2Y~NBpckS~R3W}U+eG7^`w_|vN5g5aDS&o)hjNxfqW7m{xV1|t0FWN>2CsbmNxXx( zg8?JGyR&l;detzJ7cFeLrO+7&|Ge}!ne5X**U#4>C7((7uAYp`{aKJ*dQq+6GY|^r zoVLI8SVe$FTw^LUEXr$}8(v#|4*Ve7dMw;3`Ix{X^ZAb5cbK2bJGgl)dX$KoT<2Vd z@YhdrXfQ|C9Q(ad{ykyd7df9@>9=#jCj@4~e5`$XCo9r7CNn>?@?plh)P;rl#R6{H zK&5<)J^M>Oe?~E6-JO5Upu{_`^W}%x4<#^_XAACz4){4&$6nNXk2S(s#t^-oSu_-( zq!R-dSPH#c-pw7CC;9?&DfvKEZs${cTbeC$`k55XH$waF)7F%Yy}+~>*4-)xD&XIQ z`R+#)pJ{!)HzZ`QZ1D^bv(58~F50Jpq}KBq6Jg=;p(9+O z!>@pb!rBmWk_YY8DPf4kqLFjYx@HRbBHVSfDXIwDWP##r_!z22|s? zNw25Y6QwAD^tw2u-*=7#oCK^0IR6LGFjakCgi$ME1WhzQT-oWE9Q@83GkDu;hSHQ% zEbqO)2xMlsW+EaYYr}ZG!tn9EJ}iE5Iaj@T#%O4c1ZyC(IDc3Y|1I^t8m}p<%ZFWj z=l0v4k}(J0eTo5oINczZosHrT0W)7rKj?L!4RHPT_I65x zk?*$u-fz({k!gmwHf`Zm>fQ_5_;fEu{v3jD<|?a*IaRd z0Syy}Uimd;G0)(v30hpy?1MQcDCeD>RMtaavS{6{uZZ#@!`r`%FLnMN0G4WbE|fUNu}IYtJ(Joe!p5R1?o2&-tNAEE+Nlndudl>&91GT3R<)Tkl5sq+ zyY|75ikUvnzJ>dBzvC~s#ob#68IV3!C~CCo*|17nJip#!?BkJ5E@dw1^U8|D*vWEv zi&+l8uBO8ZhaOO)j1i>(cEP{cEvu$vrA|a8wD(Agt0`=kx32rKJ7C7FUE%i4%+WO& zJ?$r!L-w#aa}jr(Ipx(^^itnyu~+K5hGv@=DV{MbhRz3#sfnL>uR2z52(pNBVPcK= z`Ly6*NV{x+4gAlmO%m#P@WS|?Qv3_mgdJ#=?ORKWHN)yl>d~@=iPmbKH$g;&x};bB zjhUT=&8Lx;wPEyzTV&D<+s`fu`Sgr8&?wN%H^GP9 z7$`61Cyp5SKB(p)7d7lg204J+3|I~P&j$SKJ)m5RAi8x33dh4OG&MT+I+ir!|Lr+x z!|I-|?D_NTouc@@LH)7yOjw7>VlkN%W{RseBY}jJPgoU>3%;^P?*wy-7yVN!SLq;+ zL%FX}=70-+&V{q9R441qLDA+u{E4`k#W7n&{7E0$JHGd-ifUZ4+K&z!me0|4*&oN! zU8H_K{eM+9_HD%LUw+`D`A>nKys>1VjLTz6OJ^dy%G2Nu1!4q>pe?7ECKbjk4)yfk zb|&pN7d=0nCYnEzy4MbQt#hD}z?Y-Ux!n_Dsg^B;J3F~IHVhnPoLD$?9U9P17CO|- z)UED)OrE=<`50nucx%+codGkjO_%v&i->vRmCW`b&)G1u`LE{O{mL%o%a+p{&uvOX z?-Q;_Ve=)o*N9Zm|A_?(i_e_&gG`s+R(b|wvDLujAtYVlbB zh*teaSNy;2o1mNc3Tz+l6$Sml@{o@M3X>~OuKq9a>n(!pvEN}^9`7mKzq*672I^fM zY^_garKuY-ua1>=r=4Mc1UkC9D%!2yW&Tex^j~qa^==Aaw^xfkQA_3*W}FhO!Hi*c zolXVrdy~Q0p#6_n_@Aq8A|3WYzH^I;qI(0|mvy7|<$Ny=DjE=BD4=O47MQ*(1G4r1 zy>dhlmDjEjZn}KOfC6i0AF=cK8fPmAn_v|74wY!MgX{`;>iqkM@wxql@z3$OZ4HEg z``8!DZ>O!gT17`!_mK%jknO+6!wC}+38~tl-CS>9w3|%`h?A|sNJjt{Ap0GXQRg54 zZv9nRnY?b4$p(xx|Jjy@Fep?Mf^`AFrb@A0tw_11B+^eIzykL~@z|9DWTgEasWHpm z>#Wlb37_xvM@#)j-_0-tVIE_iA-*}=OFmInj0E{Gyf(LT1K*fJY)sy^Aa449U;BYU zfci*rpPQlp{0UgeZx#$5k9qr_vzZ|Rs%^~{Ny3+veRGaK%=YI9;2!6A5}o~9e*WiS zBBtO6@=<>E8WsHSe#eL!V*250E)CUBDZ(9D*kdAm6hcGR1oqGahyZ$3ZklcW=U4)7 z0#iF>{`%U`+rY=iP`vt?tRDGS4TvDSv*G$cR?IBGbFxh`2@)AY+u5 zg8@|j3JN4o=!m9~Cso{$Z^4GBf6aWgca)ocx4JX)&VT=P#`~<_vHDKXog7EBskbe< zW?p;#jwSoDefR^Q;B#_u$pD$$f@nu);pi6AV~Kssj_`q<3A(E1i7)OCf9TERHgw)r ziqPHsN%V-Wm(h~WFI&lGW~%RCof~V^X3g?y>XoExmFi8$?P$AbEX-Y875aw?L0*mN$I^jrRM4rd*=3{dqqEkaFRt`!Um;}!DB zVd`yqDjz?yQSNTD6f<-gGl4q#x?JJV8u^LzLcm5!=U-YHa0zwZv!vhv*U&290E*NR zR_6FsQIQ0!deY59lzo8W_%_f>Hx@`O;oPXGsDL=4*UPI~kfYRN^(2Sl=c1mWZ{2@7 zdpTcJL*I9MxVtwAzT9oS^LcU``tt`@3+b;Wb|lgu3Jj7S11z`QDzoqF<660%2b^Gq z?_7ocB@mez23prEbmm#Rjl^?IUT(X3+~P~IYJ3jy}J2o^T@?0j(gtKu6 z4(ZpLA9-H6dd)RREvj5E*PmLq|hsNoqOA3f0Z6C6!lc&6qgUZUj%=gci-5I_IY$pv;E!=ws`e| z-EM1cR|S(M-|fT7{E(?W@o=awFKm|u@&&YX?<%|n@PWe$14NUbL?0znAZ!K$YQG-> zy#k=+X_Zv)FJd9a!wPXjP0g>iw@VFn@$>V%slO_kP5L9MO@Qube?W*lt3Q4q4~>l_ z0UDC5jt+qOf;0y^XJuvOmJ?xq{Qc$6JFF_p8OAO@DQS$^o~Q`}N+R6|P`w<(UnpWDY#5mCNJM<}ly{XY*0lrQ@!2l(roJ=#0Z`Widj0#nPZsL}E>X_# z5*##F|7L!fDpYV0EIVNsx!M~~zP@%^0;I`G85NaSW&qX+bRaXGJ=Ttk%b+SKD7ekw zwO{w!-r3RSQ<9P4*uZ62!yi=Q$!Ep+=`r%6psL}h_E|zzy)0SAT)FW>(8pB73X75$ zEJBkVYWy|Y`&8kCHg7>eb|Q zObsl_&jl@YB0h~_;Bb0Yjkza1?KZ~(YoS&ZokblQE^dik3u>4kFXJI-_pfuvxVLpW z=;CJbv#^AjTpH+!WC^a|w!C|!nvX#T_l~C4W3VIS#6{ny%m0KEf&iCKNN4n*957n% z^Fi-TBBBttw}@58fF5!oc|V{#obEf%TawaFHp6zkcA&ph3WI3D^GU-RUZ^#y-_0;n zWeV*p#7CihY6ww_Ys|v+JPr!zwvC$}I}wB_!otE_P#+p0{l_oaUN70+ecK+KTE33u z)^pDGd9WzG7PIcuH3GWy9lx$#WhZllAVMc7d^H0bVAto4ig)Hwo=Ay2#L8z0E)Y#k z*xv%&UK!zwFr`78hLtZ=q^#iohpn#wi)!oMrv#LSOGqOrAl)q}AqXPfAky7EG>8(4 z0@5HNC`fk=2!eDoBXk;(Cc4E$~`0ZQg`$Kq-vxgR3uDf{CxZZ&&C-oK?SB2u#Ed-GTd zA3CK(*F{Z#*TeBTM^;75oM11f>fNcBuz@qH1hoq2mZ z8-Q^W2&GXm*#CMFD7ZA2*T1ONEPG695#8ip6OO6lz=?le|s4+01gbv;!EYN*c+9^U>wIdg zbH_lVb*QUWz20;j4FupFi3v9<*qB+ripTQVBT1F#J^$L&Yfy61YV>7WbLk=LT#|EY zdcnq{7fnbet{I-f6!vZITn@?40U1S}%@#?iOk(vH5mwgha@_gXx-Dr8UH~5;j}$4?fC`ObvaNc=|e6flP;o5NTQ30PP(1~Z%GHzKiOkMMoHGJbed9iWm zaPwd|k2P$OUm34RJ)VX~rJtbI?UJ_E(t;DUSi2}djmWB5KAf6bJ1l{~#19y6Q{A=k z`JQ}R(tPEE<_VLERKd!l#1Q~e@J?+)ZD5&B$le0|kzVa=k}SvLU@N(5n6+iAQ?fi)ui)oo zF*&7YqM<=ad&w!%Y3im(^wLwsRe(IQTLO77e0mto zV6;So|K8_D+(v`{oB;6O^NqD zvg(?;20!2iS5E~h=J7Tb%dmUTpKrw&tSv)8cg9FRt<3Yc@R;0Um*3;)1)|?+1Uu3X zd>r&D=|?`VTkge_NGq53*q?T6lj5q^^Ybb?PCPR=GjQmc{45aL403dCV(Z=xnHu8? zN`#GB>+q87vv$f7-UsD92geW4TL*k`DHhDn%}!-aA)=IeXx3IpQXdqAI{Prmy~sU& z%XYg}M(LDtFC`5kUmmj5)B8T_z%k!Iy^W*o`L+D>jnR8TEN;n+xf0VLsr59R7@2SY zJ#MZ5O?!>Fe9hTOfZlQHV3j>vFc?lB8Wz`eWxkb>kumrI?|{%F46~dW!}3jMn7**> zz&mMJLuXw_2$h$<5?jM@%Q-ywu_59m5r%SbgH2Ac42w$!@q;iY()K)4cZ3htN3BrO zoOqmzxOPCW)`u{TW2X41c4K}0y}ycn9dLF6qpsm=CW{xTH*N48H0n1?*Ir=5Z^xT> z0L6GS@YI!UKQZ7z{NM4sbBGrYhjY!*ynVJi?P2^0Uehf~_%c=*%T4kjHa#saX+@MB zAy(4}8z<)+Mk?tcDcn9-VP6i<85_-w0qoP;;G*+0dqJK36T6Walu0~1Gw;J`K& zuWL012~Cv*NN8=sWO9mxJ^-h1aNYFnu;+hzpOA##u+9Kx%tzr(+i1wJ9HKSKcehN2 zTYL!l`qqia?Kfh|(Ah>^&M!`*ZCiz&#}D%3hv>@_FlVGr=KnHkyQcIRc6-;D$1>x5+ebdFe<>-}G}@@OyQevkjMBywx;Akn|eVhY)^=ZQCCw zu3Y>l#zuYB^3g{3flX_Q3xmq59C9FEJESVW-`3S=|q+I=fVItr;+pIB$ym0@~g zhkw(~MWwBh0c$=;wm~kKw1R;ImYQlCV1hSfQrjorkeZ2y1d4!G9eq22MtcQ}G+P51 zvG7M2h*};J8tfHTrt=rU*K`(XG2OW70OOwM4x)=fYkK-GV1`xnhy9QPz?Chh7dspS z+I9gKpNpJJZ@se-`C;}UQWXi%-36@@c4$$_5Lvl*Wj0XdDis;d#7uF^# zkmkyCMxK6KX(ea)i+{K*Sn=!3{ju3nGXZb5fQFia@&GgU^kDzKjmPcQFD2)P{mT{R zi@J;YiM(9X%v5Tm^^NjxZL=nqV6U2qtvSx&LC)2SOj$Bd%hSHhKI+wEaAh(C3trC+ zXnTFt&MRJdQHO_v&e;!E<$U~bof^ROCX)TK#v&xaROIxj&*<gw^h$=AK_J-vh+A*r~NzOp{lh?!ISeA}UyEYxQ2&SAk`uD*K}sY1S?7aj(K4UbqxTC8;$0kSgD}A4IyY)B;5!MsP~!D zzCblqpXu79CO}HX>-Hkng-=1jl9|`;z6!nJ>B3(gnl;RO>B4qfsxv-fA8MUQ!y3*e zisWq0JsmvN09bJt4@pI0Cp*I$may#A`Ecl{(9i0&9f(;Gc0jGTeSS{Fp-~C;jBMdI zhC8%tJ3wKP-Oa?R&6QVQBXHp9Kv9!ERiBU^UebyWtRaLTJnbayHQ`C)q$H4 z06H4R0dYB>_gfhgOs0=`Ssdq;FJp1%NA@E;bv_*1Mh7mj*dzjB$BRe|ZrFRL+v8PM z0>x0m;@7psHbfg;N)vRn$2NY73y?|%9rgSVNgH>t%%j|0dUQR{meS+!1Xey2(j8q5`$+fxLrNPWj~2`m>fKIqoADGaUTwZg5*u0~r-~}pkX^6g z8}Ez;ZX{K`K%T&i!dk2zNg8gA8gBG8BId8;MDW|cCCY8WVdgGJEooa^$22Z--PD|t zy#YcFUEb**a=SL!T0@UBCJPz9E!=qFK5yXqx-oI@MJa;$S<~h0-VvWPT)H;VTdV$c zbBOEKn8%Lm)&dNb7=v41dwG@h^1B;$3I^`X13y+84uD{@ULNH~D7TN%j6z=80LZ%q zIlLBw(ZU;yZ%n@xM|~MXF!sQK$N`%>^k&|(UGBjR*#pg+xI2wf2FH7Mi-w!>-^C0?2xc?^!ewwsIzqnO1urJ&f!SD<%4D{7EYf;+zL60Wr zwkM7|Ni=S3`bp3O+wi|~C2Vy%I(+1eHy#bE{uq0+bgfgC-Gq>NWEiLLVJ_sWA{4dA zzu!9SS-@cD=QQ!Q4F;ZXoidDwmpZ7AL+%{88nohv$e*ktFou?O{Ou0G~CShRx5BH^%{V0g}{byGt}lN9kB zcJ_{QJzgJ4Jy9?~npn%Rz?Sc!)+b(oucX>}od7(zYPA<0= zQ*qWEYrd7uqn!=$FwJw}lXc5!dbP3Q1vs(vuzDGsCQXgC)B;%kn{D%?B~ySxEvqAC zmg1MDnYW7V;&sqS9|+JjS-lNLeLZ5JlSePnse#D$%K%$IZ={?5n+W=ee}904!No_f z_!s662fC!m_VpXG2?C3_Owr7|aC6R)+Pj}2T7wk7a~7}L8_B{%YouRoHWv03sOH(> zU^RY1?5cMEtzw8!38Okg^dzbLE-iVCjae>Q!=yQ^gHFi;9|bUJ-iZ^Wvr@qaT6P8RZ|D zvtu62><qhXxp_5end6Sx?p z80YctuYOHG)x2DQ`iD3oX&?W;##{?O>XdElJf7mOzu3;Q9!mrqeq8GOuSxUwf2q0A zybaRjd-v|y-Rq8wh-71v_*oi_ioPf~Z2I3O#Q-!9iv5Zv$mFD&heupgbadS8#IUN4 z%L&Kn-0UoytnA}EtgOSvSGfmlv>j_WAWT5e1GXD{V_tj1ZR>FLaBA^fGSr~^-T(Gz zC`KXo)|qW>TW3a2YAQGB+QJQdh$ZFq3%t?Au`x%gv!ks?B!cv%=N^35W!fG5$=9JC zZBt^L1OV0g6}2LeeZ%Kq#ZRjL-?*cVKca&b!m;#*1^-FzFz4u~ z_Soz;^*-oeo<(c!Xs^CS$&}UH-@hSl`ZRmPCcLF)+o8lMv+(mwE_Id`%Bi43E!;NN z{WLmO^&_a|&PB`fJiNFXd)ysblXo3zvfx}bL&J&(=Zd_!^i+9xq_+W`qTi$S_YX1n zXj@xOeTtx;Iu#=P9|y(^p3Ku zbmAtwO{L)@P(6L@0fD?5+YzWV0Kz>49h>QB^E$kV6sEK`X^Gty9z$(Av^d^M;5QD{hZWrX*|8 zgu@Ju>SC2IHnQw;rom0#vyAF55b-#n5KMOki?b&#sxMvFFF3DBPD9EsXga zX8|=iM)qayi|iLYg_rkE?+^`souYYCbnxXkc!qm7WVU_e(;J5ncw}Z&(f`*(^fH90 zV2MF+)|S6Y-R{2S=C=nK&-iX~BjBy^K+-$$M~{_CGzQ{Q-@kYwnI8#tk^eBh{JA8= z)%asq$wQg$`I*SQhL9e|wPKjMPKlJ_+n2}jXHR+`Crr59tE#L})|{yA`#Dd;K+kV@ zr!(R?>&Z@=W;p~)+g0U4C9B5|A8}on@H9UaX)nv6b_tICvY}AI>3Ydx`rF<9 z^TRPK=GlHu)SHokaAf>h^Qxj(Lk39R_H_~C1P-4+CetP)at%;mX-|XHJ!*Jq)6m;_ zvB&#!t@d7W;pMWY#aGzA&E#WptAfdr=H{0uDxMmtm#t0RJzsvr7Ri}zj88^05@FNO zkhmVYe@^=9T!}nIR_9zeZHx&eddG5J4jd9Yg;q8lv6>m~o`^6+peEj2{ms??_r)23 z;LK{)#Sw?P=-A;&)$bV$Ti~KeZ2(H@M=g&(udcQF^C&9CYvMG&c;(!ZXHsL-&R8f_ zqsVuk8?+K{rcl*Rr0LhcH>{r$ec}HYhwlQQz|AHs%&uI`3Aiv^)LkM1P#-*qi7?&Y z#!ko9GI~_H7-+YC{7fk)WQsSd0|9aS^Gkv_!*j?g1o>)$^GmBlTkHmCepQ;r#lmdq zlm<8NwU(tTWw##5x#bKWBYQwmLk>7nMdD_`Dp^au9_EX>MP}}qgSSX_f(*H~VcK&%pJBRd_q&qq%lYjW>J)7tiN)5%f6hCN9@Xw>ah{n%W!H`i&3%3I9l3tx5rLJ zEhh1~o7Yr{BL5T!hOdgZHRD%$3EmzCpyFCs3!<8Qbu#CuQ0HZg<8x|&nf z`$0Sd5p_@zffRd>9{Fan0vO-lK%r?s?~=GC)ThhG5ut(CnsiONe&RSfb!f+=A)J=J z^p?T#AaUw^S`ZP~n)tw?oNwRZK+eaCM1QB@R)Ejfq%Tfl;%Btk>TTMH%cDIwcG9uw z6V|pyNVCtb(?kM;hc^R}A5A5rS9^grvku${FavY|Mse9& zH@Js}zR=^d-@@Wu`ucS?1GmI5Pj$m9lZyFRn~ADOr~R`#qW(qP$6|Z_C*dK6_x5aN zQS?|c970_(<8%v7Ybqv8S#2#0Q%`7CXs)de`Y2MMJr0*n^EV zN6J4)r;^m+g;OwK_t{?WtD-z`v1NJcY%k8avjLvkf$`$SoPZ$l-p*4EaloFIG@3ltn0q%EEK z5lIX@b%;PKtNbVj>Txv+&fbq|Z`tnL`4%N!gHzK3=*QVie$fMR1+0yYjY4)q^u4{k zl|NZVDnkKH*yM~1Zk-~{l&-D^CDnisk;(SXj?LBC0mBC-p8y;KumqrUfL0zl-)RP? zRMgSL5vW5x7sfh-d@JV}L9~9TGR+Cx&=x%v?fpt1_W`#fK5_M)Ai8hp#Tck7%~NP& zBPC?*w{DF}AQq?j>yy+yfrA6CyjF@QNXw9(;N4#CrBU}0D2-j&p^=)L8YDzBs`DPB z2kDsZ^G+c+_mwKa4cg{PqVd7}r89kK&Avkm8ocCE3?#FbGW>fW?UVK-I7`n?u zLAEd&3T41UgaF*!@Ei(veuJ7Xs6DsPx9F^y=fQVsbs$xaW{aB#A3ThUfBCK)dZ~9? z+%>CaOC3r}tZgs4q6i1yn;!UI;qioS| z)cmPGeyEO9@GOE%K%S`UrvS|^hGh8fGPJ4u7JR6Sauhc}5X5?eoVHHt1pp;`K{hsp z^WNi+Bh~W@Yq%CB4r}iX0tcM6To+$~i!;CA2 z{3-WDt?8-hCF>dMg!noZm%m@0tW6E+N_ECep-(ILsxC^u! zgB~uAcS~mK3iEotb!XbK`Gkb`_-rErG4D#31}ruUz)(yVs3i5F%T=S(jOfbNwk|zy z9zJ{s5L}uPPlotTvZJfihf_4*$ixutHZm+&wQLbG?~1kQ)lMp(vY$hf#NP;B^bza{ zN0{Fl5Yb;lmvIJaK^rScmm$zPyRzI+@FkEx`w>HTjaK9C)e61PsjliwPVBz0xPpZC z0usqU@b`xbI}q|9W|F*vxL(3L1mcQDu%=7bnHq<;8ku5`U0YA|jhcPGio$>-ot|w9 zKR<9Kjv4|I%0B55=$qtZa&&omK(;xPPHrgLrNp2-6&sIORZ%fw zuTyeVAWWtOKoHcCY?*nF2Ky`zR~_Z0FahFM{D7>-$e6sx%7aVdw2u18K=%(TQ9p@? zYeiy7m|S}ffO4aMpDzh*u!`+ElZnF<5S#t{VCR_oE#fTgaDB`~1TWT#8Gu-*a-I=# z$+B?D@gdy+&S9Pa+5EGCTg&%;J;*tb%TYq^Eg~PIYdx3rbF#D7($QPjXv;sichEfW zD0K880nP~)goN>?|ISAAcRZV*QK>k>gttxW+pCYWRTA(?uJ~Ed;EBf%V1I`uIv%x)R@@gz zVvyDcomS`e>S)Q@DAHc9jg${YH#JFleV?naiky2MK~IU(^-zIH(hwsjVEy*zl9Cj= zoDgNS1oA7qd{i##Ja;`NR?e4nLBhhrqtb|w5m0?Q?~ykbbdonkRKiE{m8RS=5JbaNc^ z;Oi#oIyyO_-h<(TZ!g2o#WjL^z<^d_c@?Yd;jE%Si%-E34>@qV`W77DG}P&dn(C{6 z4&?Zg)6zIE5pbPK2HOP~>Ec`~@ebd_Bt$L1EdS+&e!BSS(?g5qMP@ujhmzEH?DTEfoGnnRy( z^7!#3%0-R?P>D^`_kfIQ4uCpbb>G{3LQ#cb`jlK2ES zBM}oip_OI#(2CdW(wrtQ6RBoEgd=evPxrOXfRQBPRIC?%x)_#3hJaQ!Af*fzQ14jb zDSOT2K&zh0`{w*;eWqP1jla!Q&Ud4y)U(pE1B*_U{03`d*7niwK>f2n6fc=cCxD69*~Ub8@54JE|63G#d43+r|xsz6wJ&+ z3(QvbCXCE-FG(E0owvW{Tr!{Vzi*9W)X=R|p;X*?4LgBYq-DCb8JP!eu?K}WpP36F z?>a=(fUg7@zJr>HaFGQsmjYt)=XWZa}>qzw|Ow+^yS{ zj*^<_)@Fft(2#S$`j*|trZ2{L)3;rzZsB1L8*T!VYvm zTqy^`d}WhqV^5yAUaDcp+VS>_pkw(S`%1p0wWM2E=4bZ3J@W@;wM=uQq&PFg`UzwxhPRLhk_cGwE*$kHrxA z*Wyq^E7uS-bZ6p@fO;FC-)NE>{BihK_=)-!W{cKnMdFsls6ryy&(Wm>KT)hx>XH+N zg`trR2Dpvk2+O86o;&6O)OQbR1NU)`6lJ;;T;T04=?JTr_T3feze1~Q3$(aMX$}HQ z+}>41UhF2b&uulMYey`9O*Dy*`F0m)||!soLvO;+#&0&dL(ogrv@MTAj|(YSzCyNRR(=GlU_l)PCL zOzUez=G5s{=*#vWN2BW0)>R5#NZeWJCuLLS2?)3 z1C%oP>#gA*A0AShtUXh`Qfx(wz81K=d5$=-vTCkEe1unwH(9@WQg{gZb8aJAs0ght{CAK+U8aX z=b&Mk&=?ilgk&RD{$!VjC2Z_m7OBqXB~h_w&y72r&v(Aa_d-7~kWPR4YsQU^i6MR8TMslC1|FTCO z=}}#b7G|l{b{>so(1BPgWHrm3t5IyAu7**(T5{)6APZ-{t4)5e{pg*NC`(OaT~YxW zl2V)s!hvjQ$i+5z{~Ljj{%}fB*~$0sJt7uc43vo-sLTCZ|A{>1Ibkun`A|SMKa&xL&ak#xET7cb4${)OpKeI)^D4jh z!^EEM;12D-S3lh>$;Sr;YED?XE7+yoSCW=FAriF*y+WZ^->bmS>suO+*yD)Q z#vNA7Q|kTrMvrP$YZ5gVeXI10bz2}cseMUM^;nXqJv21pPvlW2y!oD-!Vfe9ucagQIHNowJNe9HDKT0#-ci?xUi1Mr1jP`Oe|K|W)`At}^fei3xJ zR~I$X^W&K2BMhkXkS6K1;k~5PiP1E%;NO{W3XOTln6T?#B!4<>E#$R6kf@CLkw9He zIY}iukJ+mx*ngC>i<*js#}9m`mMDSzj@-_w`QlWm>yZyR1tou4Vyl{KI*P1Ui@#zu zOXa=_$5}MakOH%+TZ0aRtf-K_2dkBF0YB*AmDm}1-7?2Nx^Pf73^D@sQ#4aFTov{e zqh7M1cGcAXbEgln>j5;4i}%R?O@R+nL2CgS#tI|96OIgp;2DveJ&uA?iysp&HV_Pm zkba7Yu0!;9RssrW`T_grcW_u&S0#d9b8r)H4#!@xm28q2Kmu{_1LLDC^z=R}%CFWO z8R4L78KZ-F;RInA(3G>K?A;Kz)mC$3V@J443q-qUlir=aOB!1A;%)K5%!tI&aniVq zDx`J1k_c`Ify+HvXp0RsrVsX((-EKv_ojA6dM{+{8v|37a*bP zr~Wan8zWc>@3>aU2Ahf66tp0PKJ=hJ0uwhSmRHNhTM4AWZx?*sWatUp#Dh)uyE~<6 ziLjwzF7~=`K8b^}3$v=XvFiFy69H}BCmR9$sh?7nZ;}wD9d~J{y?k7GA6_a&{*(H8 zp|C#$*m?nU9%gQ|>ZSnO%_a7DAip+*c{vZ_mF_u!b^5jJ9{|bR`3>jO<+Gul#^Xwo z6e657rutz9Yw2^K_R-%#X+Bac*upCWb3_n-)7~Ckt;P9Ps*9TpfL_PXw^%TrTrwv&Vt`0eQ!|9}55501ZS*?2+X)m_B- zeA@J2QylKo3U6-N*N8+T`zC*FKBqysk&^zA?0TR*yZ9HF@*T zI*daR3hqx{=!&QGznhmloN;R7Js2%ctN9!zA9Aa?S~XdlA5C1P9!VNhXAdWM6GkVq z4pbnR&!JO?T>>vNdCS|*Wp`UU_^g>x+5#JoWGek!RllC*(>kwm-NvVsoO(k}jLESw z4TmahB%fQ@QohJ+vD5|Y@TNS~{P08DSuNGZh*nK{c420waoAmzn4g(%06y35_Gx1m z6W>ye6i2RA-8&Zs6a^XHARM{gnHz;wusq6G;xf3Ya43fwtsExqDNsJ2v+v$~ur)TDn z@bd>t@uopmRS|-e;k4*)Aq~U8VIC0;UwM9)UXwni@$pWIz3rU+6}OhSJo1Mnx- zN_kVo(F?Pk0y-uYN0xMZLs1NLjPqQ-8WPF*#8Ja2^ymFzrm5*MasMm3wg-e~!YKSk zpq+WtSQJ)5jO1San_fq)Kb5Kec_Pq-U{x%N?Z_HiXzc@1yl_T*?^fH$klV6P9{zx&Sb#Yvpb4T*xFbhC&j+H(wX1#TGUU|cEol7SKC3(>t z3k)=R<}4ZB%+gJJ;G2agxys77{wmQY_ZOxt-Sw8_$+pz`NM|AmJ$9Qx*$*WQtPpaE zUS;K2k6F@iBGT!SpStp(xr^XO&m;fyRe2Uz2kf2{Q%nx0$`^X$g1K`ZoHOOyhM45d zcSim;wVxihB6@c%May67-zN=gcOb3sdEgOc(j8Q2ep8&Jtb6T|(2C0~q{x>C>Hka(O#FDk@Ubq9 z2AyUq4x6}9%G;}V|8gJKR|FOa8xSvTBf9kdna zksab$@N&4Dy*DXxp-*;b#E$tMCsA`#i$r1VD@1SrD5J=%!D3-RHGKT20e(L3Mh)LGlGl>s=gTagbJ|Mo zw;tEnf(e@xt&Yu^Z85#;o9bbo{qFY`%IDVX51IM8Se^qq`fR>;$Zf8P;;RyJF#fAA z`1zV}Z_-Jj=iWC5s4ho>8?|o$qS|wBrP{N|W6#G6oGZmuo)uajG*ZZ$5n_=ePi!1YUr-r{ZL8BI{m%ppf5Ty^b1=}7bW9=lCbKh4o~)EB8UrWhje;{< z$mcl${nt|H*AMMS(99G*rNq&O^3hy#Hwg&^*F3 zVs}-*G5*Jo{<@Kn0JvfmKt6%}Z)bk_$8ZeXU_1%7Kl}O5rI=_8+^Y(io7+`sDw54A8MoMZh~7=HQ(Cve_>)VB&i-hhZ*a607}c)Gsq&gj=zMVaoGrMFjr- z)$b=tk+z4yF=E;N$3pEDq+G;JcPJ<*LMQ|L{ihA&g#Nb+%Hi$pg#Nc-K|w*wu-2ga z9Kqu2XHja5v!QR_zFofBK7Yceg-3XgSZY6`Ik6L`y?;OxLpC&r9jjc0E zTF;9}$@KOR4HM8{2vHH!@LB1{3Af_RgTHE%4w_Ux8_QiFSUiJvjucNwBXNi>Wlx^U zUw@n2g^v}^>?7o=--0mIF)(;usy(=`%V=&m?&h$OyJUXMO7Pn{%mOr6*uqOb(%TP) z1_q)44zlnPoeDJHc8?tag#B4U;YdS%dOtnU(XU{t$8JWI zx7IQscObB+#k2DhcaH!m5yGL5sQqls6E4bsXICB`7Pjb;UU_GyvPj>KPiD9{#8s;$ zwa;m*qAJ(TEKKuX^)l*Pi6%4YA}gQCfo`6vp8<|#V7<}89yx06U7rVL~t%H5`qu6 zCijJNB_ML0_(i71nM5dG)<=24?8Q z*ZyKz*G^fJPtKd^pB3ZzW}Qz5yje6-wyo++36Jh!n+xmWP0cKTbZgg66C|X|MFv6r zFCe8*^Fk`_x_W)cn4ke)R77iDxvlWUBS`3FigMOMZtmTvt*0z9LXoBck3QLT96RJG zoC9ECTw>qTow}!%^1Sb1NdTf8ME){0R?bt%CdC~9TR*_Ms1vDi{%(4|-@SWaYw8-= zvpdyj_OEftabl*z+>DAji5p$@y|O6F=fVS~nkREcno`H+a+}VDsur8UOfcukh?&{^ zXIp7$Mc*kqlMTzxT^!2z2AbOouTI=s5T;)oLvERVSrVrw#PK05@vi6n8ed=FH6| zQuiu!58YwDT3R>f$`nP#-vdJkk*{nk_@_&orF!L?Dk-3~g|OtwlTWv|hD)b&P6)h~ zs|K4wFBh^aoRlx=hKHkP)T8&O5*FOoPC2bm#QgTS?bBoeI;3grOJW|A=2m_|CA8}% zF-M&$cpkUnFsx3nkK(Ao3oI1>YpdsGY%+JQATWE>6&_iMNDp` z8GQOCDLl2|l8&soYh1r`n~reA^K!aZpTo;|J8&bPh;e!ssb1%7t`2vqqqm4@eY`Gj z0~f*bi)PBgWdESyzPzJ2t6ekvWmuGwaykE?3tRmPh+|q zwysV=_pW}}uKRtR5Bd~pJ&sGr+u7}VZ#SIIYQ+J|Ph;FY!(qOUPZ$=~HU|@_ zNR`s+?=BLGV)?bE0;}Q(Px!Lvk}$)Hxmzhy0OjYxE$EiMVeG);v;jjox9+Nk@AnuG zebfVniz#nhF2^nEZXeq5wz^UwgD5VoI^+E%x8g4Ta=rfUAM*alYj5wY$@YQpSQ+tZ zzHj*q)PL_Q#JLR@rzRNWSk7p7rpH-c z4i|F+U?Q5X@qH5F&iL=BYryFjbIif>_Fp`0;donSu-%1 z=LI9sfq$19cT=>x)Y8<9c~)s(J$cfTz0{KFFyuay?;>_-`kpuIiri!8MOtZf>sa6o zQ{KhEpF5|CXD^NB<(pcm*Lho?$(~m2IYCNRY8I~#w{;tAFDKye*|C6~-kqQ4>~rPN ztnBJS7{_XP$x}S}HW1(RZotkbALa$c03q5<{pcRe7=_8yzW!ZEXmp^LrNa63$Kcz< zNtCifYQPFDaTCimt6v1{W@22{g5a93tmaSMtzR1J^Y%Yjah^9Ws6rw9n7Sebyxy0~aZJ9~ue$!v!o$*`5 zJ%PJ@B$Y)LcN z9h+FVelH4084jg;hAa2Scz4I3w@*F_MUU8p6<;lPbSA7Dj~57-@avsF(qYeJsE1Sq zkRXbKLq4k4o~CG-6dA>Mm+&)$++G^DovyzauPoSOfUPqIsOz-pbDR%0X-@NAKPL<_ zZq$8Is{@kMVX!^9CDrVk{{Hn*{^7~2l;kiI{~54x)Qk^nOS=F$HC~B>_64reP(IRE zwbXW$fJFu~rP{xA5;jzQ0 z#h~T!;o%sI%V@jR9d@(Uy4m2}^Bb2gPTiXuD^HYz)$<$|&q^RnKvfZ6r5SUU^TTR_ zx|N~{jasHi1e5N!NkpB}LfwvO1-4+WMQ*LZhHA7=QjC^LN;4nxIada3&D+e5$ zT5v?5>xB1a}c@m6j3qvqU-$&O-J>CWCrgo&6wsx$Hr#j+N(}CC=vLG znxylUSG2nun32k*4w;ME{l1^#2j$n95II#x@0%FZb(Xia(Sx=UCM%rm z9IrZZ=ZP&Ykox{EjWn(>k#8l`b>HNXGwRDNE(;yMR$suw#8ZcJ$_`;J)DxHQOxmO+ zon|lvpf?gssqL)N4X_*1?uS`)L$+B?1o5UK(xvVU=b z5Or)f#S3>lI~dH~X98b=&N|%|h`^^_!!mqeBLcZU)(_?EupukD; zIJ(?o@s;{2WH)Re6G8F~0!A=jo!6>Zh1hTekEO0MO&f0Hg4|ePMdh<^(nFC$`VE)G zR2(i7OG$05R&#+OsH9&}3c&5raAINShBI+C%<}-@5hHu&ni@$8mbb{xyh4dI_IJ}s>&1yrE!)SqxTU5| zKkIC+^&5unOo8Ty+wAeidtEZD%IZx9=GxAy&cSEv#%G_CCRae`{BvU$DBGYF)MVlL ze%0O^RL8=jvx)v!%MqAVgqf`8?a01&3 zR8{N7b>HCies0^b`Po#G3;e>CeZFFR3$>)>77M7wz+>EIL4J)*w`YUIzVopHO99uzf4=#$5(@T6Oous@FEOR87{?M!j_z=CU;b+gOJ zA3N_H20k2nb_NOc6S{(l!(u~GL6FY)U3)rQK=t#~A~1ygYNAA)Y;xsF_kE@8(Zfvk z(Cg^veSW!mMmbcEUnlBnD)NbF0n(a(AR2s>PGB`-`?Gwu>VkhZ=X6mxTe!{?=-=aU*TUy}x#D4`p_g%hUfGENS?YUeO}&>^$2H?xGJpK_4D{o15t zy(ED&b?9+y=;8Rz@}Z-ozDbNM@gK+OT_Ca~FFpJ+1DL=e=IaV{I>S1;j+a&Z@+ zXl-S1JPnHcV=dau4vR+s0&*4g_0-%ckKng{cznQlZbyCV#@v*|ZP+4s@#v?X3AXw8 zxv;RXm80YKU2O9kE9iMn&xv^r-K;Ov<&O4jq`&1nCa2l5`74kKIGYBBB2dbO!L(cF z(Z0`ztWt;DaG@1|+8>m|=H}GCBR2Ww6t5iX@9tkMlui?%8Xs34=L2ngsP7LX!cDZvJV2mwKQ z0;rVGJA_b0zyd}^h;$So^dP+?fHKrb5eOuNA|)V%5=tnEfp^itnQ!L3zc0V;mvhfO z`#WWywbtJEo-JKKIZoEzBXD|kBt;I+X0$T^fM&dV_X7A5Q++RwDEdHvx`4yWk-Vf? z1-7MM!@AS&0CmvPXhPH-Vym_CocKqV{Iv^ly#SWYE*rz6JLdd)0uX38L1VoV75Ptz zbts?@b8sfT`?pU3Jnj)d7Db)h_-_f@8DRg_ARYz61OM=k+n3)^fYMtx(Xu`J{x*C2 z^28~?d+z>}b^hN4p&E62pR(-EY}*zVd)D3G-@p52y2!6p6wMU|d`aqM6`$*wG~nUt zeD_Z83Nt+OH3rE_FUtGX*4T4 zA|gURk5G+&)SIxa`ezCA#YNOR@V@v6ymTyB%OGGPr2|I~MxR8kj!(|-beyu?o{!b`iA#t7?QckTs-GC=fHLkeka_)Ur!jBCAQr8HY& zZ_}etfz$w}4Q$69-8GL)o90oEc?orHwC4f)wMS*Kb2mGDDlwkj?x6m*9^iylPZFaX zpHyHXK2=@JP(`9EB=D5iAZ`Rd+#@HSYLbisvgMtFDb%ybrB-+X6e_2@hd7G^W-O)SrM`^0AkaXWmHE6w&6wN07doo@hcQ`@!VJs_CQT?6vZCzm0%jlVlD>9KJduc zj_3Xy=Mr!_fvAv#wG*9GIwp9b&S67%s`g!vyLc*J$VrdF1{U_pd)Ux>G+kXhnJ}bX zD@e+}9gq@PLU|8^5h+V!uj6CDn4ThP>!e3Qep;LT@_SgSbPWEWh!`phJ!)j4sE$Z! zTRk#=6N$GAiktEvxeu3(4Q6hr>IMHOnruY`Kx4nm&u>vPqg5euGqD7+ zo|N?P_@EzzbsZ<(p@+^yDf2;rPSqfB=fLRivOT%@Wc(VoN%7< zoXM{e*Pi^lX&L=JY-BcRw&0KukSdYL8PR$zFlzMA_qe>Gp)|9e*jQsVe8r-*C=h$* z!LHGi|43k1-z_T6SLHcZyDzG3I*i*QCGx;;abZ6}7Z?9-y23!!A?>;1K$%(&v6M+Y z_3Sq%emI*GihJ0S!un&J_J1E7bT+k65t*R}XoXuA%YGH})4$wxn1+mVMdxq0d#=oFHP*kwE$QM_(?rvy#}}cP?lXE3)ACL6D`2IMLjd3 zWA2Q(?e>vtW%*!ck+o>wK0*GvU^fu9;_kt;O4CCJ9w3%1WT_s$* z_db}#U(l7E#~wDiE@=jqE?s&+hu8M@r#HK13I{t2g}Mz5^MK&6vzEmMk^5D6^uxKk zpY7}p*6W9nX7o$SV zV@DK)d@j85^y)s$6uTui#X2OtHmAb;u(jMhaJ%=5KGf@0e`t8})Dym+QtUwcR>kKY(O<`$Srj&JFHc5_X7Z=pF2wAN19{|dU7bX6;h{jTJ=lW?}$#`oh~UF z>$MYCqZ&0ooet?VK0tgIN@HwHpG7{_61kjEe5-P2v;FGLX=+$d4xPi+oH_BduOlyQ zJczw)CMqgMzwGWq*=%(0l!NUHv^V|Wy=q{``yZscX*xs2KqP;=#lG8?QIoG`T z$oRam!fPGg(J$8|9bR372`K2Ny}vhohXP+>h(QklDiQ$Nk`IL$%6&y*A!YjDfeg_` z<}rtk6|%$fgpE9rV4Cyt8Df0y8uw^H6vrqeDFtKP{JVJY97#(D@(+V$cvR3RL~+E0hfy9hm6D;O!Zg6T=+o2QM7 zQYofQDt>DXO&N~8w&el-zL`f;A(GAeqn-LX;nu99?X?)Qw26J>TqPB?1eK~TO2KJ> zjRDT#oJM*Xjk#AI8G5U|ex#hW2rAB`$_{{*2kyz&V+LebnPd)+8x@RD1p729nu$3I^fkgg<5a>gbbb2yG^SnT#;e-Ym{g0-!L==RGuB&@AR=;G%;Rux|t z?IuMMKi1i0P~1iapXf}K&)&ThAk;2tksIEBuxYk_@SHiRB!~LI<}- z(=RDHx8j!ktHhzKNTQ!iUE%ZRbo*|LhZ;<3r`d?8up^r5r&@SI0H8eyT89Pu z*S|PB5d0-g8~jM|8L-zD+*y{b(LU{o_!ekcrUS~vWy0LW+NX1P+Hv=EQC)oQ8}v}K zq3q&2v!=wD#I%y)GNen$I~&Gay{eOuLUFCVhx#e0X|#8##NWws?3x;LC5(fHip0V& zco})djK?`MrJAdxug{{COpJdQK6mCV8`XT{c5~_UKxtT@OM8#FI-Wsnr41|I zXFa9sK#K@L0g@?pR0WzyjnuG#OqnZ9iS@*EGqK>*O4#lTg!kcvcjG61Qfir#d-d8w zQKv74SK&4x-k4cv@B@Zv)VWubKS8P;nFNH+dus|z< zf%1ys7NRuy&YG@V%Se^U{=B}b+oMasrg7D2ef*eURwXC zW}=!^Q2`?HWm-ufv(E(Pt1d5-t~5^E?Pm;a)!b#5uNM&B5t%Z#{3(K@{G!M{oIO5l zvr%I+Q=fC2M-Fpx4ThSqWz=coq6)$hzn>Ew*_=ozEs-@UFg3zE^tEDY?21CBa4ALk zB=;ni6p?e?iO+tl)<4F5tL0Ex&;DiEViEKEd4~5hUKv^C%{wZ`&FXz@W|Gvp1NrCF z$(SO6JN(r7F@y!~B=XOlxR131XEvd{DzuW~(j2|CFfwqk!kL`HILPz)8#EDCTcuet zO$boy2yi1^^#Nh41lMRT=3Dnamd{SV_oquvwugq$KY9C;SJ5Lq7wC5W3yB`WsIL|_ zUh%cgP0Tg7*@(u3aOJ}uBMdQ}#D#%Y_)Nu!uVF_RT1)c)wWp_XF z%<<1(dCHfH9v~Ly2o-%E@NVV_fl?ztiVLkLZl@Vx*wc;kNa}^TF}KhM>y|d~k5QI5 zS753AS=j9-QU`$A{6Wc z&KPXREjjsC2Yxw{RbZf2%2$E!>KB%ed6<{%BN&0okI%}9=I_bqZ^=nMAL}LEhVC=u z-h9^AGIPy_Vsd0hE(;qKO;%{Y|_a`@Jb9$%DT#y4xXnGF@g-*$@+WSB?>9G8*f7M@s5)Ye?J zB*o{J9YEy^+}UWCPdUzf5Pfy=L*el%OYz2{u?cShoaJY?TFqXQ{15S1{Ee{BQN_;) zuefDeVD)b^Wud?1(|Y$K`pm*0nt2)8q#}nWJ1Kj!kST;%4NKF=#Sbt=~CH;%m*4f+#xky2mY20x46C*RZV_T;_&%%6eU zsqvh`Kky&d?rr*4ZPNaZ?^z?!Va0RdOX2*z)*$IC%Mb30PD+~n-X#V7J*E3zfTmR{ zR;gP1rAs)jrTp%-JwK`g0Ez9~i}F#Jsb(4UCX*L9YuHb)d&T)?jV?Y`hj|(rzk)7? zDO)J^|DcKP)qB|;>z&{xN{Er=7Y-oRo#_e0bbjAW_gqO-z8qhu#q|@KYa9wIGE{F_)}UnuHrf!IR8kcH}r zMp-$I)ax()7flSc_kk1mO^PI<#-typ@vb*?;qImXO%hbqJteSUSFh9=g^<-P^wLbT zR7+{b3<%JC5Zcowz>EGgQfIK@yhT5gY_>U1r2RW8uxk(QaMz44Xpr^H>ifO*_L1-H z=MH!WFb~_;>Au;+&k%7(3acG3DYSf&vFFxnq5*qg&EatQ;e$F_$5n?|rl~=HoCQOt zJ#(;Dc(4!k3e^%lw)~(ZKn&HCX_Hyyg)5W;l!{k=W)Tpm&1E24M#(TK%`7MtV<_CY z03YhaCuL=B99@+Sc&TlxHd{+YVU#aRU$;TwbqoVkP;p>-WiXQZ{(3RIS`|rMqGotk zTUG?;N^|?gg5uWPQ>7s!raMatV7?1?FyOGKPE)EO71KG`C<-1p)>k*@oKJrll~(lK2n?-$FmVPIpVB6- zrrT4NjISJt4q75fskbw4FtpDnNh9W7;hKOzH<_}w;#)iAH9{%w7nZIwG*?uM({0G9 zm7kTJM>D!BGnV|TmnwC(Sm&h(Q=U?>S@2Eyj)osaSZt0n(0KBvO}9ZRb$T8lG`Qx! zA$-oN!)M(`fN!6<2p}oxG^D1|4HK|MC@Q_Bt+G~cF(;ZDi>0o3gO}IyJ6tgkE8|)S zc48n?=q}oKLCoWJJ~R|p}`C!+0$SKfx`h)+Prb-Ic891Aa;J5 zPzR?^0K;ipykV(#StS!5kc-#R@$t8uK~KZ@VOTt(8UQEwO3b>iY-)VprFOJ;Rc~RT zao#9!bTF5blM}d6No*P9#l3C>3swYV-2aHg>qUD5KGb=kx|p|Xj*k9YZ~`S$ZcyVXfmPPc(Oc%0C|SJPA<9J$P^a;%_^3G`?;J$I^Bg l8SMxde;fDzS*d=DgDXeu($v|srd_~K`=;KF!fVzK{|E1)aP0s9 literal 0 HcmV?d00001 diff --git a/docs/docsite/rst/common/images/rbac_team_access_add-roles.png b/docs/docsite/rst/common/images/rbac_team_access_add-roles.png new file mode 100644 index 0000000000000000000000000000000000000000..0597ea44a014efa9edd4a77132c8e117fecb3a4a GIT binary patch literal 52856 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%0D{@mc9 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 G@qYl#n$|7= literal 0 HcmV?d00001 diff --git a/docs/docsite/rst/common/images/rbac_team_access_apply-roles.png b/docs/docsite/rst/common/images/rbac_team_access_apply-roles.png new file mode 100644 index 0000000000000000000000000000000000000000..9f48984c99c87da28e2ef12113d607269479e755 GIT binary patch literal 92307 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 Qn6ez@F>&pu>3^MVTeE)w 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

cW&@+6LVTJYZIqdkK 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 Nbw=-Wp1M`&{{cKV*o6Q9 literal 0 HcmV?d00001 diff --git a/docs/docsite/rst/common/images/rbac_user_access_add-roles.png b/docs/docsite/rst/common/images/rbac_user_access_add-roles.png new file mode 100644 index 0000000000000000000000000000000000000000..af07da9e57a353083213c53344387f16cfba4715 GIT binary patch literal 51487 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 WJ|XX%cf^Vf{->u+)XLGc4*ow%%M}Lz literal 0 HcmV?d00001 diff --git a/docs/docsite/rst/common/images/rbac_user_access_apply-roles.png b/docs/docsite/rst/common/images/rbac_user_access_apply-roles.png new file mode 100644 index 0000000000000000000000000000000000000000..02f704d7ee8cf53d9081a66ac852f0ba2275a0cf GIT binary patch literal 71864 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%0DQqwCxj)nOw-Y2^~`oC&yCtjM*-XFNrzdw6}9<{ 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 R0Tuiq=p4{~yw^79{{d)RW{&^> literal 0 HcmV?d00001 diff --git a/docs/docsite/rst/userguide/index.rst b/docs/docsite/rst/userguide/index.rst index 11d0ca09864c..01aa96857266 100644 --- a/docs/docsite/rst/userguide/index.rst +++ b/docs/docsite/rst/userguide/index.rst @@ -29,6 +29,7 @@ You can also find lots of AWX discussion and get answers to questions at `forum. organizations users teams + rbac credentials credential_types credential_plugins diff --git a/docs/docsite/rst/userguide/rbac.rst b/docs/docsite/rst/userguide/rbac.rst new file mode 100644 index 000000000000..62f02fa960ae --- /dev/null +++ b/docs/docsite/rst/userguide/rbac.rst @@ -0,0 +1,517 @@ +.. _rbac-ug: + +Role-Based Access Controls +========================== + +.. index:: + single: role-based access controls + pair: security; RBAC + +Role-Based Access Controls (RBAC) are built into AWX and allow administrators to delegate access to server inventories, organizations, and more. Administrators can also centralize the management of various credentials, allowing end users to leverage a needed secret without ever exposing that secret to the end user. RBAC controls allow AWX to help you increase security and streamline management. + +This chapter has two parts: the latest RBAC model (:ref:`rbac-dab-ug`) and the :ref:`existing RBAC ` implementation. + +.. _rbac-dab-ug: + +DAB RBAC +--------- + +.. index:: + single: DAB + single: roles + pair: DAB; RBAC + +This section describes the latest changes to RBAC, involving use of the ``django-ansible-base`` (DAB) library, to enhance existing roles, provide a uniformed model that is compatible with platform (enterprise) components, and allow creation of custom roles. However, the internals of the system in the backend have changes implemented, but they are not reflected yet in the AWX UI. The change to the backend maintains a compatibility layer so the “old” roles in the API still exists temporarily, until a fully-functional compatible UI replaces the existing roles. + +New functionality, specifically custom roles, are possible through direct API clients or the API browser, but the presentation in the AWX UI might not reflect the changes made in the API. + +The new DAB version of RBAC allows creation of custom roles which can be done via the ``/api/v2/role_definitions/`` endpoint. Then these can only be assigned using the new endpoints, ``/api/v2/role_user_assignments/`` and ``/api/v2/role_team_assignments/``. + +If you do not want to allow custom roles, you can change the setting ``ANSIBLE_BASE_ALLOW_CUSTOM_ROLES`` to ``False``. This is still a file-based setting for now. + +New “add” permissions are a major highlight of this change. You could create a custom organization role that allows users to create all (or some) types of resources, and apply it to a particular organization. So instead of allowing a user to edit all projects, they can create a new project, and after creating it, they will automatically get admin role just for the objects they created. + + +Resource access for teams +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +This section provides a reference for managing team roles within individual resources as shown in the new UI and the corresponding API calls. + +Access the resource's **Team Access** tab to manage the team roles. + +.. image:: ../common/images/rbac_jt_team_access.png + +To obtain a list of team role assignments from the API: + +:: + + GET /api/v2/role_team_assignments/?object_id=&content_type__model=jobtemplate + +The columns are arranged so that the team name appears in the first column. The role name is under ``summary_fields.role_definition.name`` + +To revoke a role assignment for a team in the API: + +:: + + DELETE /api/v2/role_team_assignments// + + +Add roles +^^^^^^^^^^ + +Clicking the **Add roles** button from the **Team Access** tab opens the **Add roles** wizard, where you can select the teams to which you want to add roles. + +.. image:: ../common/images/rbac_team_access_add-roles.png + +To list the teams from the service endpoint: + +:: + + GET /api/v2/teams + + +The next step of the wizard in the controller UI is to apply roles to the selected team(s). + +.. image:: ../common/images/rbac_team_access_apply-roles.png + +To list available role definitions for the selected resource type in the API, issue the following, but replace ``content_type`` below to match the resource type: + +:: + + GET /api/v2/role_definitions/?content_type__model=jobtemplate + + +Finally, review your selections and click **Save** to save your changes. + +.. image:: ../common/images/rbac_team_access_add-roles-review.png + +To assign roles to selected teams in the API, you must assign a single role to individual teams separately by referencing the team ID and resource ID from the controller associated with the ``object_id``. + +Make a POST request to this resource (``jobtemplate.id`` in this example): + +:: + + POST /api/v2/role_team_assignments/ + +The following shows an example of the payload sent for the POST request made above: + +:: + + {"team": 25, "role_definition": 4, "object_id": "10"} + + +When changes are successfully applied via the UI, a message displays to confirm the changes: + +.. image:: ../common/images/rbac_team_access_add-roles-success.png + + +Resource access for users +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +This section provides a reference for managing user roles within individual resources as shown in the new UI and the corresponding API calls. + +Access the resource's **User Access** tab to manage the user roles. + +.. image:: ../common/images/rbac_jt_user_access.png + +To obtain a list of user role assignments from the API: + +:: + + GET /api/v2/role_user_assignments/?object_id=&content_type__model=jobtemplate + +The columns are arranged so that the user name appears in the first column. The role name is under ``summary_fields.role_definition.name`` + +To revoke a role assignment for a user in the API: + +:: + + DELETE /api/v2/role_user_assignments// + + +Add roles +^^^^^^^^^^ + +Clicking the **Add roles** button from the **User Access** tab opens the **Add roles** wizard, where you can select the users to which you want to add roles. + +.. image:: ../common/images/rbac_user_access_add-roles.png + +To list the teams from the service endpoint: + +:: + + GET /api/v2/users + + +The next step of the wizard in the controller UI is to apply roles to the selected team(s). + +.. image:: ../common/images/rbac_user_access_apply-roles.png + +To list available role definitions for the selected resource type in the API, issue the following, but replace ``content_type`` below to match the resource type: + +:: + + GET /api/v2/role_definitions/?content_type__model=jobtemplate + + +Finally, review your selections and click **Save** to save your changes. + +.. image:: ../common/images/rbac_user_access_add-roles-review.png + +To assign roles to selected users in the API, you must assign a single role to individual users separately by referencing the user ID and resource ID from the controller associated with the ``object_id``. + +Make a POST request to this resource (``jobtemplate.id`` in this example): + +:: + + POST /api/v2/role_user_assignments/ + +The following shows an example of the payload sent for the POST request made above: + +:: + + {"user": 25, "role_definition": 4, "object_id": "10"} + +When changes are successfully applied via the UI, a message displays to confirm the changes: + +.. image:: ../common/images/rbac_team_access_add-roles-success.png + + +Custom roles +~~~~~~~~~~~~~ +.. index:: + single: DAB + single: custom roles + pair: custom; roles + +In the DAB RBAC model, Superusers have the ability to create, modify, and delete custom roles. + +To create a custom role, click the **Create role** button from the **Roles** resource in the UI, and provide the details of the new role: + +- **Name**: Required +- **Description**: Enter an arbitrary description as appropriate (optional) +- **Resource Type**: Required. Select the resource type from the drop-down menu (only one resource type per role allowed). This is equivalent to ``content_type`` in ``OPTIONS /api/v2/role_definitions`` for choices. +- Select permissions based on the selected of resource type. (Alan will provide an endpoint containing dictionary for available permissions based on content type (The UI can use this to maintain static readable translatable texts on the client side) TBD) + +Modifying a custom role only allows you to change the permissions but does not not allow changes to the content type. + +To delete a custom role: + +:: + + DELETE /api/v2/role_definitions/:id + + +.. _rbac-legacy-ug: + +Legacy RBAC model +------------------ + +.. index:: + single: roles + pair: legacy; RBAC + +As in the name, RBAC is role-based, and roles contain a list of permissions. This is a domain-centric concept, where organization-level roles can grant you a permission (like ``update_project``) to everything in that domain, including all projects in that organizations. + +There are a few main concepts that you should become familiar with regarding AWX's RBAC design--roles, resources, and users. Users can be members of a role, which gives them certain access to any resources associated with that role, or any resources associated with "descendant" roles. + +A role is essentially a list of permissions. Users are granted access to these capabilities and AWX's resources through the roles to which they are assigned or through roles inherited through the role hierarchy. + +Roles associate a group of capabilities with a group of users. All capabilities are derived from membership within a role. Users receive capabilities only through the roles to which they are assigned or through roles they inherit through the role hierarchy. All members of a role have all capabilities granted to that role. Within an organization, roles are relatively stable, while users and capabilities are both numerous and may change rapidly. Users can have many roles. + + +Role Hierarchy and Access Inheritance +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Imagine that you have an organization named "SomeCompany" and want to allow two people, "Josie" and "Carter", access to manage all the settings associated with that organization. You should make both people members of the organization's ``admin_role``. + +|user-role-relationship| + +.. |user-role-relationship| image:: ../common/images/user-role-relationship.png + +Often, you will have many Roles in a system and you will want some roles to include all of the capabilities of other roles. For example, you may want a System Administrator to have access to everything that an Organization Administrator has access to, who has everything that a Project Administrator has access to, and so on. + +This concept is referred to as the 'Role Hierarchy': + +- Parent roles get all capabilities bestowed on any child roles +- Members of roles automatically get all capabilities for the role they are a member of, as well as any child roles. + +The Role Hierarchy is represented by allowing Roles to have "Parent Roles". Any capability that a Role has is implicitly granted to any parent roles (or parents of those parents, and so on). + +|rbac-role-hierarchy| + +.. |rbac-role-hierarchy| image:: ../common/images/rbac-role-hierarchy.png + +Often, you will have many Roles in a system and you will want some roles to include all of the capabilities of other roles. For example, you may want a System Administrator to have access to everything that an Organization Administrator has access to, who has everything that a Project Administrator has access to, and so on. We refer to this concept as the 'Role Hierarchy' and it is represented by allowing Roles to have "Parent Roles". Any capability that a Role has is implicitly granted to any parent roles (or parents of those parents, and so on). Of course Roles can have more than one parent, and capabilities are implicitly granted to all parents. + +|rbac-heirarchy-morecomplex| + +.. |rbac-heirarchy-morecomplex| image:: ../common/images/rbac-heirarchy-morecomplex.png + +RBAC controls also give you the capability to explicitly permit User and Teams of Users to run playbooks against certain sets of hosts. Users and teams are restricted to just the sets of playbooks and hosts to which they are granted capabilities. And, with AWX, you can create or import as many Users and Teams as you require--create users and teams manually or import them from LDAP or Active Directory. + +RBACs are easiest to think of in terms of who or what can see, change, or delete an "object" for which a specific capability is being determined. + +Applying RBAC +~~~~~~~~~~~~~~~~~ + +The following sections cover how to apply AWX's RBAC system in your environment. + + +Editing Users +^^^^^^^^^^^^^^^ + +When editing a user, a AWX system administrator may specify the user as being either a *System Administrator* (also referred to as the Superuser) or a *System Auditor*. + +- System administrators implicitly inherit all capabilities for all objects (read/write/execute) within the AWX environment. +- System Auditors implicitly inherit the read-only capability for all objects within the AWX environment. + +Editing Organizations +^^^^^^^^^^^^^^^^^^^^^^^^ + +When editing an organization, system administrators may specify the following roles: + +- One or more users as organization administrators +- One or more users as organization auditors +- And one or more users (or teams) as organization members + + +Users/teams that are members of an organization can view their organization administrator. + +Users who are organization administrators implicitly inherit all capabilities for all objects within that AWX organization. + +Users who are organization auditors implicitly inherit the read-only capability for all objects within that AWX organization. + + +Editing Projects in an Organization +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +When editing a project in an organization for which they are the administrator, system administrators and organization administrators may specify: + +- One or more users/teams that are project administrators +- One or more users/teams that are project members +- And one or more users/teams that may update the project from SCM, from among the users/teams that are members of that organization. + +Users who are members of a project can view their project administrators. + +Project administrators implicitly inherit the capability to update the project from SCM. + +Administrators can also specify one or more users/teams (from those that are members of that project) that can use that project in a job template. + + +Creating Inventories and Credentials within an Organization +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +All access that is granted to use, read, or write credentials is handled through roles, which use AWX's RBAC system to grant ownership, auditor, or usage roles. + +System administrators and organization administrators may create inventories and credentials within organizations under their administrative capabilities. + +Whether editing an inventory or a credential, System administrators and organization administrators may specify one or more users/teams (from those that are members of that organization) to be granted the usage capability for that inventory or credential. + +System administrators and organization administrators may specify one or more users/teams (from those that are members of that organization) that have the capabilities to update (dynamic or manually) an inventory. Administrators can also execute ad hoc commands for an inventory. + + +Editing Job Templates +^^^^^^^^^^^^^^^^^^^^^^ + +System administrators, organization administrators, and project administrators, within a project under their administrative capabilities, may create and modify new job templates for that project. + +When editing a job template, administrators (AWX, organization, and project) can select among the inventory and credentials in the organization for which they have usage capabilities or they may leave those fields blank so that they will be selected at runtime. + +Additionally, they may specify one or more users/teams (from those that are members of that project) that have execution capabilities for that job template. The execution capability is valid regardless of any explicit capabilities the user/team may have been granted against the inventory or credential specified in the job template. + +User View +^^^^^^^^^^^^^ + +A user can: + +- See any organization or project for which they are a member +- Create their own credential objects which only belong to them +- See and execute any job template for which they have been granted execution capabilities + +If a job template that a user has been granted execution capabilities on does not specify an inventory or credential, the user will be prompted at run-time to select among the inventory and credentials in the organization they own or have been granted usage capabilities. + +Users that are job template administrators can make changes to job templates; however, to change to the inventory, project, playbook, credentials, or instance groups used in the job template, the user must also have the "Use" role for the project and inventory currently being used or being set. + +.. _rbac-ug-roles: + +Roles +~~~~~~~~~~~~~ + +All access that is granted to use, read, or write credentials is handled through roles, and roles are defined for a resource. + + +Built-in roles +^^^^^^^^^^^^^^ + +The following table lists the RBAC system roles and a brief description of the how that role is defined with regard to privileges in AWX. + ++-----------------------------------------------------------------------+------------------------------------------------------------------------------------------+ +| System Role | What it can do | ++=======================================================================+==========================================================================================+ +| System Administrator - System wide singleton | Manages all aspects of the system | ++-----------------------------------------------------------------------+------------------------------------------------------------------------------------------+ +| System Auditor - System wide singleton | Views all aspects of the system | ++-----------------------------------------------------------------------+------------------------------------------------------------------------------------------+ +| Ad Hoc Role - Inventory | Runs ad hoc commands on an Inventory | ++-----------------------------------------------------------------------+------------------------------------------------------------------------------------------+ +| Admin Role - Organizations, Teams, Inventory, Projects, Job Templates | Manages all aspects of a defined Organization, Team, Inventory, Project, or Job Template | ++-----------------------------------------------------------------------+------------------------------------------------------------------------------------------+ +| Auditor Role - All | Views all aspects of a defined Organization, Team, Inventory, Project, or Job Template | ++-----------------------------------------------------------------------+------------------------------------------------------------------------------------------+ +| Execute Role - Job Templates | Runs assigned Job Template | ++-----------------------------------------------------------------------+------------------------------------------------------------------------------------------+ +| Member Role - Organization, Team | User is a member of a defined Organization or Team | ++-----------------------------------------------------------------------+------------------------------------------------------------------------------------------+ +| Read Role - Organizations, Teams, Inventory, Projects, Job Templates | Views all aspects of a defined Organization, Team, Inventory, Project, or Job Template | ++-----------------------------------------------------------------------+------------------------------------------------------------------------------------------+ +| Update Role - Project | Updates the Project from the configured source control management system | ++-----------------------------------------------------------------------+------------------------------------------------------------------------------------------+ +| Update Role - Inventory | Updates the Inventory using the cloud source update system | ++-----------------------------------------------------------------------+------------------------------------------------------------------------------------------+ +| Owner Role - Credential | Owns and manages all aspects of this Credential | ++-----------------------------------------------------------------------+------------------------------------------------------------------------------------------+ +| Use Role - Credential, Inventory, Project, IGs, CGs | Uses the Credential, Inventory, Project, IGs, or CGs in a Job Template | ++-----------------------------------------------------------------------+------------------------------------------------------------------------------------------+ + + +A Singleton Role is a special role that grants system-wide permissions. AWX currently provides two built-in Singleton Roles but the ability to create or customize a Singleton Role is not supported at this time. + +Common Team Roles - "Personas" +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Support personnel typically works on ensuring that AWX is available and manages it a way to balance supportability and ease-of-use for users. Often, support will assign “Organization Owner/Admin” to users in order to allow them to create a new Organization and add members from their team the respective access needed. This minimizes supporting individuals and focuses more on maintaining uptime of the service and assisting users who are using AWX. + +Below are some common roles managed by the AWX Organization: + ++-----------------------+------------------------+-----------------------------------------------------------------------------------------------------------+ +| | System Role | | Common User | | Description | +| | (for Organizations) | | Roles | | ++-----------------------+------------------------+-----------------------------------------------------------------------------------------------------------+ +| | Owner | | Team Lead - | | This user has the ability to control access for other users in their organization. | +| | | Technical Lead | | They can add/remove and grant users specific access to projects, inventories, and job templates. | +| | | | This user also has the ability to create/remove/modify any aspect of an organization’s projects, | +| | | | templates, inventories, teams, and credentials. | ++-----------------------+------------------------+-----------------------------------------------------------------------------------------------------------+ +| | Auditor | | Security Engineer - | | This account can view all aspects of the organization in read-only mode. | +| | | Project Manager | | This may be good for a user who checks in and maintains compliance. | +| | | | This might also be a good role for a service account who manages or | +| | | | ships job data from AWX to some other data collector. | ++-----------------------+------------------------+-----------------------------------------------------------------------------------------------------------+ +| | Member - | | All other users | | These users by default as an organization member do not receive any access to any aspect | +| | Team | | | of the organization. In order to grant them access the respective organization owner needs | +| | | | to add them to their respective team and grant them Admin, Execute, Use, Update, Ad-hoc | +| | | | permissions to each component of the organization’s projects, inventories, and job templates. | ++-----------------------+------------------------+-----------------------------------------------------------------------------------------------------------+ +| | Member - | | Power users - | | Organization Owners can provide “admin” through the team interface, over any component | +| | Team “Owner” | | Lead Developer | | of their organization including projects, inventories, and job templates. These users are able | +| | | | to modify and utilize the respective component given access. | ++-----------------------+------------------------+-----------------------------------------------------------------------------------------------------------+ +| | Member - | | Developers - | | This will be the most common and allows the organization member the ability to execute | +| | Team “Execute” | | Engineers | | job templates and read permission to the specific components. This is permission applies to templates. | ++-----------------------+------------------------+-----------------------------------------------------------------------------------------------------------+ +| | Member - | | Developers - | | This permission applies to an organization’s credentials, inventories, and projects. | +| | Team “Use” | | Engineers | | This permission allows the ability for a user to use the respective component within their job template.| ++-----------------------+------------------------+-----------------------------------------------------------------------------------------------------------+ +| | Member - | | Developers - | | This permission applies to projects. Allows the user to be able to run an SCM update on a project. | +| | Team “Update” | | Engineers | | ++-----------------------+------------------------+-----------------------------------------------------------------------------------------------------------+ + + +Function of roles: editing and creating +------------------------------------------ + +Organization “resource roles” functionality are specific to a certain resource type - such as workflows. Being a member of such a role usually provides two types of permissions, in the case of workflows, where a user is given a "workflow admin role" for the organization "Default": + +- this user can create new workflows in the organization "Default" +- user can edit all workflows in the "Default" organization + +One exception is job templates, where having the role is irrelevant of creation permission (more details on its own section). + +Independence of resource roles and organization membership roles +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Resource-specific organization roles are independent of the organization roles of admin and member. Having the "workflow admin role" for the "Default" organization will not allow a user to view all users in the organization, but having a "member" role in the "Default" organization will. The two types of roles are delegated independently of each other. + + +Necessary permissions to edit job templates +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Users can edit fields not impacting job runs (non-sensitive fields) with a Job Template admin role alone. However, to edit fields that impact job runs in a job template, a user needs the following: + +- **admin** role to the job template and container groups +- **use** role to related project +- **use** role to related inventory +- **use** role to related instance groups + +An "organization job template admin" role was introduced, but having this role isn't sufficient by itself to edit a job template within the organization if the user does not have use role to the project / inventory / instance group or an admin role to the container group that a job template uses. + +In order to delegate *full* job template control (within an organization) to a user or team, you will need grant the team or user all 3 organization-level roles: + +- job template admin +- project admin +- inventory admin + +This will ensure that the user (or all users who are members of the team with these roles) have full access to modify job templates in the organization. If a job template uses an inventory or project from another organization, the user with these organization roles may still not have permission to modify that job template. For clarity of managing permissions, it is best-practice to not mix projects / inventories from different organizations. + +RBAC permissions +^^^^^^^^^^^^^^^^^^^ + +Each role should have a content object, for instance, the org admin role has a content object of the org. To delegate a role, you need admin permission to the content object, with some exceptions that would result in you being able to reset a user's password. + +**Parent** is the organization. + +**Allow** is what this new permission will explicitly allow. + +**Scope** is the parent resource that this new role will be created on. Example: ``Organization.project_create_role``. + +An assumption is being made that the creator of the resource should be given the admin role for that resource. If there are any instances where resource creation does not also imply resource administration, they will be explicitly called out. + +Here are the rules associated with each admin type: + +**Project Admin** + +- Allow: Create, read, update, delete any project +- Scope: Organization +- User Interface: *Project Add Screen - Organizations* + +**Inventory Admin** + +- Parent: Org admin +- Allow: Create, read, update, delete any inventory +- Scope: Organization +- User Interface: *Inventory Add Screen - Organizations* + +.. note:: + + As it is with the **Use** role, if you give a user Project Admin and Inventory Admin, it allows them to create Job Templates (not workflows) for your organization. + +**Credential Admin** + +- Parent: Org admin +- Allow: Create, read, update, delete shared credentials +- Scope: Organization +- User Interface: *Credential Add Screen - Organizations* + +**Notification Admin** + +- Parent: Org admin +- Allow: Assignment of notifications +- Scope: Organization + +**Workflow Admin** + +- Parent: Org admin +- Allow: Create a workflow +- Scope: Organization + +**Org Execute** + +- Parent: Org admin +- Allow: Executing JTs and WFJTs +- Scope: Organization + + +The following is a sample scenario showing an organization with its roles and which resource(s) each have access to: + +.. image:: ../common/images/rbac-multiple-resources-scenario.png \ No newline at end of file diff --git a/docs/docsite/rst/userguide/security.rst b/docs/docsite/rst/userguide/security.rst index 00420a5326c0..6834e12096a9 100644 --- a/docs/docsite/rst/userguide/security.rst +++ b/docs/docsite/rst/userguide/security.rst @@ -39,320 +39,4 @@ Isolation functionality and variables pair: isolation; functionality pair: isolation; variables -.. include:: ../common/isolation_variables.rst - -.. _rbac-ug: - -Role-Based Access Controls ------------------------------ - -.. index:: - single: role-based access controls - pair: security; RBAC - -Role-Based Access Controls (RBAC) are built into AWX and allow administrators to delegate access to server inventories, organizations, and more. Administrators can also centralize the management of various credentials, allowing end users to leverage a needed secret without ever exposing that secret to the end user. RBAC controls allow AWX to help you increase security and streamline management. - -RBACs are easiest to think of in terms of Roles which define precisely who or what can see, change, or delete an "object" for which a specific capability is being set. RBAC is the practice of granting roles to users or teams. - -There are a few main concepts that you should become familiar with regarding AWX's RBAC design--roles, resources, and users. Users can be members of a role, which gives them certain access to any resources associated with that role, or any resources associated with "descendant" roles. - -A role is essentially a collection of capabilities. Users are granted access to these capabilities and AWX's resources through the roles to which they are assigned or through roles inherited through the role hierarchy. - -Roles associate a group of capabilities with a group of users. All capabilities are derived from membership within a role. Users receive capabilities only through the roles to which they are assigned or through roles they inherit through the role hierarchy. All members of a role have all capabilities granted to that role. Within an organization, roles are relatively stable, while users and capabilities are both numerous and may change rapidly. Users can have many roles. - - -Role Hierarchy and Access Inheritance -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Imagine that you have an organization named "SomeCompany" and want to allow two people, "Josie" and "Carter", access to manage all the settings associated with that organization. You should make both people members of the organization's ``admin_role``. - -|user-role-relationship| - -.. |user-role-relationship| image:: ../common/images/user-role-relationship.png - -Often, you will have many Roles in a system and you will want some roles to include all of the capabilities of other roles. For example, you may want a System Administrator to have access to everything that an Organization Administrator has access to, who has everything that a Project Administrator has access to, and so on. - -This concept is referred to as the 'Role Hierarchy': - -- Parent roles get all capabilities bestowed on any child roles -- Members of roles automatically get all capabilities for the role they are a member of, as well as any child roles. - -The Role Hierarchy is represented by allowing Roles to have "Parent Roles". Any capability that a Role has is implicitly granted to any parent roles (or parents of those parents, and so on). - -|rbac-role-hierarchy| - -.. |rbac-role-hierarchy| image:: ../common/images/rbac-role-hierarchy.png - -Often, you will have many Roles in a system and you will want some roles to include all of the capabilities of other roles. For example, you may want a System Administrator to have access to everything that an Organization Administrator has access to, who has everything that a Project Administrator has access to, and so on. We refer to this concept as the 'Role Hierarchy' and it is represented by allowing Roles to have "Parent Roles". Any capability that a Role has is implicitly granted to any parent roles (or parents of those parents, and so on). Of course Roles can have more than one parent, and capabilities are implicitly granted to all parents. - -|rbac-heirarchy-morecomplex| - -.. |rbac-heirarchy-morecomplex| image:: ../common/images/rbac-heirarchy-morecomplex.png - -RBAC controls also give you the capability to explicitly permit User and Teams of Users to run playbooks against certain sets of hosts. Users and teams are restricted to just the sets of playbooks and hosts to which they are granted capabilities. And, with AWX, you can create or import as many Users and Teams as you require--create users and teams manually or import them from LDAP or Active Directory. - -RBACs are easiest to think of in terms of who or what can see, change, or delete an "object" for which a specific capability is being determined. - -Applying RBAC -~~~~~~~~~~~~~~~~~ - -The following sections cover how to apply AWX's RBAC system in your environment. - - -Editing Users -^^^^^^^^^^^^^^^ - -When editing a user, a AWX system administrator may specify the user as being either a *System Administrator* (also referred to as the Superuser) or a *System Auditor*. - -- System administrators implicitly inherit all capabilities for all objects (read/write/execute) within the AWX environment. -- System Auditors implicitly inherit the read-only capability for all objects within the AWX environment. - -Editing Organizations -^^^^^^^^^^^^^^^^^^^^^^^^ - -When editing an organization, system administrators may specify the following roles: - -- One or more users as organization administrators -- One or more users as organization auditors -- And one or more users (or teams) as organization members - - -Users/teams that are members of an organization can view their organization administrator. - -Users who are organization administrators implicitly inherit all capabilities for all objects within that AWX organization. - -Users who are organization auditors implicitly inherit the read-only capability for all objects within that AWX organization. - - -Editing Projects in an Organization -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -When editing a project in an organization for which they are the administrator, system administrators and organization administrators may specify: - -- One or more users/teams that are project administrators -- One or more users/teams that are project members -- And one or more users/teams that may update the project from SCM, from among the users/teams that are members of that organization. - -Users who are members of a project can view their project administrators. - -Project administrators implicitly inherit the capability to update the project from SCM. - -Administrators can also specify one or more users/teams (from those that are members of that project) that can use that project in a job template. - - -Creating Inventories and Credentials within an Organization -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -All access that is granted to use, read, or write credentials is handled through roles, which use AWX's RBAC system to grant ownership, auditor, or usage roles. - -System administrators and organization administrators may create inventories and credentials within organizations under their administrative capabilities. - -Whether editing an inventory or a credential, System administrators and organization administrators may specify one or more users/teams (from those that are members of that organization) to be granted the usage capability for that inventory or credential. - -System administrators and organization administrators may specify one or more users/teams (from those that are members of that organization) that have the capabilities to update (dynamic or manually) an inventory. Administrators can also execute ad hoc commands for an inventory. - - -Editing Job Templates -^^^^^^^^^^^^^^^^^^^^^^ - -System administrators, organization administrators, and project administrators, within a project under their administrative capabilities, may create and modify new job templates for that project. - -When editing a job template, administrators (AWX, organization, and project) can select among the inventory and credentials in the organization for which they have usage capabilities or they may leave those fields blank so that they will be selected at runtime. - -Additionally, they may specify one or more users/teams (from those that are members of that project) that have execution capabilities for that job template. The execution capability is valid regardless of any explicit capabilities the user/team may have been granted against the inventory or credential specified in the job template. - -User View -^^^^^^^^^^^^^ - -A user can: - -- See any organization or project for which they are a member -- Create their own credential objects which only belong to them -- See and execute any job template for which they have been granted execution capabilities - -If a job template that a user has been granted execution capabilities on does not specify an inventory or credential, the user will be prompted at run-time to select among the inventory and credentials in the organization they own or have been granted usage capabilities. - -Users that are job template administrators can make changes to job templates; however, to change to the inventory, project, playbook, credentials, or instance groups used in the job template, the user must also have the "Use" role for the project and inventory currently being used or being set. - -.. _rbac-ug-roles: - -Roles -~~~~~~~~~~~~~ - -All access that is granted to use, read, or write credentials is handled through roles, and roles are defined for a resource. - - -Built-in roles -^^^^^^^^^^^^^^ - -The following table lists the RBAC system roles and a brief description of the how that role is defined with regard to privileges in AWX. - -+-----------------------------------------------------------------------+------------------------------------------------------------------------------------------+ -| System Role | What it can do | -+=======================================================================+==========================================================================================+ -| System Administrator - System wide singleton | Manages all aspects of the system | -+-----------------------------------------------------------------------+------------------------------------------------------------------------------------------+ -| System Auditor - System wide singleton | Views all aspects of the system | -+-----------------------------------------------------------------------+------------------------------------------------------------------------------------------+ -| Ad Hoc Role - Inventory | Runs ad hoc commands on an Inventory | -+-----------------------------------------------------------------------+------------------------------------------------------------------------------------------+ -| Admin Role - Organizations, Teams, Inventory, Projects, Job Templates | Manages all aspects of a defined Organization, Team, Inventory, Project, or Job Template | -+-----------------------------------------------------------------------+------------------------------------------------------------------------------------------+ -| Auditor Role - All | Views all aspects of a defined Organization, Team, Inventory, Project, or Job Template | -+-----------------------------------------------------------------------+------------------------------------------------------------------------------------------+ -| Execute Role - Job Templates | Runs assigned Job Template | -+-----------------------------------------------------------------------+------------------------------------------------------------------------------------------+ -| Member Role - Organization, Team | User is a member of a defined Organization or Team | -+-----------------------------------------------------------------------+------------------------------------------------------------------------------------------+ -| Read Role - Organizations, Teams, Inventory, Projects, Job Templates | Views all aspects of a defined Organization, Team, Inventory, Project, or Job Template | -+-----------------------------------------------------------------------+------------------------------------------------------------------------------------------+ -| Update Role - Project | Updates the Project from the configured source control management system | -+-----------------------------------------------------------------------+------------------------------------------------------------------------------------------+ -| Update Role - Inventory | Updates the Inventory using the cloud source update system | -+-----------------------------------------------------------------------+------------------------------------------------------------------------------------------+ -| Owner Role - Credential | Owns and manages all aspects of this Credential | -+-----------------------------------------------------------------------+------------------------------------------------------------------------------------------+ -| Use Role - Credential, Inventory, Project, IGs, CGs | Uses the Credential, Inventory, Project, IGs, or CGs in a Job Template | -+-----------------------------------------------------------------------+------------------------------------------------------------------------------------------+ - - -A Singleton Role is a special role that grants system-wide permissions. AWX currently provides two built-in Singleton Roles but the ability to create or customize a Singleton Role is not supported at this time. - -Common Team Roles - "Personas" -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -Support personnel typically works on ensuring that AWX is available and manages it a way to balance supportability and ease-of-use for users. Often, support will assign “Organization Owner/Admin” to users in order to allow them to create a new Organization and add members from their team the respective access needed. This minimizes supporting individuals and focuses more on maintaining uptime of the service and assisting users who are using AWX. - -Below are some common roles managed by the AWX Organization: - -+-----------------------+------------------------+-----------------------------------------------------------------------------------------------------------+ -| | System Role | | Common User | | Description | -| | (for Organizations) | | Roles | | -+-----------------------+------------------------+-----------------------------------------------------------------------------------------------------------+ -| | Owner | | Team Lead - | | This user has the ability to control access for other users in their organization. | -| | | Technical Lead | | They can add/remove and grant users specific access to projects, inventories, and job templates. | -| | | | This user also has the ability to create/remove/modify any aspect of an organization’s projects, | -| | | | templates, inventories, teams, and credentials. | -+-----------------------+------------------------+-----------------------------------------------------------------------------------------------------------+ -| | Auditor | | Security Engineer - | | This account can view all aspects of the organization in read-only mode. | -| | | Project Manager | | This may be good for a user who checks in and maintains compliance. | -| | | | This might also be a good role for a service account who manages or | -| | | | ships job data from AWX to some other data collector. | -+-----------------------+------------------------+-----------------------------------------------------------------------------------------------------------+ -| | Member - | | All other users | | These users by default as an organization member do not receive any access to any aspect | -| | Team | | | of the organization. In order to grant them access the respective organization owner needs | -| | | | to add them to their respective team and grant them Admin, Execute, Use, Update, Ad-hoc | -| | | | permissions to each component of the organization’s projects, inventories, and job templates. | -+-----------------------+------------------------+-----------------------------------------------------------------------------------------------------------+ -| | Member - | | Power users - | | Organization Owners can provide “admin” through the team interface, over any component | -| | Team “Owner” | | Lead Developer | | of their organization including projects, inventories, and job templates. These users are able | -| | | | to modify and utilize the respective component given access. | -+-----------------------+------------------------+-----------------------------------------------------------------------------------------------------------+ -| | Member - | | Developers - | | This will be the most common and allows the organization member the ability to execute | -| | Team “Execute” | | Engineers | | job templates and read permission to the specific components. This is permission applies to templates. | -+-----------------------+------------------------+-----------------------------------------------------------------------------------------------------------+ -| | Member - | | Developers - | | This permission applies to an organization’s credentials, inventories, and projects. | -| | Team “Use” | | Engineers | | This permission allows the ability for a user to use the respective component within their job template.| -+-----------------------+------------------------+-----------------------------------------------------------------------------------------------------------+ -| | Member - | | Developers - | | This permission applies to projects. Allows the user to be able to run an SCM update on a project. | -| | Team “Update” | | Engineers | | -+-----------------------+------------------------+-----------------------------------------------------------------------------------------------------------+ - - -Function of roles: editing and creating ------------------------------------------- - -Organization “resource roles” functionality are specific to a certain resource type - such as workflows. Being a member of such a role usually provides two types of permissions, in the case of workflows, where a user is given a "workflow admin role" for the organization "Default": - -- this user can create new workflows in the organization "Default" -- user can edit all workflows in the "Default" organization - -One exception is job templates, where having the role is irrelevant of creation permission (more details on its own section). - -Independence of resource roles and organization membership roles -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Resource-specific organization roles are independent of the organization roles of admin and member. Having the "workflow admin role" for the "Default" organization will not allow a user to view all users in the organization, but having a "member" role in the "Default" organization will. The two types of roles are delegated independently of each other. - - -Necessary permissions to edit job templates -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -Users can edit fields not impacting job runs (non-sensitive fields) with a Job Template admin role alone. However, to edit fields that impact job runs in a job template, a user needs the following: - -- **admin** role to the job template and container groups -- **use** role to related project -- **use** role to related inventory -- **use** role to related instance groups - -An "organization job template admin" role was introduced, but having this role isn't sufficient by itself to edit a job template within the organization if the user does not have use role to the project / inventory / instance group or an admin role to the container group that a job template uses. - -In order to delegate *full* job template control (within an organization) to a user or team, you will need grant the team or user all 3 organization-level roles: - -- job template admin -- project admin -- inventory admin - -This will ensure that the user (or all users who are members of the team with these roles) have full access to modify job templates in the organization. If a job template uses an inventory or project from another organization, the user with these organization roles may still not have permission to modify that job template. For clarity of managing permissions, it is best-practice to not mix projects / inventories from different organizations. - -RBAC permissions -^^^^^^^^^^^^^^^^^^^ - -Each role should have a content object, for instance, the org admin role has a content object of the org. To delegate a role, you need admin permission to the content object, with some exceptions that would result in you being able to reset a user's password. - -**Parent** is the organization. - -**Allow** is what this new permission will explicitly allow. - -**Scope** is the parent resource that this new role will be created on. Example: ``Organization.project_create_role``. - -An assumption is being made that the creator of the resource should be given the admin role for that resource. If there are any instances where resource creation does not also imply resource administration, they will be explicitly called out. - -Here are the rules associated with each admin type: - -**Project Admin** - -- Allow: Create, read, update, delete any project -- Scope: Organization -- User Interface: *Project Add Screen - Organizations* - -**Inventory Admin** - -- Parent: Org admin -- Allow: Create, read, update, delete any inventory -- Scope: Organization -- User Interface: *Inventory Add Screen - Organizations* - -.. note:: - - As it is with the **Use** role, if you give a user Project Admin and Inventory Admin, it allows them to create Job Templates (not workflows) for your organization. - -**Credential Admin** - -- Parent: Org admin -- Allow: Create, read, update, delete shared credentials -- Scope: Organization -- User Interface: *Credential Add Screen - Organizations* - -**Notification Admin** - -- Parent: Org admin -- Allow: Assignment of notifications -- Scope: Organization - -**Workflow Admin** - -- Parent: Org admin -- Allow: Create a workflow -- Scope: Organization - -**Org Execute** - -- Parent: Org admin -- Allow: Executing JTs and WFJTs -- Scope: Organization - - -The following is a sample scenario showing an organization with its roles and which resource(s) each have access to: - -.. image:: ../common/images/rbac-multiple-resources-scenario.png +.. include:: ../common/isolation_variables.rst \ No newline at end of file From fc9064e27f33f5f52db967c4a95f0ce4ab232f80 Mon Sep 17 00:00:00 2001 From: Hao Liu <44379968+TheRealHaoLiu@users.noreply.github.com> Date: Mon, 20 May 2024 19:34:12 -0400 Subject: [PATCH 02/75] Allow wsrelay to fail without FATAL (#15191) We have not identify the root cause of wsrelay failure but attempt to make wsrelay restart itself resulted in postgres and redis connection leak. We were not able to fully identify where the redis connection leak comes from so reverting back to failing and removing startsecs 30 will prevent wsrelay to FATAL --- awx/main/management/commands/run_wsrelay.py | 32 ++--- awx/main/wsrelay.py | 114 +++++++----------- .../templates/supervisor_task.conf.j2 | 1 - 3 files changed, 60 insertions(+), 87 deletions(-) diff --git a/awx/main/management/commands/run_wsrelay.py b/awx/main/management/commands/run_wsrelay.py index ee7cbca6825e..a3016ffc9296 100644 --- a/awx/main/management/commands/run_wsrelay.py +++ b/awx/main/management/commands/run_wsrelay.py @@ -101,8 +101,9 @@ def handle(self, *arg, **options): migrating = bool(executor.migration_plan(executor.loader.graph.leaf_nodes())) connection.close() # Because of async nature, main loop will use new connection, so close this except Exception as exc: - logger.warning(f'Error on startup of run_wsrelay (error: {exc}), retry in 10s...') - time.sleep(10) + time.sleep(10) # Prevent supervisor from restarting the service too quickly and the service to enter FATAL state + # sleeping before logging because logging rely on setting which require database connection... + logger.warning(f'Error on startup of run_wsrelay (error: {exc}), slept for 10s...') return # In containerized deployments, migrations happen in the task container, @@ -121,13 +122,14 @@ def handle(self, *arg, **options): return try: - my_hostname = Instance.objects.my_hostname() + my_hostname = Instance.objects.my_hostname() # This relies on settings.CLUSTER_HOST_ID which requires database connection logger.info('Active instance with hostname {} is registered.'.format(my_hostname)) except RuntimeError as e: # the CLUSTER_HOST_ID in the task, and web instance must match and # ensure network connectivity between the task and web instance - logger.info('Unable to return currently active instance: {}, retry in 5s...'.format(e)) - time.sleep(5) + time.sleep(10) # Prevent supervisor from restarting the service too quickly and the service to enter FATAL state + # sleeping before logging because logging rely on setting which require database connection... + logger.warning(f"Unable to return currently active instance: {e}, slept for 10s before return.") return if options.get('status'): @@ -166,12 +168,14 @@ def handle(self, *arg, **options): WebsocketsMetricsServer().start() - while True: - try: - asyncio.run(WebSocketRelayManager().run()) - except KeyboardInterrupt: - logger.info('Shutting down Websocket Relayer') - break - except Exception as e: - logger.exception('Error in Websocket Relayer, exception: {}. Restarting in 10 seconds'.format(e)) - time.sleep(10) + try: + logger.info('Starting Websocket Relayer...') + websocket_relay_manager = WebSocketRelayManager() + asyncio.run(websocket_relay_manager.run()) + except KeyboardInterrupt: + logger.info('Terminating Websocket Relayer') + except BaseException as e: # BaseException is used to catch all exceptions including asyncio.CancelledError + time.sleep(10) # Prevent supervisor from restarting the service too quickly and the service to enter FATAL state + # sleeping before logging because logging rely on setting which require database connection... + logger.warning(f"Encounter error while running Websocket Relayer {e}, slept for 10s...") + return diff --git a/awx/main/wsrelay.py b/awx/main/wsrelay.py index a4c94bd7e60b..bedf68efb030 100644 --- a/awx/main/wsrelay.py +++ b/awx/main/wsrelay.py @@ -285,8 +285,6 @@ async def cleanup_offline_host(self, hostname): except asyncio.CancelledError: # Handle the case where the task was already cancelled by the time we got here. pass - except Exception as e: - logger.warning(f"Failed to cancel relay connection for {hostname}: {e}") del self.relay_connections[hostname] @@ -297,8 +295,6 @@ async def cleanup_offline_host(self, hostname): self.stats_mgr.delete_remote_host_stats(hostname) except KeyError: pass - except Exception as e: - logger.warning(f"Failed to delete stats for {hostname}: {e}") async def run(self): event_loop = asyncio.get_running_loop() @@ -306,7 +302,6 @@ async def run(self): self.stats_mgr = RelayWebsocketStatsManager(event_loop, self.local_hostname) self.stats_mgr.start() - # Set up a pg_notify consumer for allowing web nodes to "provision" and "deprovision" themselves gracefully. database_conf = deepcopy(settings.DATABASES['default']) database_conf['OPTIONS'] = deepcopy(database_conf.get('OPTIONS', {})) @@ -318,79 +313,54 @@ async def run(self): if 'PASSWORD' in database_conf: database_conf['OPTIONS']['password'] = database_conf.pop('PASSWORD') - task = None + async_conn = await psycopg.AsyncConnection.connect( + dbname=database_conf['NAME'], + host=database_conf['HOST'], + user=database_conf['USER'], + port=database_conf['PORT'], + **database_conf.get("OPTIONS", {}), + ) - # Managing the async_conn here so that we can close it if we need to restart the connection - async_conn = None + await async_conn.set_autocommit(True) + on_ws_heartbeat_task = event_loop.create_task(self.on_ws_heartbeat(async_conn)) # Establishes a websocket connection to /websocket/relay on all API servers - try: - while True: - if not task or task.done(): - try: - # Try to close the connection if it's open - if async_conn: - try: - await async_conn.close() - except Exception as e: - logger.warning(f"Failed to close connection to database for pg_notify: {e}") - - # and re-establish the connection - async_conn = await psycopg.AsyncConnection.connect( - dbname=database_conf['NAME'], - host=database_conf['HOST'], - user=database_conf['USER'], - port=database_conf['PORT'], - **database_conf.get("OPTIONS", {}), - ) - await async_conn.set_autocommit(True) - - # before creating the task that uses the connection - task = event_loop.create_task(self.on_ws_heartbeat(async_conn), name="on_ws_heartbeat") - logger.info("Creating `on_ws_heartbeat` task in event loop.") - - except Exception as e: - logger.warning(f"Failed to connect to database for pg_notify: {e}") - - future_remote_hosts = self.known_hosts.keys() - current_remote_hosts = self.relay_connections.keys() - deleted_remote_hosts = set(current_remote_hosts) - set(future_remote_hosts) - new_remote_hosts = set(future_remote_hosts) - set(current_remote_hosts) - - # This loop handles if we get an advertisement from a host we already know about but - # the advertisement has a different IP than we are currently connected to. - for hostname, address in self.known_hosts.items(): - if hostname not in self.relay_connections: - # We've picked up a new hostname that we don't know about yet. - continue + while True: + if on_ws_heartbeat_task.done(): + raise Exception("on_ws_heartbeat_task has exited") + + future_remote_hosts = self.known_hosts.keys() + current_remote_hosts = self.relay_connections.keys() + deleted_remote_hosts = set(current_remote_hosts) - set(future_remote_hosts) + new_remote_hosts = set(future_remote_hosts) - set(current_remote_hosts) + + # This loop handles if we get an advertisement from a host we already know about but + # the advertisement has a different IP than we are currently connected to. + for hostname, address in self.known_hosts.items(): + if hostname not in self.relay_connections: + # We've picked up a new hostname that we don't know about yet. + continue - if address != self.relay_connections[hostname].remote_host: - deleted_remote_hosts.add(hostname) - new_remote_hosts.add(hostname) + if address != self.relay_connections[hostname].remote_host: + deleted_remote_hosts.add(hostname) + new_remote_hosts.add(hostname) - # Delete any hosts with closed connections - for hostname, relay_conn in self.relay_connections.items(): - if not relay_conn.connected: - deleted_remote_hosts.add(hostname) + # Delete any hosts with closed connections + for hostname, relay_conn in self.relay_connections.items(): + if not relay_conn.connected: + deleted_remote_hosts.add(hostname) - if deleted_remote_hosts: - logger.info(f"Removing {deleted_remote_hosts} from websocket broadcast list") - await asyncio.gather(*[self.cleanup_offline_host(h) for h in deleted_remote_hosts]) + if deleted_remote_hosts: + logger.info(f"Removing {deleted_remote_hosts} from websocket broadcast list") + await asyncio.gather(*[self.cleanup_offline_host(h) for h in deleted_remote_hosts]) - if new_remote_hosts: - logger.info(f"Adding {new_remote_hosts} to websocket broadcast list") + if new_remote_hosts: + logger.info(f"Adding {new_remote_hosts} to websocket broadcast list") - for h in new_remote_hosts: - stats = self.stats_mgr.new_remote_host_stats(h) - relay_connection = WebsocketRelayConnection(name=self.local_hostname, stats=stats, remote_host=self.known_hosts[h]) - relay_connection.start() - self.relay_connections[h] = relay_connection + for h in new_remote_hosts: + stats = self.stats_mgr.new_remote_host_stats(h) + relay_connection = WebsocketRelayConnection(name=self.local_hostname, stats=stats, remote_host=self.known_hosts[h]) + relay_connection.start() + self.relay_connections[h] = relay_connection - await asyncio.sleep(settings.BROADCAST_WEBSOCKET_NEW_INSTANCE_POLL_RATE_SECONDS) - finally: - if async_conn: - logger.info("Shutting down db connection for wsrelay.") - try: - await async_conn.close() - except Exception as e: - logger.info(f"Failed to close connection to database for pg_notify: {e}") + await asyncio.sleep(settings.BROADCAST_WEBSOCKET_NEW_INSTANCE_POLL_RATE_SECONDS) diff --git a/tools/ansible/roles/dockerfile/templates/supervisor_task.conf.j2 b/tools/ansible/roles/dockerfile/templates/supervisor_task.conf.j2 index 90b6313635b2..b102a50977b1 100644 --- a/tools/ansible/roles/dockerfile/templates/supervisor_task.conf.j2 +++ b/tools/ansible/roles/dockerfile/templates/supervisor_task.conf.j2 @@ -31,7 +31,6 @@ command = awx-manage run_wsrelay directory = /var/lib/awx {% endif %} autorestart = true -startsecs = 30 stopasgroup=true killasgroup=true stdout_logfile=/dev/stdout From 8de8f6dce2e31cc0b72c3ea5dfc627a81051d879 Mon Sep 17 00:00:00 2001 From: Alan Rominger Date: Mon, 20 May 2024 19:37:02 -0400 Subject: [PATCH 03/75] Update a few dev requirements (#15203) * Update a few dev requirements * Fix test failures due to upgrade * Update patterns for mocker usage --- awx/conf/tests/unit/test_settings.py | 42 ++--- .../api/test_create_attach_views.py | 12 +- .../functional/api/test_job_runtime_params.py | 160 +++++++++--------- .../functional/api/test_rbac_displays.py | 20 +-- .../functional/commands/test_commands.py | 12 +- .../models/test_context_managers.py | 12 +- .../task_management/test_rampart_groups.py | 18 +- .../task_management/test_scheduler.py | 6 +- .../test_job_template_serializers.py | 20 +-- .../serializers/test_workflow_serializers.py | 4 +- awx/main/tests/unit/api/test_generics.py | 14 +- awx/main/tests/unit/api/test_views.py | 19 ++- .../tests/unit/models/test_workflow_unit.py | 52 +++--- awx/main/tests/unit/test_tasks.py | 8 +- awx/main/tests/unit/utils/test_reload.py | 24 +-- awx/sso/tests/unit/test_ldap.py | 14 +- requirements/requirements_dev.txt | 6 +- 17 files changed, 222 insertions(+), 221 deletions(-) diff --git a/awx/conf/tests/unit/test_settings.py b/awx/conf/tests/unit/test_settings.py index 1d5d721680e4..1910a136f32f 100644 --- a/awx/conf/tests/unit/test_settings.py +++ b/awx/conf/tests/unit/test_settings.py @@ -130,9 +130,9 @@ def test_default_setting(settings, mocker): settings.registry.register('AWX_SOME_SETTING', field_class=fields.CharField, category=_('System'), category_slug='system', default='DEFAULT') settings_to_cache = mocker.Mock(**{'order_by.return_value': []}) - with mocker.patch('awx.conf.models.Setting.objects.filter', return_value=settings_to_cache): - assert settings.AWX_SOME_SETTING == 'DEFAULT' - assert settings.cache.get('AWX_SOME_SETTING') == 'DEFAULT' + mocker.patch('awx.conf.models.Setting.objects.filter', return_value=settings_to_cache) + assert settings.AWX_SOME_SETTING == 'DEFAULT' + assert settings.cache.get('AWX_SOME_SETTING') == 'DEFAULT' @pytest.mark.defined_in_file(AWX_SOME_SETTING='DEFAULT') @@ -146,9 +146,9 @@ def test_setting_is_not_from_setting_file(settings, mocker): settings.registry.register('AWX_SOME_SETTING', field_class=fields.CharField, category=_('System'), category_slug='system', default='DEFAULT') settings_to_cache = mocker.Mock(**{'order_by.return_value': []}) - with mocker.patch('awx.conf.models.Setting.objects.filter', return_value=settings_to_cache): - assert settings.AWX_SOME_SETTING == 'DEFAULT' - assert settings.registry.get_setting_field('AWX_SOME_SETTING').defined_in_file is False + mocker.patch('awx.conf.models.Setting.objects.filter', return_value=settings_to_cache) + assert settings.AWX_SOME_SETTING == 'DEFAULT' + assert settings.registry.get_setting_field('AWX_SOME_SETTING').defined_in_file is False def test_empty_setting(settings, mocker): @@ -156,10 +156,10 @@ def test_empty_setting(settings, mocker): settings.registry.register('AWX_SOME_SETTING', field_class=fields.CharField, category=_('System'), category_slug='system') mocks = mocker.Mock(**{'order_by.return_value': mocker.Mock(**{'__iter__': lambda self: iter([]), 'first.return_value': None})}) - with mocker.patch('awx.conf.models.Setting.objects.filter', return_value=mocks): - with pytest.raises(AttributeError): - settings.AWX_SOME_SETTING - assert settings.cache.get('AWX_SOME_SETTING') == SETTING_CACHE_NOTSET + mocker.patch('awx.conf.models.Setting.objects.filter', return_value=mocks) + with pytest.raises(AttributeError): + settings.AWX_SOME_SETTING + assert settings.cache.get('AWX_SOME_SETTING') == SETTING_CACHE_NOTSET def test_setting_from_db(settings, mocker): @@ -168,9 +168,9 @@ def test_setting_from_db(settings, mocker): setting_from_db = mocker.Mock(key='AWX_SOME_SETTING', value='FROM_DB') mocks = mocker.Mock(**{'order_by.return_value': mocker.Mock(**{'__iter__': lambda self: iter([setting_from_db]), 'first.return_value': setting_from_db})}) - with mocker.patch('awx.conf.models.Setting.objects.filter', return_value=mocks): - assert settings.AWX_SOME_SETTING == 'FROM_DB' - assert settings.cache.get('AWX_SOME_SETTING') == 'FROM_DB' + mocker.patch('awx.conf.models.Setting.objects.filter', return_value=mocks) + assert settings.AWX_SOME_SETTING == 'FROM_DB' + assert settings.cache.get('AWX_SOME_SETTING') == 'FROM_DB' @pytest.mark.defined_in_file(AWX_SOME_SETTING='DEFAULT') @@ -205,8 +205,8 @@ def test_db_setting_update(settings, mocker): existing_setting = mocker.Mock(key='AWX_SOME_SETTING', value='FROM_DB') setting_list = mocker.Mock(**{'order_by.return_value.first.return_value': existing_setting}) - with mocker.patch('awx.conf.models.Setting.objects.filter', return_value=setting_list): - settings.AWX_SOME_SETTING = 'NEW-VALUE' + mocker.patch('awx.conf.models.Setting.objects.filter', return_value=setting_list) + settings.AWX_SOME_SETTING = 'NEW-VALUE' assert existing_setting.value == 'NEW-VALUE' existing_setting.save.assert_called_with(update_fields=['value']) @@ -217,8 +217,8 @@ def test_db_setting_deletion(settings, mocker): settings.registry.register('AWX_SOME_SETTING', field_class=fields.CharField, category=_('System'), category_slug='system') existing_setting = mocker.Mock(key='AWX_SOME_SETTING', value='FROM_DB') - with mocker.patch('awx.conf.models.Setting.objects.filter', return_value=[existing_setting]): - del settings.AWX_SOME_SETTING + mocker.patch('awx.conf.models.Setting.objects.filter', return_value=[existing_setting]) + del settings.AWX_SOME_SETTING assert existing_setting.delete.call_count == 1 @@ -283,10 +283,10 @@ def rot13(obj, attribute): # use its primary key as part of the encryption key setting_from_db = mocker.Mock(pk=123, key='AWX_ENCRYPTED', value='SECRET!') mocks = mocker.Mock(**{'order_by.return_value': mocker.Mock(**{'__iter__': lambda self: iter([setting_from_db]), 'first.return_value': setting_from_db})}) - with mocker.patch('awx.conf.models.Setting.objects.filter', return_value=mocks): - cache.set('AWX_ENCRYPTED', 'SECRET!') - assert cache.get('AWX_ENCRYPTED') == 'SECRET!' - assert native_cache.get('AWX_ENCRYPTED') == 'FRPERG!' + mocker.patch('awx.conf.models.Setting.objects.filter', return_value=mocks) + cache.set('AWX_ENCRYPTED', 'SECRET!') + assert cache.get('AWX_ENCRYPTED') == 'SECRET!' + assert native_cache.get('AWX_ENCRYPTED') == 'FRPERG!' def test_readonly_sensitive_cache_data_is_encrypted(settings): diff --git a/awx/main/tests/functional/api/test_create_attach_views.py b/awx/main/tests/functional/api/test_create_attach_views.py index b22ec089122f..7b92f82f5076 100644 --- a/awx/main/tests/functional/api/test_create_attach_views.py +++ b/awx/main/tests/functional/api/test_create_attach_views.py @@ -9,8 +9,8 @@ def test_user_role_view_access(rando, inventory, mocker, post): role_pk = inventory.admin_role.pk data = {"id": role_pk} mock_access = mocker.MagicMock(can_attach=mocker.MagicMock(return_value=False)) - with mocker.patch('awx.main.access.RoleAccess', return_value=mock_access): - post(url=reverse('api:user_roles_list', kwargs={'pk': rando.pk}), data=data, user=rando, expect=403) + mocker.patch('awx.main.access.RoleAccess', return_value=mock_access) + post(url=reverse('api:user_roles_list', kwargs={'pk': rando.pk}), data=data, user=rando, expect=403) mock_access.can_attach.assert_called_once_with(inventory.admin_role, rando, 'members', data, skip_sub_obj_read_check=False) @@ -21,8 +21,8 @@ def test_team_role_view_access(rando, team, inventory, mocker, post): role_pk = inventory.admin_role.pk data = {"id": role_pk} mock_access = mocker.MagicMock(can_attach=mocker.MagicMock(return_value=False)) - with mocker.patch('awx.main.access.RoleAccess', return_value=mock_access): - post(url=reverse('api:team_roles_list', kwargs={'pk': team.pk}), data=data, user=rando, expect=403) + mocker.patch('awx.main.access.RoleAccess', return_value=mock_access) + post(url=reverse('api:team_roles_list', kwargs={'pk': team.pk}), data=data, user=rando, expect=403) mock_access.can_attach.assert_called_once_with(inventory.admin_role, team, 'member_role.parents', data, skip_sub_obj_read_check=False) @@ -33,8 +33,8 @@ def test_role_team_view_access(rando, team, inventory, mocker, post): role_pk = inventory.admin_role.pk data = {"id": team.pk} mock_access = mocker.MagicMock(return_value=False, __name__='mocked') - with mocker.patch('awx.main.access.RoleAccess.can_attach', mock_access): - post(url=reverse('api:role_teams_list', kwargs={'pk': role_pk}), data=data, user=rando, expect=403) + mocker.patch('awx.main.access.RoleAccess.can_attach', mock_access) + post(url=reverse('api:role_teams_list', kwargs={'pk': role_pk}), data=data, user=rando, expect=403) mock_access.assert_called_once_with(inventory.admin_role, team, 'member_role.parents', data, skip_sub_obj_read_check=False) diff --git a/awx/main/tests/functional/api/test_job_runtime_params.py b/awx/main/tests/functional/api/test_job_runtime_params.py index f477a66ed945..f893b53c66f0 100644 --- a/awx/main/tests/functional/api/test_job_runtime_params.py +++ b/awx/main/tests/functional/api/test_job_runtime_params.py @@ -131,11 +131,11 @@ def test_job_ignore_unprompted_vars(runtime_data, job_template_prompts, post, ad mock_job = mocker.MagicMock(spec=Job, id=968, **runtime_data) - with mocker.patch.object(JobTemplate, 'create_unified_job', return_value=mock_job): - with mocker.patch('awx.api.serializers.JobSerializer.to_representation'): - response = post(reverse('api:job_template_launch', kwargs={'pk': job_template.pk}), runtime_data, admin_user, expect=201) - assert JobTemplate.create_unified_job.called - assert JobTemplate.create_unified_job.call_args == () + mocker.patch.object(JobTemplate, 'create_unified_job', return_value=mock_job) + mocker.patch('awx.api.serializers.JobSerializer.to_representation') + response = post(reverse('api:job_template_launch', kwargs={'pk': job_template.pk}), runtime_data, admin_user, expect=201) + assert JobTemplate.create_unified_job.called + assert JobTemplate.create_unified_job.call_args == () # Check that job is serialized correctly job_id = response.data['job'] @@ -167,12 +167,12 @@ def test_job_accept_prompted_vars(runtime_data, job_template_prompts, post, admi mock_job = mocker.MagicMock(spec=Job, id=968, **runtime_data) - with mocker.patch.object(JobTemplate, 'create_unified_job', return_value=mock_job): - with mocker.patch('awx.api.serializers.JobSerializer.to_representation'): - response = post(reverse('api:job_template_launch', kwargs={'pk': job_template.pk}), runtime_data, admin_user, expect=201) - assert JobTemplate.create_unified_job.called - called_with = data_to_internal(runtime_data) - JobTemplate.create_unified_job.assert_called_with(**called_with) + mocker.patch.object(JobTemplate, 'create_unified_job', return_value=mock_job) + mocker.patch('awx.api.serializers.JobSerializer.to_representation') + response = post(reverse('api:job_template_launch', kwargs={'pk': job_template.pk}), runtime_data, admin_user, expect=201) + assert JobTemplate.create_unified_job.called + called_with = data_to_internal(runtime_data) + JobTemplate.create_unified_job.assert_called_with(**called_with) job_id = response.data['job'] assert job_id == 968 @@ -187,11 +187,11 @@ def test_job_accept_empty_tags(job_template_prompts, post, admin_user, mocker): mock_job = mocker.MagicMock(spec=Job, id=968) - with mocker.patch.object(JobTemplate, 'create_unified_job', return_value=mock_job): - with mocker.patch('awx.api.serializers.JobSerializer.to_representation'): - post(reverse('api:job_template_launch', kwargs={'pk': job_template.pk}), {'job_tags': '', 'skip_tags': ''}, admin_user, expect=201) - assert JobTemplate.create_unified_job.called - assert JobTemplate.create_unified_job.call_args == ({'job_tags': '', 'skip_tags': ''},) + mocker.patch.object(JobTemplate, 'create_unified_job', return_value=mock_job) + mocker.patch('awx.api.serializers.JobSerializer.to_representation') + post(reverse('api:job_template_launch', kwargs={'pk': job_template.pk}), {'job_tags': '', 'skip_tags': ''}, admin_user, expect=201) + assert JobTemplate.create_unified_job.called + assert JobTemplate.create_unified_job.call_args == ({'job_tags': '', 'skip_tags': ''},) mock_job.signal_start.assert_called_once() @@ -203,14 +203,14 @@ def test_slice_timeout_forks_need_int(job_template_prompts, post, admin_user, mo mock_job = mocker.MagicMock(spec=Job, id=968) - with mocker.patch.object(JobTemplate, 'create_unified_job', return_value=mock_job): - with mocker.patch('awx.api.serializers.JobSerializer.to_representation'): - response = post( - reverse('api:job_template_launch', kwargs={'pk': job_template.pk}), {'timeout': '', 'job_slice_count': '', 'forks': ''}, admin_user, expect=400 - ) - assert 'forks' in response.data and response.data['forks'][0] == 'A valid integer is required.' - assert 'job_slice_count' in response.data and response.data['job_slice_count'][0] == 'A valid integer is required.' - assert 'timeout' in response.data and response.data['timeout'][0] == 'A valid integer is required.' + mocker.patch.object(JobTemplate, 'create_unified_job', return_value=mock_job) + mocker.patch('awx.api.serializers.JobSerializer.to_representation') + response = post( + reverse('api:job_template_launch', kwargs={'pk': job_template.pk}), {'timeout': '', 'job_slice_count': '', 'forks': ''}, admin_user, expect=400 + ) + assert 'forks' in response.data and response.data['forks'][0] == 'A valid integer is required.' + assert 'job_slice_count' in response.data and response.data['job_slice_count'][0] == 'A valid integer is required.' + assert 'timeout' in response.data and response.data['timeout'][0] == 'A valid integer is required.' @pytest.mark.django_db @@ -244,12 +244,12 @@ def test_job_accept_prompted_vars_null(runtime_data, job_template_prompts_null, mock_job = mocker.MagicMock(spec=Job, id=968, **runtime_data) - with mocker.patch.object(JobTemplate, 'create_unified_job', return_value=mock_job): - with mocker.patch('awx.api.serializers.JobSerializer.to_representation'): - response = post(reverse('api:job_template_launch', kwargs={'pk': job_template.pk}), runtime_data, rando, expect=201) - assert JobTemplate.create_unified_job.called - expected_call = data_to_internal(runtime_data) - assert JobTemplate.create_unified_job.call_args == (expected_call,) + mocker.patch.object(JobTemplate, 'create_unified_job', return_value=mock_job) + mocker.patch('awx.api.serializers.JobSerializer.to_representation') + response = post(reverse('api:job_template_launch', kwargs={'pk': job_template.pk}), runtime_data, rando, expect=201) + assert JobTemplate.create_unified_job.called + expected_call = data_to_internal(runtime_data) + assert JobTemplate.create_unified_job.call_args == (expected_call,) job_id = response.data['job'] assert job_id == 968 @@ -641,18 +641,18 @@ def test_job_launch_unprompted_vars_with_survey(mocker, survey_spec_factory, job job_template.survey_spec = survey_spec_factory('survey_var') job_template.save() - with mocker.patch('awx.main.access.BaseAccess.check_license'): - mock_job = mocker.MagicMock(spec=Job, id=968, extra_vars={"job_launch_var": 3, "survey_var": 4}) - with mocker.patch.object(JobTemplate, 'create_unified_job', return_value=mock_job): - with mocker.patch('awx.api.serializers.JobSerializer.to_representation', return_value={}): - response = post( - reverse('api:job_template_launch', kwargs={'pk': job_template.pk}), - dict(extra_vars={"job_launch_var": 3, "survey_var": 4}), - admin_user, - expect=201, - ) - assert JobTemplate.create_unified_job.called - assert JobTemplate.create_unified_job.call_args == ({'extra_vars': {'survey_var': 4}},) + mocker.patch('awx.main.access.BaseAccess.check_license') + mock_job = mocker.MagicMock(spec=Job, id=968, extra_vars={"job_launch_var": 3, "survey_var": 4}) + mocker.patch.object(JobTemplate, 'create_unified_job', return_value=mock_job) + mocker.patch('awx.api.serializers.JobSerializer.to_representation', return_value={}) + response = post( + reverse('api:job_template_launch', kwargs={'pk': job_template.pk}), + dict(extra_vars={"job_launch_var": 3, "survey_var": 4}), + admin_user, + expect=201, + ) + assert JobTemplate.create_unified_job.called + assert JobTemplate.create_unified_job.call_args == ({'extra_vars': {'survey_var': 4}},) job_id = response.data['job'] assert job_id == 968 @@ -670,22 +670,22 @@ def test_callback_accept_prompted_extra_var(mocker, survey_spec_factory, job_tem job_template.survey_spec = survey_spec_factory('survey_var') job_template.save() - with mocker.patch('awx.main.access.BaseAccess.check_license'): - mock_job = mocker.MagicMock(spec=Job, id=968, extra_vars={"job_launch_var": 3, "survey_var": 4}) - with mocker.patch.object(UnifiedJobTemplate, 'create_unified_job', return_value=mock_job): - with mocker.patch('awx.api.serializers.JobSerializer.to_representation', return_value={}): - with mocker.patch('awx.api.views.JobTemplateCallback.find_matching_hosts', return_value=[host]): - post( - reverse('api:job_template_callback', kwargs={'pk': job_template.pk}), - dict(extra_vars={"job_launch_var": 3, "survey_var": 4}, host_config_key="foo"), - admin_user, - expect=201, - format='json', - ) - assert UnifiedJobTemplate.create_unified_job.called - call_args = UnifiedJobTemplate.create_unified_job.call_args[1] - call_args.pop('_eager_fields', None) # internal purposes - assert call_args == {'extra_vars': {'survey_var': 4, 'job_launch_var': 3}, 'limit': 'single-host'} + mocker.patch('awx.main.access.BaseAccess.check_license') + mock_job = mocker.MagicMock(spec=Job, id=968, extra_vars={"job_launch_var": 3, "survey_var": 4}) + mocker.patch.object(UnifiedJobTemplate, 'create_unified_job', return_value=mock_job) + mocker.patch('awx.api.serializers.JobSerializer.to_representation', return_value={}) + mocker.patch('awx.api.views.JobTemplateCallback.find_matching_hosts', return_value=[host]) + post( + reverse('api:job_template_callback', kwargs={'pk': job_template.pk}), + dict(extra_vars={"job_launch_var": 3, "survey_var": 4}, host_config_key="foo"), + admin_user, + expect=201, + format='json', + ) + assert UnifiedJobTemplate.create_unified_job.called + call_args = UnifiedJobTemplate.create_unified_job.call_args[1] + call_args.pop('_eager_fields', None) # internal purposes + assert call_args == {'extra_vars': {'survey_var': 4, 'job_launch_var': 3}, 'limit': 'single-host'} mock_job.signal_start.assert_called_once() @@ -697,22 +697,22 @@ def test_callback_ignore_unprompted_extra_var(mocker, survey_spec_factory, job_t job_template.host_config_key = "foo" job_template.save() - with mocker.patch('awx.main.access.BaseAccess.check_license'): - mock_job = mocker.MagicMock(spec=Job, id=968, extra_vars={"job_launch_var": 3, "survey_var": 4}) - with mocker.patch.object(UnifiedJobTemplate, 'create_unified_job', return_value=mock_job): - with mocker.patch('awx.api.serializers.JobSerializer.to_representation', return_value={}): - with mocker.patch('awx.api.views.JobTemplateCallback.find_matching_hosts', return_value=[host]): - post( - reverse('api:job_template_callback', kwargs={'pk': job_template.pk}), - dict(extra_vars={"job_launch_var": 3, "survey_var": 4}, host_config_key="foo"), - admin_user, - expect=201, - format='json', - ) - assert UnifiedJobTemplate.create_unified_job.called - call_args = UnifiedJobTemplate.create_unified_job.call_args[1] - call_args.pop('_eager_fields', None) # internal purposes - assert call_args == {'limit': 'single-host'} + mocker.patch('awx.main.access.BaseAccess.check_license') + mock_job = mocker.MagicMock(spec=Job, id=968, extra_vars={"job_launch_var": 3, "survey_var": 4}) + mocker.patch.object(UnifiedJobTemplate, 'create_unified_job', return_value=mock_job) + mocker.patch('awx.api.serializers.JobSerializer.to_representation', return_value={}) + mocker.patch('awx.api.views.JobTemplateCallback.find_matching_hosts', return_value=[host]) + post( + reverse('api:job_template_callback', kwargs={'pk': job_template.pk}), + dict(extra_vars={"job_launch_var": 3, "survey_var": 4}, host_config_key="foo"), + admin_user, + expect=201, + format='json', + ) + assert UnifiedJobTemplate.create_unified_job.called + call_args = UnifiedJobTemplate.create_unified_job.call_args[1] + call_args.pop('_eager_fields', None) # internal purposes + assert call_args == {'limit': 'single-host'} mock_job.signal_start.assert_called_once() @@ -725,9 +725,9 @@ def test_callback_find_matching_hosts(mocker, get, job_template_prompts, admin_u job_template.save() host_with_alias = Host(name='localhost', inventory=job_template.inventory) host_with_alias.save() - with mocker.patch('awx.main.access.BaseAccess.check_license'): - r = get(reverse('api:job_template_callback', kwargs={'pk': job_template.pk}), user=admin_user, expect=200) - assert tuple(r.data['matching_hosts']) == ('localhost',) + mocker.patch('awx.main.access.BaseAccess.check_license') + r = get(reverse('api:job_template_callback', kwargs={'pk': job_template.pk}), user=admin_user, expect=200) + assert tuple(r.data['matching_hosts']) == ('localhost',) @pytest.mark.django_db @@ -738,6 +738,6 @@ def test_callback_extra_var_takes_priority_over_host_name(mocker, get, job_templ job_template.save() host_with_alias = Host(name='localhost', variables={'ansible_host': 'foobar'}, inventory=job_template.inventory) host_with_alias.save() - with mocker.patch('awx.main.access.BaseAccess.check_license'): - r = get(reverse('api:job_template_callback', kwargs={'pk': job_template.pk}), user=admin_user, expect=200) - assert not r.data['matching_hosts'] + mocker.patch('awx.main.access.BaseAccess.check_license') + r = get(reverse('api:job_template_callback', kwargs={'pk': job_template.pk}), user=admin_user, expect=200) + assert not r.data['matching_hosts'] diff --git a/awx/main/tests/functional/api/test_rbac_displays.py b/awx/main/tests/functional/api/test_rbac_displays.py index 8178da672c05..22d9ec9990a9 100644 --- a/awx/main/tests/functional/api/test_rbac_displays.py +++ b/awx/main/tests/functional/api/test_rbac_displays.py @@ -165,8 +165,8 @@ def _assert_one_in_list(self, data, sublist='direct_access'): def test_access_list_direct_access_capability(self, inventory, rando, get, mocker, mock_access_method): inventory.admin_role.members.add(rando) - with mocker.patch.object(access_registry[Role], 'can_unattach', mock_access_method): - response = get(reverse('api:inventory_access_list', kwargs={'pk': inventory.id}), rando) + mocker.patch.object(access_registry[Role], 'can_unattach', mock_access_method) + response = get(reverse('api:inventory_access_list', kwargs={'pk': inventory.id}), rando) mock_access_method.assert_called_once_with(inventory.admin_role, rando, 'members', **self.extra_kwargs) self._assert_one_in_list(response.data) @@ -174,8 +174,8 @@ def test_access_list_direct_access_capability(self, inventory, rando, get, mocke assert direct_access_list[0]['role']['user_capabilities']['unattach'] == 'foobar' def test_access_list_indirect_access_capability(self, inventory, organization, org_admin, get, mocker, mock_access_method): - with mocker.patch.object(access_registry[Role], 'can_unattach', mock_access_method): - response = get(reverse('api:inventory_access_list', kwargs={'pk': inventory.id}), org_admin) + mocker.patch.object(access_registry[Role], 'can_unattach', mock_access_method) + response = get(reverse('api:inventory_access_list', kwargs={'pk': inventory.id}), org_admin) mock_access_method.assert_called_once_with(organization.admin_role, org_admin, 'members', **self.extra_kwargs) self._assert_one_in_list(response.data, sublist='indirect_access') @@ -185,8 +185,8 @@ def test_access_list_indirect_access_capability(self, inventory, organization, o def test_access_list_team_direct_access_capability(self, inventory, team, team_member, get, mocker, mock_access_method): team.member_role.children.add(inventory.admin_role) - with mocker.patch.object(access_registry[Role], 'can_unattach', mock_access_method): - response = get(reverse('api:inventory_access_list', kwargs={'pk': inventory.id}), team_member) + mocker.patch.object(access_registry[Role], 'can_unattach', mock_access_method) + response = get(reverse('api:inventory_access_list', kwargs={'pk': inventory.id}), team_member) mock_access_method.assert_called_once_with(inventory.admin_role, team.member_role, 'parents', **self.extra_kwargs) self._assert_one_in_list(response.data) @@ -198,8 +198,8 @@ def test_access_list_team_direct_access_capability(self, inventory, team, team_m def test_team_roles_unattach(mocker, team, team_member, inventory, mock_access_method, get): team.member_role.children.add(inventory.admin_role) - with mocker.patch.object(access_registry[Role], 'can_unattach', mock_access_method): - response = get(reverse('api:team_roles_list', kwargs={'pk': team.id}), team_member) + mocker.patch.object(access_registry[Role], 'can_unattach', mock_access_method) + response = get(reverse('api:team_roles_list', kwargs={'pk': team.id}), team_member) # Did we assess whether team_member can remove team's permission to the inventory? mock_access_method.assert_called_once_with(inventory.admin_role, team.member_role, 'parents', skip_sub_obj_read_check=True, data={}) @@ -212,8 +212,8 @@ def test_user_roles_unattach(mocker, organization, alice, bob, mock_access_metho organization.member_role.members.add(alice) organization.member_role.members.add(bob) - with mocker.patch.object(access_registry[Role], 'can_unattach', mock_access_method): - response = get(reverse('api:user_roles_list', kwargs={'pk': alice.id}), bob) + mocker.patch.object(access_registry[Role], 'can_unattach', mock_access_method) + response = get(reverse('api:user_roles_list', kwargs={'pk': alice.id}), bob) # Did we assess whether bob can remove alice's permission to the inventory? mock_access_method.assert_called_once_with(organization.member_role, alice, 'members', skip_sub_obj_read_check=True, data={}) diff --git a/awx/main/tests/functional/commands/test_commands.py b/awx/main/tests/functional/commands/test_commands.py index 69f584c2871b..f62dac02064d 100644 --- a/awx/main/tests/functional/commands/test_commands.py +++ b/awx/main/tests/functional/commands/test_commands.py @@ -43,9 +43,9 @@ def run_command(name, *args, **options): ], ) def test_update_password_command(mocker, username, password, expected, changed): - with mocker.patch.object(UpdatePassword, 'update_password', return_value=changed): - result, stdout, stderr = run_command('update_password', username=username, password=password) - if result is None: - assert stdout == expected - else: - assert str(result) == expected + mocker.patch.object(UpdatePassword, 'update_password', return_value=changed) + result, stdout, stderr = run_command('update_password', username=username, password=password) + if result is None: + assert stdout == expected + else: + assert str(result) == expected diff --git a/awx/main/tests/functional/models/test_context_managers.py b/awx/main/tests/functional/models/test_context_managers.py index 271f88b21f57..7dfc0da11729 100644 --- a/awx/main/tests/functional/models/test_context_managers.py +++ b/awx/main/tests/functional/models/test_context_managers.py @@ -21,13 +21,13 @@ class TestComputedFields: def test_computed_fields_normal_use(self, mocker, inventory): job = Job.objects.create(name='fake-job', inventory=inventory) with immediate_on_commit(): - with mocker.patch.object(update_inventory_computed_fields, 'delay'): - job.delete() - update_inventory_computed_fields.delay.assert_called_once_with(inventory.id) + mocker.patch.object(update_inventory_computed_fields, 'delay') + job.delete() + update_inventory_computed_fields.delay.assert_called_once_with(inventory.id) def test_disable_computed_fields(self, mocker, inventory): job = Job.objects.create(name='fake-job', inventory=inventory) with disable_computed_fields(): - with mocker.patch.object(update_inventory_computed_fields, 'delay'): - job.delete() - update_inventory_computed_fields.delay.assert_not_called() + mocker.patch.object(update_inventory_computed_fields, 'delay') + job.delete() + update_inventory_computed_fields.delay.assert_not_called() diff --git a/awx/main/tests/functional/task_management/test_rampart_groups.py b/awx/main/tests/functional/task_management/test_rampart_groups.py index 48ea9edb0805..f4bb81f405a6 100644 --- a/awx/main/tests/functional/task_management/test_rampart_groups.py +++ b/awx/main/tests/functional/task_management/test_rampart_groups.py @@ -21,13 +21,13 @@ def test_multi_group_basic_job_launch(instance_factory, controlplane_instance_gr j2 = create_job(objects2.job_template) with mock.patch('awx.main.models.Job.task_impact', new_callable=mock.PropertyMock) as mock_task_impact: mock_task_impact.return_value = 500 - with mocker.patch("awx.main.scheduler.TaskManager.start_task"): - TaskManager().schedule() - TaskManager.start_task.assert_has_calls([mock.call(j1, ig1, i1), mock.call(j2, ig2, i2)]) + mocker.patch("awx.main.scheduler.TaskManager.start_task") + TaskManager().schedule() + TaskManager.start_task.assert_has_calls([mock.call(j1, ig1, i1), mock.call(j2, ig2, i2)]) @pytest.mark.django_db -def test_multi_group_with_shared_dependency(instance_factory, controlplane_instance_group, mocker, instance_group_factory, job_template_factory): +def test_multi_group_with_shared_dependency(instance_factory, controlplane_instance_group, instance_group_factory, job_template_factory): i1 = instance_factory("i1") i2 = instance_factory("i2") ig1 = instance_group_factory("ig1", instances=[i1]) @@ -50,7 +50,7 @@ def test_multi_group_with_shared_dependency(instance_factory, controlplane_insta objects2 = job_template_factory('jt2', organization=objects1.organization, project=p, inventory='inv2', credential='cred2') objects2.job_template.instance_groups.add(ig2) j2 = create_job(objects2.job_template, dependencies_processed=False) - with mocker.patch("awx.main.scheduler.TaskManager.start_task"): + with mock.patch("awx.main.scheduler.TaskManager.start_task"): DependencyManager().schedule() TaskManager().schedule() pu = p.project_updates.first() @@ -73,10 +73,10 @@ def test_workflow_job_no_instancegroup(workflow_job_template_factory, controlpla wfj = wfjt.create_unified_job() wfj.status = "pending" wfj.save() - with mocker.patch("awx.main.scheduler.TaskManager.start_task"): - TaskManager().schedule() - TaskManager.start_task.assert_called_once_with(wfj, None, None) - assert wfj.instance_group is None + mocker.patch("awx.main.scheduler.TaskManager.start_task") + TaskManager().schedule() + TaskManager.start_task.assert_called_once_with(wfj, None, None) + assert wfj.instance_group is None @pytest.mark.django_db diff --git a/awx/main/tests/functional/task_management/test_scheduler.py b/awx/main/tests/functional/task_management/test_scheduler.py index 743609c760b4..32651311d83f 100644 --- a/awx/main/tests/functional/task_management/test_scheduler.py +++ b/awx/main/tests/functional/task_management/test_scheduler.py @@ -16,9 +16,9 @@ def test_single_job_scheduler_launch(hybrid_instance, controlplane_instance_grou instance = controlplane_instance_group.instances.all()[0] objects = job_template_factory('jt', organization='org1', project='proj', inventory='inv', credential='cred') j = create_job(objects.job_template) - with mocker.patch("awx.main.scheduler.TaskManager.start_task"): - TaskManager().schedule() - TaskManager.start_task.assert_called_once_with(j, controlplane_instance_group, instance) + mocker.patch("awx.main.scheduler.TaskManager.start_task") + TaskManager().schedule() + TaskManager.start_task.assert_called_once_with(j, controlplane_instance_group, instance) @pytest.mark.django_db diff --git a/awx/main/tests/unit/api/serializers/test_job_template_serializers.py b/awx/main/tests/unit/api/serializers/test_job_template_serializers.py index 51e64fd753a1..0a9e31b91cbd 100644 --- a/awx/main/tests/unit/api/serializers/test_job_template_serializers.py +++ b/awx/main/tests/unit/api/serializers/test_job_template_serializers.py @@ -76,15 +76,15 @@ def test_callback_absent(self, get_related_mock_and_run, job_template): class TestJobTemplateSerializerGetSummaryFields: def test_survey_spec_exists(self, test_get_summary_fields, mocker, job_template): job_template.survey_spec = {'name': 'blah', 'description': 'blah blah'} - with mocker.patch.object(JobTemplateSerializer, '_recent_jobs') as mock_rj: - mock_rj.return_value = [] - test_get_summary_fields(JobTemplateSerializer, job_template, 'survey') + mock_rj = mocker.patch.object(JobTemplateSerializer, '_recent_jobs') + mock_rj.return_value = [] + test_get_summary_fields(JobTemplateSerializer, job_template, 'survey') def test_survey_spec_absent(self, get_summary_fields_mock_and_run, mocker, job_template): job_template.survey_spec = None - with mocker.patch.object(JobTemplateSerializer, '_recent_jobs') as mock_rj: - mock_rj.return_value = [] - summary = get_summary_fields_mock_and_run(JobTemplateSerializer, job_template) + mock_rj = mocker.patch.object(JobTemplateSerializer, '_recent_jobs') + mock_rj.return_value = [] + summary = get_summary_fields_mock_and_run(JobTemplateSerializer, job_template) assert 'survey' not in summary def test_copy_edit_standard(self, mocker, job_template_factory): @@ -107,10 +107,10 @@ def test_copy_edit_standard(self, mocker, job_template_factory): view.kwargs = {} serializer.context['view'] = view - with mocker.patch("awx.api.serializers.role_summary_fields_generator", return_value='Can eat pie'): - with mocker.patch("awx.main.access.JobTemplateAccess.can_change", return_value='foobar'): - with mocker.patch("awx.main.access.JobTemplateAccess.can_copy", return_value='foo'): - response = serializer.get_summary_fields(jt_obj) + mocker.patch("awx.api.serializers.role_summary_fields_generator", return_value='Can eat pie') + mocker.patch("awx.main.access.JobTemplateAccess.can_change", return_value='foobar') + mocker.patch("awx.main.access.JobTemplateAccess.can_copy", return_value='foo') + response = serializer.get_summary_fields(jt_obj) assert response['user_capabilities']['copy'] == 'foo' assert response['user_capabilities']['edit'] == 'foobar' diff --git a/awx/main/tests/unit/api/serializers/test_workflow_serializers.py b/awx/main/tests/unit/api/serializers/test_workflow_serializers.py index 9e7fe51344e0..218395dc6c62 100644 --- a/awx/main/tests/unit/api/serializers/test_workflow_serializers.py +++ b/awx/main/tests/unit/api/serializers/test_workflow_serializers.py @@ -189,8 +189,8 @@ def test_use_db_answer(self, jt, mocker): serializer = WorkflowJobTemplateNodeSerializer() wfjt = WorkflowJobTemplate.objects.create(name='fake-wfjt') serializer.instance = WorkflowJobTemplateNode(workflow_job_template=wfjt, unified_job_template=jt, extra_data={'var1': '$encrypted$foooooo'}) - with mocker.patch('awx.main.models.mixins.decrypt_value', return_value='foo'): - attrs = serializer.validate({'unified_job_template': jt, 'workflow_job_template': wfjt, 'extra_data': {'var1': '$encrypted$'}}) + mocker.patch('awx.main.models.mixins.decrypt_value', return_value='foo') + attrs = serializer.validate({'unified_job_template': jt, 'workflow_job_template': wfjt, 'extra_data': {'var1': '$encrypted$'}}) assert 'survey_passwords' in attrs assert 'var1' in attrs['survey_passwords'] assert attrs['extra_data']['var1'] == '$encrypted$foooooo' diff --git a/awx/main/tests/unit/api/test_generics.py b/awx/main/tests/unit/api/test_generics.py index 6f0982bfd847..ea8cb388786c 100644 --- a/awx/main/tests/unit/api/test_generics.py +++ b/awx/main/tests/unit/api/test_generics.py @@ -191,16 +191,16 @@ def mock_view(self, parent=None): def test_parent_access_check_failed(self, mocker, mock_organization): mock_access = mocker.MagicMock(__name__='for logger', return_value=False) - with mocker.patch('awx.main.access.BaseAccess.can_read', mock_access): - with pytest.raises(PermissionDenied): - self.mock_view(parent=mock_organization).check_permissions(self.mock_request()) - mock_access.assert_called_once_with(mock_organization) + mocker.patch('awx.main.access.BaseAccess.can_read', mock_access) + with pytest.raises(PermissionDenied): + self.mock_view(parent=mock_organization).check_permissions(self.mock_request()) + mock_access.assert_called_once_with(mock_organization) def test_parent_access_check_worked(self, mocker, mock_organization): mock_access = mocker.MagicMock(__name__='for logger', return_value=True) - with mocker.patch('awx.main.access.BaseAccess.can_read', mock_access): - self.mock_view(parent=mock_organization).check_permissions(self.mock_request()) - mock_access.assert_called_once_with(mock_organization) + mocker.patch('awx.main.access.BaseAccess.can_read', mock_access) + self.mock_view(parent=mock_organization).check_permissions(self.mock_request()) + mock_access.assert_called_once_with(mock_organization) def test_related_search_reverse_FK_field(): diff --git a/awx/main/tests/unit/api/test_views.py b/awx/main/tests/unit/api/test_views.py index edb60351d0f8..503ad6e854dd 100644 --- a/awx/main/tests/unit/api/test_views.py +++ b/awx/main/tests/unit/api/test_views.py @@ -66,7 +66,7 @@ def test_inherited_mixin_unattach(self): mock_request = mock.MagicMock() super(JobTemplateLabelList, view).unattach(mock_request, None, None) - assert mixin_unattach.called_with(mock_request, None, None) + mixin_unattach.assert_called_with(mock_request, None, None) class TestInventoryInventorySourcesUpdate: @@ -108,15 +108,16 @@ def exclude(self, **kwargs): mock_request = mocker.MagicMock() mock_request.user.can_access.return_value = can_access - with mocker.patch.object(InventoryInventorySourcesUpdate, 'get_object', return_value=obj): - with mocker.patch.object(InventoryInventorySourcesUpdate, 'get_serializer_context', return_value=None): - with mocker.patch('awx.api.serializers.InventoryUpdateDetailSerializer') as serializer_class: - serializer = serializer_class.return_value - serializer.to_representation.return_value = {} + mocker.patch.object(InventoryInventorySourcesUpdate, 'get_object', return_value=obj) + mocker.patch.object(InventoryInventorySourcesUpdate, 'get_serializer_context', return_value=None) + serializer_class = mocker.patch('awx.api.serializers.InventoryUpdateDetailSerializer') - view = InventoryInventorySourcesUpdate() - response = view.post(mock_request) - assert response.data == expected + serializer = serializer_class.return_value + serializer.to_representation.return_value = {} + + view = InventoryInventorySourcesUpdate() + response = view.post(mock_request) + assert response.data == expected class TestSurveySpecValidation: diff --git a/awx/main/tests/unit/models/test_workflow_unit.py b/awx/main/tests/unit/models/test_workflow_unit.py index dc01c3301f6d..7ac2009403de 100644 --- a/awx/main/tests/unit/models/test_workflow_unit.py +++ b/awx/main/tests/unit/models/test_workflow_unit.py @@ -155,35 +155,35 @@ def test_node_getter_and_setters(): class TestWorkflowJobCreate: def test_create_no_prompts(self, wfjt_node_no_prompts, workflow_job_unit, mocker): mock_create = mocker.MagicMock() - with mocker.patch('awx.main.models.WorkflowJobNode.objects.create', mock_create): - wfjt_node_no_prompts.create_workflow_job_node(workflow_job=workflow_job_unit) - mock_create.assert_called_once_with( - all_parents_must_converge=False, - extra_data={}, - survey_passwords={}, - char_prompts=wfjt_node_no_prompts.char_prompts, - inventory=None, - unified_job_template=wfjt_node_no_prompts.unified_job_template, - workflow_job=workflow_job_unit, - identifier=mocker.ANY, - execution_environment=None, - ) + mocker.patch('awx.main.models.WorkflowJobNode.objects.create', mock_create) + wfjt_node_no_prompts.create_workflow_job_node(workflow_job=workflow_job_unit) + mock_create.assert_called_once_with( + all_parents_must_converge=False, + extra_data={}, + survey_passwords={}, + char_prompts=wfjt_node_no_prompts.char_prompts, + inventory=None, + unified_job_template=wfjt_node_no_prompts.unified_job_template, + workflow_job=workflow_job_unit, + identifier=mocker.ANY, + execution_environment=None, + ) def test_create_with_prompts(self, wfjt_node_with_prompts, workflow_job_unit, credential, mocker): mock_create = mocker.MagicMock() - with mocker.patch('awx.main.models.WorkflowJobNode.objects.create', mock_create): - wfjt_node_with_prompts.create_workflow_job_node(workflow_job=workflow_job_unit) - mock_create.assert_called_once_with( - all_parents_must_converge=False, - extra_data={}, - survey_passwords={}, - char_prompts=wfjt_node_with_prompts.char_prompts, - inventory=wfjt_node_with_prompts.inventory, - unified_job_template=wfjt_node_with_prompts.unified_job_template, - workflow_job=workflow_job_unit, - identifier=mocker.ANY, - execution_environment=None, - ) + mocker.patch('awx.main.models.WorkflowJobNode.objects.create', mock_create) + wfjt_node_with_prompts.create_workflow_job_node(workflow_job=workflow_job_unit) + mock_create.assert_called_once_with( + all_parents_must_converge=False, + extra_data={}, + survey_passwords={}, + char_prompts=wfjt_node_with_prompts.char_prompts, + inventory=wfjt_node_with_prompts.inventory, + unified_job_template=wfjt_node_with_prompts.unified_job_template, + workflow_job=workflow_job_unit, + identifier=mocker.ANY, + execution_environment=None, + ) @pytest.mark.django_db diff --git a/awx/main/tests/unit/test_tasks.py b/awx/main/tests/unit/test_tasks.py index e5b8fbf79e6c..10ed00b186a9 100644 --- a/awx/main/tests/unit/test_tasks.py +++ b/awx/main/tests/unit/test_tasks.py @@ -137,10 +137,10 @@ def test_send_notifications_not_list(): def test_send_notifications_job_id(mocker): - with mocker.patch('awx.main.models.UnifiedJob.objects.get'): - system.send_notifications([], job_id=1) - assert UnifiedJob.objects.get.called - assert UnifiedJob.objects.get.called_with(id=1) + mocker.patch('awx.main.models.UnifiedJob.objects.get') + system.send_notifications([], job_id=1) + assert UnifiedJob.objects.get.called + assert UnifiedJob.objects.get.called_with(id=1) @mock.patch('awx.main.models.UnifiedJob.objects.get') diff --git a/awx/main/tests/unit/utils/test_reload.py b/awx/main/tests/unit/utils/test_reload.py index 5f8c7b95e35b..2b41a5fef0c1 100644 --- a/awx/main/tests/unit/utils/test_reload.py +++ b/awx/main/tests/unit/utils/test_reload.py @@ -7,15 +7,15 @@ def test_produce_supervisor_command(mocker): mock_process = mocker.MagicMock() mock_process.communicate = communicate_mock Popen_mock = mocker.MagicMock(return_value=mock_process) - with mocker.patch.object(reload.subprocess, 'Popen', Popen_mock): - reload.supervisor_service_command("restart") - reload.subprocess.Popen.assert_called_once_with( - [ - 'supervisorctl', - 'restart', - 'tower-processes:*', - ], - stderr=-1, - stdin=-1, - stdout=-1, - ) + mocker.patch.object(reload.subprocess, 'Popen', Popen_mock) + reload.supervisor_service_command("restart") + reload.subprocess.Popen.assert_called_once_with( + [ + 'supervisorctl', + 'restart', + 'tower-processes:*', + ], + stderr=-1, + stdin=-1, + stdout=-1, + ) diff --git a/awx/sso/tests/unit/test_ldap.py b/awx/sso/tests/unit/test_ldap.py index 300102934f79..aa54aaa49dbe 100644 --- a/awx/sso/tests/unit/test_ldap.py +++ b/awx/sso/tests/unit/test_ldap.py @@ -7,18 +7,18 @@ def test_ldap_default_settings(mocker): from_db = mocker.Mock(**{'order_by.return_value': []}) - with mocker.patch('awx.conf.models.Setting.objects.filter', return_value=from_db): - settings = LDAPSettings() - assert settings.ORGANIZATION_MAP == {} - assert settings.TEAM_MAP == {} + mocker.patch('awx.conf.models.Setting.objects.filter', return_value=from_db) + settings = LDAPSettings() + assert settings.ORGANIZATION_MAP == {} + assert settings.TEAM_MAP == {} def test_ldap_default_network_timeout(mocker): cache.clear() # clearing cache avoids picking up stray default for OPT_REFERRALS from_db = mocker.Mock(**{'order_by.return_value': []}) - with mocker.patch('awx.conf.models.Setting.objects.filter', return_value=from_db): - settings = LDAPSettings() - assert settings.CONNECTION_OPTIONS[ldap.OPT_NETWORK_TIMEOUT] == 30 + mocker.patch('awx.conf.models.Setting.objects.filter', return_value=from_db) + settings = LDAPSettings() + assert settings.CONNECTION_OPTIONS[ldap.OPT_NETWORK_TIMEOUT] == 30 def test_ldap_filter_validator(): diff --git a/requirements/requirements_dev.txt b/requirements/requirements_dev.txt index 4d087803fda0..15f662fa8ef5 100644 --- a/requirements/requirements_dev.txt +++ b/requirements/requirements_dev.txt @@ -11,9 +11,9 @@ pytest!=7.0.0 pytest-asyncio pytest-cov pytest-django -pytest-mock==1.11.1 +pytest-mock pytest-timeout -pytest-xdist==1.34.0 # 2.0.0 broke zuul for some reason +pytest-xdist tox # for awxkit logutils jupyter @@ -21,7 +21,7 @@ jupyter backports.tempfile # support in unit tests for py32+ tempfile.TemporaryDirectory git+https://github.com/artefactual-labs/mockldap.git@master#egg=mockldap gprof2dot -atomicwrites==1.4.0 +atomicwrites flake8 yamllint pip>=21.3 # PEP 660 – Editable installs for pyproject.toml based builds (wheel based) From 0d4f6537944b7260e59ad04919290b84cb5a27b2 Mon Sep 17 00:00:00 2001 From: Seth Foster Date: Tue, 21 May 2024 15:05:59 -0400 Subject: [PATCH 04/75] Fix up ansible-test sanity checks due to ansible 2.17 release (#15208) * Fix up ansible sanity checks * Fix awx-collection test failure * Add ignore for ansible-test 2.17 --------- Signed-off-by: Seth Foster Co-authored-by: Hao Liu <44379968+TheRealHaoLiu@users.noreply.github.com> --- awx_collection/plugins/module_utils/controller_api.py | 6 +++--- awx_collection/plugins/modules/ad_hoc_command.py | 2 +- awx_collection/plugins/modules/import.py | 2 +- awx_collection/test/awx/conftest.py | 2 +- awx_collection/tests/sanity/ignore-2.17.txt | 1 + 5 files changed, 7 insertions(+), 6 deletions(-) create mode 100644 awx_collection/tests/sanity/ignore-2.17.txt diff --git a/awx_collection/plugins/module_utils/controller_api.py b/awx_collection/plugins/module_utils/controller_api.py index 0f48fc2dff1c..758fcd198664 100644 --- a/awx_collection/plugins/module_utils/controller_api.py +++ b/awx_collection/plugins/module_utils/controller_api.py @@ -107,7 +107,7 @@ def __init__(self, argument_spec=None, direct_params=None, error_callback=None, # Perform magic depending on whether controller_oauthtoken is a string or a dict if self.params.get('controller_oauthtoken'): token_param = self.params.get('controller_oauthtoken') - if type(token_param) is dict: + if isinstance(token_param, dict): if 'token' in token_param: self.oauth_token = self.params.get('controller_oauthtoken')['token'] else: @@ -215,7 +215,7 @@ def load_config(self, config_path): try: config_data = yaml.load(config_string, Loader=yaml.SafeLoader) # If this is an actual ini file, yaml will return the whole thing as a string instead of a dict - if type(config_data) is not dict: + if not isinstance(config_data, dict): raise AssertionError("The yaml config file is not properly formatted as a dict.") try_config_parsing = False @@ -257,7 +257,7 @@ def load_config(self, config_path): if honorred_setting in config_data: # Veriffy SSL must be a boolean if honorred_setting == 'verify_ssl': - if type(config_data[honorred_setting]) is str: + if isinstance(config_data[honorred_setting], str): setattr(self, honorred_setting, strtobool(config_data[honorred_setting])) else: setattr(self, honorred_setting, bool(config_data[honorred_setting])) diff --git a/awx_collection/plugins/modules/ad_hoc_command.py b/awx_collection/plugins/modules/ad_hoc_command.py index 5864d392a5c7..10d1c7e3520a 100644 --- a/awx_collection/plugins/modules/ad_hoc_command.py +++ b/awx_collection/plugins/modules/ad_hoc_command.py @@ -163,7 +163,7 @@ def main(): for arg in ['job_type', 'limit', 'forks', 'verbosity', 'extra_vars', 'become_enabled', 'diff_mode']: if module.params.get(arg): # extra_var can receive a dict or a string, if a dict covert it to a string - if arg == 'extra_vars' and type(module.params.get(arg)) is not str: + if arg == 'extra_vars' and not isinstance(module.params.get(arg), str): post_data[arg] = json.dumps(module.params.get(arg)) else: post_data[arg] = module.params.get(arg) diff --git a/awx_collection/plugins/modules/import.py b/awx_collection/plugins/modules/import.py index fe66b2a7a3c4..ae0180ccd173 100644 --- a/awx_collection/plugins/modules/import.py +++ b/awx_collection/plugins/modules/import.py @@ -56,7 +56,7 @@ # In this module we don't use EXPORTABLE_RESOURCES, we just want to validate that our installed awxkit has import/export try: - from awxkit.api.pages.api import EXPORTABLE_RESOURCES # noqa + from awxkit.api.pages.api import EXPORTABLE_RESOURCES # noqa: F401; pylint: disable=unused-import HAS_EXPORTABLE_RESOURCES = True except ImportError: diff --git a/awx_collection/test/awx/conftest.py b/awx_collection/test/awx/conftest.py index b7fb6333dd31..42500342ac9c 100644 --- a/awx_collection/test/awx/conftest.py +++ b/awx_collection/test/awx/conftest.py @@ -19,7 +19,7 @@ from ansible_base.rbac.models import RoleDefinition, DABPermission from awx.main.tests.functional.conftest import _request -from awx.main.tests.functional.conftest import credentialtype_scm, credentialtype_ssh # noqa: F401; pylint: disable=unused-variable +from awx.main.tests.functional.conftest import credentialtype_scm, credentialtype_ssh # noqa: F401; pylint: disable=unused-import from awx.main.models import ( Organization, Project, diff --git a/awx_collection/tests/sanity/ignore-2.17.txt b/awx_collection/tests/sanity/ignore-2.17.txt new file mode 100644 index 000000000000..19512ea0c162 --- /dev/null +++ b/awx_collection/tests/sanity/ignore-2.17.txt @@ -0,0 +1 @@ +plugins/modules/export.py validate-modules:nonexistent-parameter-documented # needs awxkit to construct argspec From 892410477aea1b289e67c95b418a79fcaa7f558a Mon Sep 17 00:00:00 2001 From: Hao Liu <44379968+TheRealHaoLiu@users.noreply.github.com> Date: Wed, 22 May 2024 14:58:11 -0400 Subject: [PATCH 05/75] Fix promote from release event (#15215) --- .github/workflows/promote.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/promote.yml b/.github/workflows/promote.yml index cbbd7d0270ac..76a3194724ae 100644 --- a/.github/workflows/promote.yml +++ b/.github/workflows/promote.yml @@ -29,7 +29,7 @@ jobs: - name: Set GitHub Env vars if release event if: ${{ github.event_name == 'release' }} run: | - echo "TAG_NAME=${{ env.TAG_NAME }}" >> $GITHUB_ENV + echo "TAG_NAME=${{ github.event.release.tag_name }}" >> $GITHUB_ENV - name: Checkout awx uses: actions/checkout@v3 From adf930ee426b122b4858ccc5bb567366123b5a05 Mon Sep 17 00:00:00 2001 From: Beni ~HB9HNT <11706262+hb9hnt@users.noreply.github.com> Date: Wed, 22 May 2024 21:27:31 +0200 Subject: [PATCH 06/75] awxkit: replace deprecated locale.format() with locale.format_string() to fix human output on Python 3.12 (#15170) Replace deprecated locale.format with locale.format_string This will be removed in Python 3.12 and will break human output unless fixed. --- awxkit/awxkit/cli/format.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/awxkit/awxkit/cli/format.py b/awxkit/awxkit/cli/format.py index e7629a4b6918..de2de4726226 100644 --- a/awxkit/awxkit/cli/format.py +++ b/awxkit/awxkit/cli/format.py @@ -185,7 +185,7 @@ def format_human(output, fmt): def format_num(v): try: - return locale.format("%.*f", (0, int(v)), True) + return locale.format_string("%.*f", (0, int(v)), True) except (ValueError, TypeError): if isinstance(v, (list, dict)): return json.dumps(v) From 66efe7198a1209a1fbcc748c8abeeab05e9be266 Mon Sep 17 00:00:00 2001 From: irozet12 <119814380+irozet12@users.noreply.github.com> Date: Wed, 22 May 2024 22:31:03 +0300 Subject: [PATCH 07/75] Wrap long line to fit help window (#14597) (#15169) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Wrap long line to fit description window (#14597) Co-authored-by: Ирина Розет --- awx/ui/src/components/Workflow/WorkflowHelp.js | 2 +- awx/ui/src/components/Workflow/WorkflowNodeHelp.js | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/awx/ui/src/components/Workflow/WorkflowHelp.js b/awx/ui/src/components/Workflow/WorkflowHelp.js index 4b2251a7cef4..7789dc5ca08c 100644 --- a/awx/ui/src/components/Workflow/WorkflowHelp.js +++ b/awx/ui/src/components/Workflow/WorkflowHelp.js @@ -12,7 +12,7 @@ const Inner = styled.div` border-radius: 2px; color: white; left: 10px; - max-width: 300px; + max-width: 500px; padding: 5px 10px; position: absolute; top: 10px; diff --git a/awx/ui/src/components/Workflow/WorkflowNodeHelp.js b/awx/ui/src/components/Workflow/WorkflowNodeHelp.js index 30b423df9e32..3d66bd10f960 100644 --- a/awx/ui/src/components/Workflow/WorkflowNodeHelp.js +++ b/awx/ui/src/components/Workflow/WorkflowNodeHelp.js @@ -12,6 +12,7 @@ const GridDL = styled.dl` column-gap: 15px; display: grid; grid-template-columns: max-content; + overflow-wrap: anywhere; row-gap: 0px; dt { grid-column-start: 1; From c3d9aa54d89b23dd23012fc2a21825f47ba87872 Mon Sep 17 00:00:00 2001 From: Alexander Pykavy Date: Wed, 22 May 2024 21:33:13 +0200 Subject: [PATCH 08/75] Mention in the docs that you can skip `make docker-compose-build` (#15149) Signed-off-by: Alexander Pykavy --- tools/docker-compose/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/docker-compose/README.md b/tools/docker-compose/README.md index 8ac287945ce9..7139281d7b71 100644 --- a/tools/docker-compose/README.md +++ b/tools/docker-compose/README.md @@ -22,7 +22,7 @@ Once you have a local copy, run the commands in the following sections from the Here are the main `make` targets: -- `docker-compose-build` - used for building the development image, which is used by the `docker-compose` target +- `docker-compose-build` - used for building the development image, which is used by the `docker-compose` target. You can skip this target if you want to use the latest [ghcr.io/ansible/awx_devel:devel](https://github.com/ansible/awx/pkgs/container/awx_devel) image rather than build a new one. - `docker-compose` - make target for development, passes awx_devel image and tag Notable files: From 208ef0ce256cf6d410a4f2ed58b5ec123a4deb72 Mon Sep 17 00:00:00 2001 From: Alan Rominger Date: Tue, 28 May 2024 11:53:01 -0400 Subject: [PATCH 09/75] Update test so that DAB change can merge (#15222) --- awx/main/tests/functional/test_rbac_api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/awx/main/tests/functional/test_rbac_api.py b/awx/main/tests/functional/test_rbac_api.py index e1e76e981e01..8eb26177a42f 100644 --- a/awx/main/tests/functional/test_rbac_api.py +++ b/awx/main/tests/functional/test_rbac_api.py @@ -187,7 +187,7 @@ def test_remove_role_from_user(role, post, admin): @pytest.mark.django_db -@override_settings(ANSIBLE_BASE_ALLOW_TEAM_ORG_ADMIN=True) +@override_settings(ANSIBLE_BASE_ALLOW_TEAM_ORG_ADMIN=True, ANSIBLE_BASE_ALLOW_TEAM_ORG_MEMBER=True) def test_get_teams_roles_list(get, team, organization, admin): team.member_role.children.add(organization.admin_role) url = reverse('api:team_roles_list', kwargs={'pk': team.id}) From 559ab3564b8962488ea592056d6e654a22e1f980 Mon Sep 17 00:00:00 2001 From: Matthew Jones Date: Tue, 28 May 2024 14:05:24 -0400 Subject: [PATCH 10/75] Include Kube credentials in the inventory source picker (#15223) --- awx/ui/src/components/Lookup/CredentialLookup.js | 4 ++-- .../Inventory/shared/InventorySourceSubForms/SCMSubForm.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/awx/ui/src/components/Lookup/CredentialLookup.js b/awx/ui/src/components/Lookup/CredentialLookup.js index 5256c20e6b44..f1da6f391a2d 100644 --- a/awx/ui/src/components/Lookup/CredentialLookup.js +++ b/awx/ui/src/components/Lookup/CredentialLookup.js @@ -62,7 +62,7 @@ function CredentialLookup({ ? { credential_type: credentialTypeId } : {}; const typeKindParams = credentialTypeKind - ? { credential_type__kind: credentialTypeKind } + ? { credential_type__kind__in: credentialTypeKind } : {}; const typeNamespaceParams = credentialTypeNamespace ? { credential_type__namespace: credentialTypeNamespace } @@ -125,7 +125,7 @@ function CredentialLookup({ ? { credential_type: credentialTypeId } : {}; const typeKindParams = credentialTypeKind - ? { credential_type__kind: credentialTypeKind } + ? { credential_type__kind__in: credentialTypeKind } : {}; const typeNamespaceParams = credentialTypeNamespace ? { credential_type__namespace: credentialTypeNamespace } diff --git a/awx/ui/src/screens/Inventory/shared/InventorySourceSubForms/SCMSubForm.js b/awx/ui/src/screens/Inventory/shared/InventorySourceSubForms/SCMSubForm.js index 043ddcebe7ac..5b772bf08a08 100644 --- a/awx/ui/src/screens/Inventory/shared/InventorySourceSubForms/SCMSubForm.js +++ b/awx/ui/src/screens/Inventory/shared/InventorySourceSubForms/SCMSubForm.js @@ -87,7 +87,7 @@ const SCMSubForm = ({ autoPopulateProject }) => { /> )} Date: Tue, 28 May 2024 15:27:34 -0400 Subject: [PATCH 11/75] Fix galaxy publishing (#15233) - switch to galaxy search API for determining if the version we want to publish already exist - switch from github action variable to env var for easier copy and paste testing --- .github/workflows/promote.yml | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/.github/workflows/promote.yml b/.github/workflows/promote.yml index 76a3194724ae..604485d32851 100644 --- a/.github/workflows/promote.yml +++ b/.github/workflows/promote.yml @@ -60,15 +60,18 @@ jobs: COLLECTION_VERSION: ${{ env.TAG_NAME }} COLLECTION_TEMPLATE_VERSION: true run: | + sudo apt-get install jq make build_collection - curl_with_redirects=$(curl --head -sLw '%{http_code}' https://galaxy.ansible.com/download/${{ env.collection_namespace }}-awx-${{ env.TAG_NAME }}.tar.gz | tail -1) - curl_without_redirects=$(curl --head -sw '%{http_code}' https://galaxy.ansible.com/download/${{ env.collection_namespace }}-awx-${{ env.TAG_NAME }}.tar.gz | tail -1) - if [[ "$curl_with_redirects" == "302" ]] || [[ "$curl_without_redirects" == "302" ]]; then + count=$(curl -s https://galaxy.ansible.com/api/v3/plugin/ansible/search/collection-versions/\?namespace\=${COLLECTION_NAMESPACE}\&name\=awx\&version\=${COLLECTION_VERSION} | jq .meta.count) + if [[ "$count" == "1" ]]; then echo "Galaxy release already done"; - else + elif [[ "$count" == "0" ]]; then ansible-galaxy collection publish \ --token=${{ secrets.GALAXY_TOKEN }} \ - awx_collection_build/${{ env.collection_namespace }}-awx-${{ env.TAG_NAME }}.tar.gz; + awx_collection_build/${COLLECTION_NAMESPACE}-awx-${COLLECTION_VERSION}.tar.gz; + else + echo "Unexpected count from galaxy search: $count"; + exit 1; fi - name: Set official pypi info From 776b661fb3f7eece02b70d2cf13c8274b44d93c6 Mon Sep 17 00:00:00 2001 From: Harshith u <44830597+Harshith-umesh@users.noreply.github.com> Date: Wed, 29 May 2024 11:54:05 -0400 Subject: [PATCH 12/75] use optional api prefix in collection if set as environ vairable (#15205) * use optional api prefix if set as environ variable * Different default depending on collection type --- .../plugins/module_utils/controller_api.py | 21 +++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/awx_collection/plugins/module_utils/controller_api.py b/awx_collection/plugins/module_utils/controller_api.py index 758fcd198664..541639306ac2 100644 --- a/awx_collection/plugins/module_utils/controller_api.py +++ b/awx_collection/plugins/module_utils/controller_api.py @@ -17,7 +17,7 @@ import re from json import loads, dumps from os.path import isfile, expanduser, split, join, exists, isdir -from os import access, R_OK, getcwd, environ +from os import access, R_OK, getcwd, environ, getenv try: @@ -148,9 +148,10 @@ def build_url(self, endpoint, query_params=None): # Make sure we start with /api/vX if not endpoint.startswith("/"): endpoint = "/{0}".format(endpoint) - prefix = self.url_prefix.rstrip("/") - if not endpoint.startswith(prefix + "/api/"): - endpoint = prefix + "/api/v2{0}".format(endpoint) + hostname_prefix = self.url_prefix.rstrip("/") + api_path = self.api_path() + if not endpoint.startswith(hostname_prefix + api_path): + endpoint = hostname_prefix + f"{api_path}v2{endpoint}" if not endpoint.endswith('/') and '?' not in endpoint: endpoint = "{0}/".format(endpoint) @@ -603,6 +604,14 @@ def make_request(self, method, endpoint, *args, **kwargs): status_code = response.status return {'status_code': status_code, 'json': response_json} + def api_path(self): + + default_api_path = "/api/" + if self._COLLECTION_TYPE != "awx": + default_api_path = "/api/controller/" + prefix = getenv('CONTROLLER_OPTIONAL_API_URLPATTERN_PREFIX', default_api_path) + return prefix + def authenticate(self, **kwargs): if self.username and self.password: # Attempt to get a token from /api/v2/tokens/ by giving it our username/password combo @@ -613,7 +622,7 @@ def authenticate(self, **kwargs): "scope": "write", } # Preserve URL prefix - endpoint = self.url_prefix.rstrip('/') + '/api/v2/tokens/' + endpoint = self.url_prefix.rstrip('/') + f'{self.api_path()}v2/tokens/' # Post to the tokens endpoint with baisc auth to try and get a token api_token_url = (self.url._replace(path=endpoint)).geturl() @@ -1002,7 +1011,7 @@ def logout(self): if self.authenticated and self.oauth_token_id: # Attempt to delete our current token from /api/v2/tokens/ # Post to the tokens endpoint with baisc auth to try and get a token - endpoint = self.url_prefix.rstrip('/') + '/api/v2/tokens/{0}/'.format(self.oauth_token_id) + endpoint = self.url_prefix.rstrip('/') + f'{self.api_path()}v2/tokens/{self.oauth_token_id}/' api_token_url = (self.url._replace(path=endpoint, query=None)).geturl() # in error cases, fail_json exists before exception handling try: From 08e1454098c9dd265930cd77f10fb52dcc56f93e Mon Sep 17 00:00:00 2001 From: Chris Meyers Date: Tue, 16 Apr 2024 14:24:01 -0400 Subject: [PATCH 13/75] Make named url work with optional url prefix * Handle named url sub-resources * i.e. /api/v2/inventories/my_inventory++Default/hosts/ --- awx/main/middleware.py | 38 ++++++++++--- awx/main/tests/functional/test_named_url.py | 62 +++++++++++++++++++++ 2 files changed, 92 insertions(+), 8 deletions(-) diff --git a/awx/main/middleware.py b/awx/main/middleware.py index d485ce45f7e3..433ade596fe4 100644 --- a/awx/main/middleware.py +++ b/awx/main/middleware.py @@ -6,7 +6,7 @@ import threading import time import urllib.parse -from pathlib import Path +from pathlib import Path, PurePosixPath from django.conf import settings from django.contrib.auth import logout @@ -138,14 +138,36 @@ def _named_url_to_pk(cls, node, resource, named_url): @classmethod def _convert_named_url(cls, url_path): - url_units = url_path.split('/') - # If the identifier is an empty string, it is always invalid. - if len(url_units) < 6 or url_units[1] != 'api' or url_units[2] not in ['v2'] or not url_units[4]: - return url_path - resource = url_units[3] + default_prefix = PurePosixPath('/api/v2/') + optional_prefix = PurePosixPath(f'/api/{settings.OPTIONAL_API_URLPATTERN_PREFIX}/v2/') + + url_path_original = url_path + url_path = PurePosixPath(url_path) + + if set(optional_prefix.parts).issubset(set(url_path.parts)): + url_prefix = optional_prefix + elif set(default_prefix.parts).issubset(set(url_path.parts)): + url_prefix = default_prefix + else: + return url_path_original + + # Remove prefix + url_path = PurePosixPath(*url_path.parts[len(url_prefix.parts) :]) + try: + resource_path = PurePosixPath(url_path.parts[0]) + name = url_path.parts[1] + url_suffix = PurePosixPath(*url_path.parts[2:]) # remove name and resource + except IndexError: + return url_path_original + + resource = resource_path.parts[0] if resource in settings.NAMED_URL_MAPPINGS: - url_units[4] = cls._named_url_to_pk(settings.NAMED_URL_GRAPH[settings.NAMED_URL_MAPPINGS[resource]], resource, url_units[4]) - return '/'.join(url_units) + pk = PurePosixPath(cls._named_url_to_pk(settings.NAMED_URL_GRAPH[settings.NAMED_URL_MAPPINGS[resource]], resource, name)) + else: + return url_path_original + + parts = url_prefix.parts + resource_path.parts + pk.parts + url_suffix.parts + return PurePosixPath(*parts).as_posix() + '/' def process_request(self, request): old_path = request.path_info diff --git a/awx/main/tests/functional/test_named_url.py b/awx/main/tests/functional/test_named_url.py index 54e3b96eddb9..d093307c7c70 100644 --- a/awx/main/tests/functional/test_named_url.py +++ b/awx/main/tests/functional/test_named_url.py @@ -205,3 +205,65 @@ def test_403_vs_404(get): get(f'/api/v2/users/{cindy.pk}/', expect=401) get('/api/v2/users/cindy/', expect=404) + + +@pytest.mark.django_db +class TestConvertNamedUrl: + @pytest.mark.parametrize( + "url", + ( + "/api/", + "/api/v2/", + "/api/v2/hosts/", + "/api/v2/hosts/1/", + "/api/v2/organizations/1/inventories/", + "/api/foo/", + "/api/foo/v2/", + "/api/foo/v2/organizations/", + "/api/foo/v2/organizations/1/", + "/api/foo/v2/organizations/1/inventories/", + "/api/foobar/", + "/api/foobar/v2/", + "/api/foobar/v2/organizations/", + "/api/foobar/v2/organizations/1/", + "/api/foobar/v2/organizations/1/inventories/", + "/api/foobar/v2/organizations/1/inventories/", + ), + ) + def test_noop(self, url): + settings.OPTIONAL_API_URLPATTERN_PREFIX = '' + assert URLModificationMiddleware._convert_named_url(url) == url + + settings.OPTIONAL_API_URLPATTERN_PREFIX = 'foo' + assert URLModificationMiddleware._convert_named_url(url) == url + + def test_named_org(self): + test_org = Organization.objects.create(name='test_org') + + assert URLModificationMiddleware._convert_named_url('/api/v2/organizations/test_org/') == f'/api/v2/organizations/{test_org.pk}/' + + def test_named_org_optional_api_urlpattern_prefix_interaction(self, settings): + settings.OPTIONAL_API_URLPATTERN_PREFIX = 'bar' + test_org = Organization.objects.create(name='test_org') + + assert URLModificationMiddleware._convert_named_url('/api/bar/v2/organizations/test_org/') == f'/api/bar/v2/organizations/{test_org.pk}/' + + @pytest.mark.parametrize("prefix", ['', 'bar']) + def test_named_org_not_found(self, prefix, settings): + settings.OPTIONAL_API_URLPATTERN_PREFIX = prefix + if prefix: + prefix += '/' + + assert URLModificationMiddleware._convert_named_url(f'/api/{prefix}v2/organizations/does-not-exist/') == f'/api/{prefix}v2/organizations/0/' + + @pytest.mark.parametrize("prefix", ['', 'bar']) + def test_named_sub_resource(self, prefix, settings): + settings.OPTIONAL_API_URLPATTERN_PREFIX = prefix + test_org = Organization.objects.create(name='test_org') + if prefix: + prefix += '/' + + assert ( + URLModificationMiddleware._convert_named_url(f'/api/{prefix}v2/organizations/test_org/inventories/') + == f'/api/{prefix}v2/organizations/{test_org.pk}/inventories/' + ) From ceafa14c9dc79d0e2c79f30e230f0cf287649cfb Mon Sep 17 00:00:00 2001 From: Chris Meyers Date: Thu, 30 May 2024 14:01:53 -0400 Subject: [PATCH 14/75] Use settings fixture in tests * Otherwise, settings value changes bleeds over into other tests. * Remove django.conf settings import so that we do not accidentally forget to use the settings fixture. --- awx/main/tests/functional/test_named_url.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/awx/main/tests/functional/test_named_url.py b/awx/main/tests/functional/test_named_url.py index d093307c7c70..557fdb8e9276 100644 --- a/awx/main/tests/functional/test_named_url.py +++ b/awx/main/tests/functional/test_named_url.py @@ -1,8 +1,6 @@ # -*- coding: utf-8 -*- import pytest -from django.conf import settings - from awx.api.versioning import reverse from awx.main.middleware import URLModificationMiddleware from awx.main.models import ( # noqa @@ -121,7 +119,7 @@ def test_notification_template(get, admin_user): @pytest.mark.django_db -def test_instance(get, admin_user): +def test_instance(get, admin_user, settings): test_instance = Instance.objects.create(uuid=settings.SYSTEM_UUID, hostname="localhost", capacity=100) url = reverse('api:instance_detail', kwargs={'pk': test_instance.pk}) response = get(url, user=admin_user, expect=200) @@ -230,7 +228,7 @@ class TestConvertNamedUrl: "/api/foobar/v2/organizations/1/inventories/", ), ) - def test_noop(self, url): + def test_noop(self, url, settings): settings.OPTIONAL_API_URLPATTERN_PREFIX = '' assert URLModificationMiddleware._convert_named_url(url) == url From d0fe0ed796425cca4dea6431f36f6820c0c98e42 Mon Sep 17 00:00:00 2001 From: Hao Liu <44379968+TheRealHaoLiu@users.noreply.github.com> Date: Fri, 31 May 2024 09:29:40 -0400 Subject: [PATCH 15/75] Add check_instance_ready management command (#15238) - throw exception and return 1 if instance not ready - return 0 if ready --- awx/main/management/commands/check_instance_ready.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 awx/main/management/commands/check_instance_ready.py diff --git a/awx/main/management/commands/check_instance_ready.py b/awx/main/management/commands/check_instance_ready.py new file mode 100644 index 000000000000..833870d8dbc7 --- /dev/null +++ b/awx/main/management/commands/check_instance_ready.py @@ -0,0 +1,12 @@ +from django.core.management.base import BaseCommand, CommandError +from awx.main.models.ha import Instance + + +class Command(BaseCommand): + help = 'Check if the task manager instance is ready throw error if not ready, can be use as readiness probe for k8s.' + + def handle(self, *args, **options): + if Instance.objects.me().node_state != Instance.States.READY: + raise CommandError('Instance is not ready') # so that return code is not 0 + + return From 0eb465531ca97aa5b932ec5752b1bdfea3181d9f Mon Sep 17 00:00:00 2001 From: Chris Meyers Date: Mon, 13 May 2024 11:16:17 -0400 Subject: [PATCH 16/75] Centralized logging via otel --- .yamllint | 2 + Makefile | 6 ++ awx/main/utils/handlers.py | 47 +++++++++ awx/settings/defaults.py | 1 + requirements/requirements_dev.txt | 6 ++ tools/docker-compose/README.md | 10 ++ .../sources/templates/docker-compose.yml.j2 | 40 ++++++++ .../sources/templates/local_settings.py.j2 | 12 +++ tools/grafana/datasources/loki_source.yml | 11 +++ tools/loki/local-config.yaml | 96 +++++++++++++++++++ tools/otel/otel-collector-config.yaml | 39 ++++++++ 11 files changed, 270 insertions(+) create mode 100644 tools/grafana/datasources/loki_source.yml create mode 100644 tools/loki/local-config.yaml create mode 100644 tools/otel/otel-collector-config.yaml diff --git a/.yamllint b/.yamllint index a937588cdc26..87a0d311a651 100644 --- a/.yamllint +++ b/.yamllint @@ -11,6 +11,8 @@ ignore: | # django template files awx/api/templates/instance_install_bundle/** .readthedocs.yaml + tools/loki + tools/otel extends: default diff --git a/Makefile b/Makefile index 2f5223621f06..5df99c544af1 100644 --- a/Makefile +++ b/Makefile @@ -47,6 +47,10 @@ VAULT ?= false VAULT_TLS ?= false # If set to true docker-compose will also start a tacacs+ instance TACACS ?= false +# If set to true docker-compose will also start an OpenTelemetry Collector instance +OTEL ?= false +# If set to true docker-compose will also start a Loki instance +LOKI ?= false # If set to true docker-compose will install editable dependencies EDITABLE_DEPENDENCIES ?= false @@ -535,6 +539,8 @@ docker-compose-sources: .git/hooks/pre-commit -e enable_vault=$(VAULT) \ -e vault_tls=$(VAULT_TLS) \ -e enable_tacacs=$(TACACS) \ + -e enable_otel=$(OTEL) \ + -e enable_loki=$(LOKI) \ -e install_editable_dependencies=$(EDITABLE_DEPENDENCIES) \ $(EXTRA_SOURCES_ANSIBLE_OPTS) diff --git a/awx/main/utils/handlers.py b/awx/main/utils/handlers.py index 15343463e8d3..4def0b6ba094 100644 --- a/awx/main/utils/handlers.py +++ b/awx/main/utils/handlers.py @@ -2,9 +2,11 @@ # All Rights Reserved. # Python +import base64 import logging import sys import traceback +import os from datetime import datetime # Django @@ -15,6 +17,15 @@ # AWX from awx.main.exceptions import PostRunError +# OTEL +from opentelemetry._logs import set_logger_provider +from opentelemetry.exporter.otlp.proto.grpc._log_exporter import OTLPLogExporter as OTLPGrpcLogExporter +from opentelemetry.exporter.otlp.proto.http._log_exporter import OTLPLogExporter as OTLPHttpLogExporter + +from opentelemetry.sdk._logs import LoggerProvider, LoggingHandler +from opentelemetry.sdk._logs.export import BatchLogRecordProcessor +from opentelemetry.sdk.resources import Resource + class RSysLogHandler(logging.handlers.SysLogHandler): append_nul = False @@ -133,3 +144,39 @@ def format(self, record): pass else: ColorHandler = logging.StreamHandler + + +class OTLPHandler(LoggingHandler): + def __init__(self, endpoint=None, protocol='grpc', service_name=None, instance_id=None, auth=None, username=None, password=None): + if not endpoint: + raise ValueError("endpoint required") + + if auth == 'basic' and (username is None or password is None): + raise ValueError("auth type basic requires username and passsword parameters") + + self.endpoint = endpoint + self.service_name = service_name or (sys.argv[1] if len(sys.argv) > 1 else (sys.argv[0] or 'unknown_service')) + self.instance_id = instance_id or os.uname().nodename + + logger_provider = LoggerProvider( + resource=Resource.create( + { + "service.name": self.service_name, + "service.instance.id": self.instance_id, + } + ), + ) + set_logger_provider(logger_provider) + + headers = {} + if auth == 'basic': + secret = f'{username}:{password}' + headers['Authorization'] = "Basic " + base64.b64encode(secret.encode()).decode() + + if protocol == 'grpc': + otlp_exporter = OTLPGrpcLogExporter(endpoint=self.endpoint, insecure=True, headers=headers) + elif protocol == 'http': + otlp_exporter = OTLPHttpLogExporter(endpoint=self.endpoint, headers=headers) + logger_provider.add_log_record_processor(BatchLogRecordProcessor(otlp_exporter)) + + super().__init__(level=logging.NOTSET, logger_provider=logger_provider) diff --git a/awx/settings/defaults.py b/awx/settings/defaults.py index 9a144777bb10..12a880a63409 100644 --- a/awx/settings/defaults.py +++ b/awx/settings/defaults.py @@ -880,6 +880,7 @@ 'address': '/var/run/awx-rsyslog/rsyslog.sock', 'filters': ['external_log_enabled', 'dynamic_level_filter', 'guid'], }, + 'otel': {'class': 'logging.NullHandler'}, }, 'loggers': { 'django': {'handlers': ['console']}, diff --git a/requirements/requirements_dev.txt b/requirements/requirements_dev.txt index 15f662fa8ef5..48437e53f26a 100644 --- a/requirements/requirements_dev.txt +++ b/requirements/requirements_dev.txt @@ -30,3 +30,9 @@ pip>=21.3 # PEP 660 – Editable installs for pyproject.toml based builds (wheel debugpy remote-pdb sdb + +# OTEL +opentelemetry-api==1.24.0 +opentelemetry-sdk==1.24.0 +opentelemetry-instrumentation-logging +opentelemetry-exporter-otlp diff --git a/tools/docker-compose/README.md b/tools/docker-compose/README.md index 7139281d7b71..22a3c7b390e0 100644 --- a/tools/docker-compose/README.md +++ b/tools/docker-compose/README.md @@ -613,3 +613,13 @@ docker exec -it -e VAULT_TOKEN= tools_vault_1 vault kv get --address=http ### Prometheus and Grafana integration See docs at https://github.com/ansible/awx/blob/devel/tools/grafana/README.md + +### OpenTelemetry Integration + +```bash +OTEL=true GRAFANA=true LOKI=true PROMETHEUS=true make docker-compose +``` + +This will start the sidecar container `tools_otel_1` and configure AWX logging to send to it. The OpenTelemetry Collector is configured to export logs to Loki. Grafana is configured with Loki as a datasource. AWX logs can be viewed in Grafana. + +`http://localhost:3001` grafana diff --git a/tools/docker-compose/ansible/roles/sources/templates/docker-compose.yml.j2 b/tools/docker-compose/ansible/roles/sources/templates/docker-compose.yml.j2 index e6cb929482e4..c6a0b4ed9095 100644 --- a/tools/docker-compose/ansible/roles/sources/templates/docker-compose.yml.j2 +++ b/tools/docker-compose/ansible/roles/sources/templates/docker-compose.yml.j2 @@ -269,6 +269,42 @@ services: # pg_notify will NOT work in transaction mode. PGBOUNCER_POOL_MODE: session {% endif %} +{% if enable_otel|bool %} + otel: + image: otel/opentelemetry-collector-contrib:0.88.0 + container_name: tools_otel_1 + hostname: otel + command: ["--config=/etc/otel-collector-config.yaml", ""] + networks: + - awx + ports: + - "4317:4317" # OTLP gRPC receiver + - "4318:4318" # OTLP http receiver + - "55679:55679" # zpages http://localhost:55679/debug/servicez /tracez + volumes: + - "../../otel/otel-collector-config.yaml:/etc/otel-collector-config.yaml" + depends_on: + - loki +{% endif %} +{% if enable_loki|bool %} + loki: + image: grafana/loki:2.9.5 + container_name: tools_loki_1 + hostname: loki + ports: + - "3100:3100" + command: -config.file=/etc/loki/local-config.yaml + networks: + - awx + volumes: + - "loki_storage:/loki:rw" + #- "../../docker-compose/loki/volumes/index:/loki/index" + #- "../../docker-compose/loki/volumes/boltdb-cache:/loki/boltdb-cache" + - "../../loki/local-config.yaml:/etc/loki/local-config.yaml" + depends_on: + - grafana +{% endif %} + {% if execution_node_count|int > 0 %} receptor-hop: image: {{ receptor_image }} @@ -360,6 +396,10 @@ volumes: grafana_storage: name: tools_grafana_storage {% endif %} +{% if enable_loki|bool %} + loki_storage: + name: tools_loki_storage +{% endif %} networks: awx: diff --git a/tools/docker-compose/ansible/roles/sources/templates/local_settings.py.j2 b/tools/docker-compose/ansible/roles/sources/templates/local_settings.py.j2 index fe9596a7b02a..fa93ccecc508 100644 --- a/tools/docker-compose/ansible/roles/sources/templates/local_settings.py.j2 +++ b/tools/docker-compose/ansible/roles/sources/templates/local_settings.py.j2 @@ -46,6 +46,18 @@ OPTIONAL_API_URLPATTERN_PREFIX = '{{ api_urlpattern_prefix }}' # LOGGING['loggers']['django_auth_ldap']['handlers'] = ['console'] # LOGGING['loggers']['django_auth_ldap']['level'] = 'DEBUG' +{% if enable_otel|bool %} +LOGGING['handlers']['otel'] |= { + 'class': 'awx.main.utils.handlers.OTLPHandler', + 'endpoint': 'http://otel:4317', +} +# Add otel log handler to all log handlers +for name in LOGGING['loggers'].keys(): + handler = LOGGING['loggers'][name].get('handlers', []) + if 'otel' not in handler: + LOGGING['loggers'][name].get('handlers', []).append('otel') +{% endif %} + BROADCAST_WEBSOCKET_PORT = 8013 BROADCAST_WEBSOCKET_VERIFY_CERT = False BROADCAST_WEBSOCKET_PROTOCOL = 'http' diff --git a/tools/grafana/datasources/loki_source.yml b/tools/grafana/datasources/loki_source.yml new file mode 100644 index 000000000000..4a6c740f3410 --- /dev/null +++ b/tools/grafana/datasources/loki_source.yml @@ -0,0 +1,11 @@ +--- +apiVersion: 1 + +datasources: + - name: Loki + type: loki + access: proxy + url: http://loki:3100 + jsonData: + timeout: 60 + maxLines: 100000 diff --git a/tools/loki/local-config.yaml b/tools/loki/local-config.yaml new file mode 100644 index 000000000000..dde03673aa70 --- /dev/null +++ b/tools/loki/local-config.yaml @@ -0,0 +1,96 @@ +auth_enabled: false + +server: + http_listen_port: 3100 + grpc_server_max_recv_msg_size: 524288000 # 500 MB + grpc_server_max_send_msg_size: 524288000 # 500 MB, might be too much, be careful + +frontend_worker: + match_max_concurrent: true + grpc_client_config: + max_send_msg_size: 524288000 # 500 MB + + +ingester: + max_chunk_age: 8766h + +common: + path_prefix: /loki + storage: + filesystem: + chunks_directory: /loki/chunks + rules_directory: /loki/rules + replication_factor: 1 + ring: + kvstore: + store: inmemory + +# compactor: +# retention_enabled: true +# # cmeyers: YOLO. 1s seems wrong but it works so right +# compaction_interval: 1s # default 10m + +schema_config: + configs: + - from: 2020-10-24 + store: boltdb-shipper + object_store: filesystem + schema: v11 + index: + prefix: index_ + period: 24h + +storage_config: + boltdb_shipper: + active_index_directory: /loki/index + cache_location: /loki/boltdb-cache + +ruler: + alertmanager_url: http://localhost:9093 + +limits_config: + retention_period: 3y + # cmeyers: The default of 30m triggers a loop of queries that take a long time + # to complete and the UI times out + split_queries_by_interval: 1d + # cmeyers: Default of 30d1h limits grafana time queries. Can't, for example, + # query last 90 days + max_query_length: 3y + # cmeyers: Made the batch post request succeed. + reject_old_samples: false + reject_old_samples_max_age: 365d + + ingestion_rate_mb: 32 + ingestion_burst_size_mb: 32 + per_stream_rate_limit: 32M + per_stream_rate_limit_burst: 32M + ingestion_rate_strategy: local # Default: global + max_global_streams_per_user: 100000000 + max_entries_limit_per_query: 100000000 + max_query_series: 1000000 + max_query_parallelism: 32 # Old Default: 14 + max_streams_per_user: 100000000 # Old Default: 10000 + +# Taken from aap-log-visualizer +frontend: + max_outstanding_per_tenant: 2048 + +query_scheduler: + max_outstanding_requests_per_tenant: 2048 + +query_range: + parallelise_shardable_queries: false + split_queries_by_interval: 0 + +# By default, Loki will send anonymous, but uniquely-identifiable usage and configuration +# analytics to Grafana Labs. These statistics are sent to https://stats.grafana.org/ +# +# Statistics help us better understand how Loki is used, and they show us performance +# levels for most users. This helps us prioritize features and documentation. +# For more information on what's sent, look at +# https://github.com/grafana/loki/blob/main/pkg/usagestats/stats.go +# Refer to the buildReport method to see what goes into a report. +# +# If you would like to disable reporting, uncomment the following lines: +#analytics: +# reporting_enabled: false diff --git a/tools/otel/otel-collector-config.yaml b/tools/otel/otel-collector-config.yaml new file mode 100644 index 000000000000..ebbf0606cb7e --- /dev/null +++ b/tools/otel/otel-collector-config.yaml @@ -0,0 +1,39 @@ +receivers: + otlp: + protocols: + grpc: + +exporters: + debug: + verbosity: detailed + + loki: + endpoint: http://loki:3100/loki/api/v1/push + tls: + insecure: true + headers: + "X-Scope-OrgID": "1" + default_labels_enabled: + exporter: true + job: true + instance: true + level: true + +processors: + batch: + +extensions: + health_check: + zpages: + endpoint: ":55679" + +service: + pipelines: + logs: + receivers: [otlp] + processors: [batch] + exporters: [loki] + + extensions: + - health_check + - zpages From da46a29f4080d784fe110b64334047ed3db75019 Mon Sep 17 00:00:00 2001 From: Chris Meyers Date: Fri, 17 May 2024 15:19:07 -0400 Subject: [PATCH 17/75] Move requirements out of dev and into mainline * Add new package license files --- licenses/deprecated.txt | 21 + licenses/googleapis-common-protos.txt | 202 ++++++ licenses/grpcio.txt | 610 ++++++++++++++++++ licenses/opentelemetry-api.txt | 201 ++++++ ...entelemetry-exporter-otlp-proto-common.txt | 201 ++++++ ...opentelemetry-exporter-otlp-proto-grpc.txt | 201 ++++++ ...opentelemetry-exporter-otlp-proto-http.txt | 201 ++++++ licenses/opentelemetry-exporter-otlp.txt | 201 ++++++ .../opentelemetry-instrumentation-logging.txt | 201 ++++++ licenses/opentelemetry-instrumentation.txt | 201 ++++++ licenses/opentelemetry-proto.txt | 201 ++++++ licenses/opentelemetry-sdk.txt | 201 ++++++ .../opentelemetry-semantic-conventions.txt | 201 ++++++ licenses/protobuf.txt | 32 + licenses/wrapt.txt | 24 + requirements/requirements.in | 4 + requirements/requirements.txt | 57 ++ requirements/requirements_dev.txt | 5 - 18 files changed, 2960 insertions(+), 5 deletions(-) create mode 100644 licenses/deprecated.txt create mode 100644 licenses/googleapis-common-protos.txt create mode 100644 licenses/grpcio.txt create mode 100644 licenses/opentelemetry-api.txt create mode 100644 licenses/opentelemetry-exporter-otlp-proto-common.txt create mode 100644 licenses/opentelemetry-exporter-otlp-proto-grpc.txt create mode 100644 licenses/opentelemetry-exporter-otlp-proto-http.txt create mode 100644 licenses/opentelemetry-exporter-otlp.txt create mode 100644 licenses/opentelemetry-instrumentation-logging.txt create mode 100644 licenses/opentelemetry-instrumentation.txt create mode 100644 licenses/opentelemetry-proto.txt create mode 100644 licenses/opentelemetry-sdk.txt create mode 100644 licenses/opentelemetry-semantic-conventions.txt create mode 100644 licenses/protobuf.txt create mode 100644 licenses/wrapt.txt diff --git a/licenses/deprecated.txt b/licenses/deprecated.txt new file mode 100644 index 000000000000..7898d9f648fc --- /dev/null +++ b/licenses/deprecated.txt @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2017 Laurent LAPORTE + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/licenses/googleapis-common-protos.txt b/licenses/googleapis-common-protos.txt new file mode 100644 index 000000000000..d64569567334 --- /dev/null +++ b/licenses/googleapis-common-protos.txt @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/licenses/grpcio.txt b/licenses/grpcio.txt new file mode 100644 index 000000000000..0e09a3e90900 --- /dev/null +++ b/licenses/grpcio.txt @@ -0,0 +1,610 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +----------------------------------------------------------- + +BSD 3-Clause License + +Copyright 2016, Google Inc. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, +this list of conditions and the following disclaimer in the documentation +and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its +contributors may be used to endorse or promote products derived from this +software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +THE POSSIBILITY OF SUCH DAMAGE. + +----------------------------------------------------------- + +Mozilla Public License Version 2.0 +================================== + +1. Definitions +-------------- + +1.1. "Contributor" + means each individual or legal entity that creates, contributes to + the creation of, or owns Covered Software. + +1.2. "Contributor Version" + means the combination of the Contributions of others (if any) used + by a Contributor and that particular Contributor's Contribution. + +1.3. "Contribution" + means Covered Software of a particular Contributor. + +1.4. "Covered Software" + means Source Code Form to which the initial Contributor has attached + the notice in Exhibit A, the Executable Form of such Source Code + Form, and Modifications of such Source Code Form, in each case + including portions thereof. + +1.5. "Incompatible With Secondary Licenses" + means + + (a) that the initial Contributor has attached the notice described + in Exhibit B to the Covered Software; or + + (b) that the Covered Software was made available under the terms of + version 1.1 or earlier of the License, but not also under the + terms of a Secondary License. + +1.6. "Executable Form" + means any form of the work other than Source Code Form. + +1.7. "Larger Work" + means a work that combines Covered Software with other material, in + a separate file or files, that is not Covered Software. + +1.8. "License" + means this document. + +1.9. "Licensable" + means having the right to grant, to the maximum extent possible, + whether at the time of the initial grant or subsequently, any and + all of the rights conveyed by this License. + +1.10. "Modifications" + means any of the following: + + (a) any file in Source Code Form that results from an addition to, + deletion from, or modification of the contents of Covered + Software; or + + (b) any new file in Source Code Form that contains any Covered + Software. + +1.11. "Patent Claims" of a Contributor + means any patent claim(s), including without limitation, method, + process, and apparatus claims, in any patent Licensable by such + Contributor that would be infringed, but for the grant of the + License, by the making, using, selling, offering for sale, having + made, import, or transfer of either its Contributions or its + Contributor Version. + +1.12. "Secondary License" + means either the GNU General Public License, Version 2.0, the GNU + Lesser General Public License, Version 2.1, the GNU Affero General + Public License, Version 3.0, or any later versions of those + licenses. + +1.13. "Source Code Form" + means the form of the work preferred for making modifications. + +1.14. "You" (or "Your") + means an individual or a legal entity exercising rights under this + License. For legal entities, "You" includes any entity that + controls, is controlled by, or is under common control with You. For + purposes of this definition, "control" means (a) the power, direct + or indirect, to cause the direction or management of such entity, + whether by contract or otherwise, or (b) ownership of more than + fifty percent (50%) of the outstanding shares or beneficial + ownership of such entity. + +2. License Grants and Conditions +-------------------------------- + +2.1. Grants + +Each Contributor hereby grants You a world-wide, royalty-free, +non-exclusive license: + +(a) under intellectual property rights (other than patent or trademark) + Licensable by such Contributor to use, reproduce, make available, + modify, display, perform, distribute, and otherwise exploit its + Contributions, either on an unmodified basis, with Modifications, or + as part of a Larger Work; and + +(b) under Patent Claims of such Contributor to make, use, sell, offer + for sale, have made, import, and otherwise transfer either its + Contributions or its Contributor Version. + +2.2. Effective Date + +The licenses granted in Section 2.1 with respect to any Contribution +become effective for each Contribution on the date the Contributor first +distributes such Contribution. + +2.3. Limitations on Grant Scope + +The licenses granted in this Section 2 are the only rights granted under +this License. No additional rights or licenses will be implied from the +distribution or licensing of Covered Software under this License. +Notwithstanding Section 2.1(b) above, no patent license is granted by a +Contributor: + +(a) for any code that a Contributor has removed from Covered Software; + or + +(b) for infringements caused by: (i) Your and any other third party's + modifications of Covered Software, or (ii) the combination of its + Contributions with other software (except as part of its Contributor + Version); or + +(c) under Patent Claims infringed by Covered Software in the absence of + its Contributions. + +This License does not grant any rights in the trademarks, service marks, +or logos of any Contributor (except as may be necessary to comply with +the notice requirements in Section 3.4). + +2.4. Subsequent Licenses + +No Contributor makes additional grants as a result of Your choice to +distribute the Covered Software under a subsequent version of this +License (see Section 10.2) or under the terms of a Secondary License (if +permitted under the terms of Section 3.3). + +2.5. Representation + +Each Contributor represents that the Contributor believes its +Contributions are its original creation(s) or it has sufficient rights +to grant the rights to its Contributions conveyed by this License. + +2.6. Fair Use + +This License is not intended to limit any rights You have under +applicable copyright doctrines of fair use, fair dealing, or other +equivalents. + +2.7. Conditions + +Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted +in Section 2.1. + +3. Responsibilities +------------------- + +3.1. Distribution of Source Form + +All distribution of Covered Software in Source Code Form, including any +Modifications that You create or to which You contribute, must be under +the terms of this License. You must inform recipients that the Source +Code Form of the Covered Software is governed by the terms of this +License, and how they can obtain a copy of this License. You may not +attempt to alter or restrict the recipients' rights in the Source Code +Form. + +3.2. Distribution of Executable Form + +If You distribute Covered Software in Executable Form then: + +(a) such Covered Software must also be made available in Source Code + Form, as described in Section 3.1, and You must inform recipients of + the Executable Form how they can obtain a copy of such Source Code + Form by reasonable means in a timely manner, at a charge no more + than the cost of distribution to the recipient; and + +(b) You may distribute such Executable Form under the terms of this + License, or sublicense it under different terms, provided that the + license for the Executable Form does not attempt to limit or alter + the recipients' rights in the Source Code Form under this License. + +3.3. Distribution of a Larger Work + +You may create and distribute a Larger Work under terms of Your choice, +provided that You also comply with the requirements of this License for +the Covered Software. If the Larger Work is a combination of Covered +Software with a work governed by one or more Secondary Licenses, and the +Covered Software is not Incompatible With Secondary Licenses, this +License permits You to additionally distribute such Covered Software +under the terms of such Secondary License(s), so that the recipient of +the Larger Work may, at their option, further distribute the Covered +Software under the terms of either this License or such Secondary +License(s). + +3.4. Notices + +You may not remove or alter the substance of any license notices +(including copyright notices, patent notices, disclaimers of warranty, +or limitations of liability) contained within the Source Code Form of +the Covered Software, except that You may alter any license notices to +the extent required to remedy known factual inaccuracies. + +3.5. Application of Additional Terms + +You may choose to offer, and to charge a fee for, warranty, support, +indemnity or liability obligations to one or more recipients of Covered +Software. However, You may do so only on Your own behalf, and not on +behalf of any Contributor. You must make it absolutely clear that any +such warranty, support, indemnity, or liability obligation is offered by +You alone, and You hereby agree to indemnify every Contributor for any +liability incurred by such Contributor as a result of warranty, support, +indemnity or liability terms You offer. You may include additional +disclaimers of warranty and limitations of liability specific to any +jurisdiction. + +4. Inability to Comply Due to Statute or Regulation +--------------------------------------------------- + +If it is impossible for You to comply with any of the terms of this +License with respect to some or all of the Covered Software due to +statute, judicial order, or regulation then You must: (a) comply with +the terms of this License to the maximum extent possible; and (b) +describe the limitations and the code they affect. Such description must +be placed in a text file included with all distributions of the Covered +Software under this License. Except to the extent prohibited by statute +or regulation, such description must be sufficiently detailed for a +recipient of ordinary skill to be able to understand it. + +5. Termination +-------------- + +5.1. The rights granted under this License will terminate automatically +if You fail to comply with any of its terms. However, if You become +compliant, then the rights granted under this License from a particular +Contributor are reinstated (a) provisionally, unless and until such +Contributor explicitly and finally terminates Your grants, and (b) on an +ongoing basis, if such Contributor fails to notify You of the +non-compliance by some reasonable means prior to 60 days after You have +come back into compliance. Moreover, Your grants from a particular +Contributor are reinstated on an ongoing basis if such Contributor +notifies You of the non-compliance by some reasonable means, this is the +first time You have received notice of non-compliance with this License +from such Contributor, and You become compliant prior to 30 days after +Your receipt of the notice. + +5.2. If You initiate litigation against any entity by asserting a patent +infringement claim (excluding declaratory judgment actions, +counter-claims, and cross-claims) alleging that a Contributor Version +directly or indirectly infringes any patent, then the rights granted to +You by any and all Contributors for the Covered Software under Section +2.1 of this License shall terminate. + +5.3. In the event of termination under Sections 5.1 or 5.2 above, all +end user license agreements (excluding distributors and resellers) which +have been validly granted by You or Your distributors under this License +prior to termination shall survive termination. + +************************************************************************ +* * +* 6. Disclaimer of Warranty * +* ------------------------- * +* * +* Covered Software is provided under this License on an "as is" * +* basis, without warranty of any kind, either expressed, implied, or * +* statutory, including, without limitation, warranties that the * +* Covered Software is free of defects, merchantable, fit for a * +* particular purpose or non-infringing. The entire risk as to the * +* quality and performance of the Covered Software is with You. * +* Should any Covered Software prove defective in any respect, You * +* (not any Contributor) assume the cost of any necessary servicing, * +* repair, or correction. This disclaimer of warranty constitutes an * +* essential part of this License. No use of any Covered Software is * +* authorized under this License except under this disclaimer. * +* * +************************************************************************ + +************************************************************************ +* * +* 7. Limitation of Liability * +* -------------------------- * +* * +* Under no circumstances and under no legal theory, whether tort * +* (including negligence), contract, or otherwise, shall any * +* Contributor, or anyone who distributes Covered Software as * +* permitted above, be liable to You for any direct, indirect, * +* special, incidental, or consequential damages of any character * +* including, without limitation, damages for lost profits, loss of * +* goodwill, work stoppage, computer failure or malfunction, or any * +* and all other commercial damages or losses, even if such party * +* shall have been informed of the possibility of such damages. This * +* limitation of liability shall not apply to liability for death or * +* personal injury resulting from such party's negligence to the * +* extent applicable law prohibits such limitation. Some * +* jurisdictions do not allow the exclusion or limitation of * +* incidental or consequential damages, so this exclusion and * +* limitation may not apply to You. * +* * +************************************************************************ + +8. Litigation +------------- + +Any litigation relating to this License may be brought only in the +courts of a jurisdiction where the defendant maintains its principal +place of business and such litigation shall be governed by laws of that +jurisdiction, without reference to its conflict-of-law provisions. +Nothing in this Section shall prevent a party's ability to bring +cross-claims or counter-claims. + +9. Miscellaneous +---------------- + +This License represents the complete agreement concerning the subject +matter hereof. If any provision of this License is held to be +unenforceable, such provision shall be reformed only to the extent +necessary to make it enforceable. Any law or regulation which provides +that the language of a contract shall be construed against the drafter +shall not be used to construe this License against a Contributor. + +10. Versions of the License +--------------------------- + +10.1. New Versions + +Mozilla Foundation is the license steward. Except as provided in Section +10.3, no one other than the license steward has the right to modify or +publish new versions of this License. Each version will be given a +distinguishing version number. + +10.2. Effect of New Versions + +You may distribute the Covered Software under the terms of the version +of the License under which You originally received the Covered Software, +or under the terms of any subsequent version published by the license +steward. + +10.3. Modified Versions + +If you create software not governed by this License, and you want to +create a new license for such software, you may create and use a +modified version of this License if you rename the license and remove +any references to the name of the license steward (except to note that +such modified license differs from this License). + +10.4. Distributing Source Code Form that is Incompatible With Secondary +Licenses + +If You choose to distribute Source Code Form that is Incompatible With +Secondary Licenses under the terms of this version of the License, the +notice described in Exhibit B of this License must be attached. + +Exhibit A - Source Code Form License Notice +------------------------------------------- + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. + +If it is not possible or desirable to put the notice in a particular +file, then You may include the notice in a location (such as a LICENSE +file in a relevant directory) where a recipient would be likely to look +for such a notice. + +You may add additional accurate notices of copyright ownership. + +Exhibit B - "Incompatible With Secondary Licenses" Notice +--------------------------------------------------------- + + This Source Code Form is "Incompatible With Secondary Licenses", as + defined by the Mozilla Public License, v. 2.0. diff --git a/licenses/opentelemetry-api.txt b/licenses/opentelemetry-api.txt new file mode 100644 index 000000000000..261eeb9e9f8b --- /dev/null +++ b/licenses/opentelemetry-api.txt @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/licenses/opentelemetry-exporter-otlp-proto-common.txt b/licenses/opentelemetry-exporter-otlp-proto-common.txt new file mode 100644 index 000000000000..261eeb9e9f8b --- /dev/null +++ b/licenses/opentelemetry-exporter-otlp-proto-common.txt @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/licenses/opentelemetry-exporter-otlp-proto-grpc.txt b/licenses/opentelemetry-exporter-otlp-proto-grpc.txt new file mode 100644 index 000000000000..261eeb9e9f8b --- /dev/null +++ b/licenses/opentelemetry-exporter-otlp-proto-grpc.txt @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/licenses/opentelemetry-exporter-otlp-proto-http.txt b/licenses/opentelemetry-exporter-otlp-proto-http.txt new file mode 100644 index 000000000000..261eeb9e9f8b --- /dev/null +++ b/licenses/opentelemetry-exporter-otlp-proto-http.txt @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/licenses/opentelemetry-exporter-otlp.txt b/licenses/opentelemetry-exporter-otlp.txt new file mode 100644 index 000000000000..261eeb9e9f8b --- /dev/null +++ b/licenses/opentelemetry-exporter-otlp.txt @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/licenses/opentelemetry-instrumentation-logging.txt b/licenses/opentelemetry-instrumentation-logging.txt new file mode 100644 index 000000000000..261eeb9e9f8b --- /dev/null +++ b/licenses/opentelemetry-instrumentation-logging.txt @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/licenses/opentelemetry-instrumentation.txt b/licenses/opentelemetry-instrumentation.txt new file mode 100644 index 000000000000..261eeb9e9f8b --- /dev/null +++ b/licenses/opentelemetry-instrumentation.txt @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/licenses/opentelemetry-proto.txt b/licenses/opentelemetry-proto.txt new file mode 100644 index 000000000000..261eeb9e9f8b --- /dev/null +++ b/licenses/opentelemetry-proto.txt @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/licenses/opentelemetry-sdk.txt b/licenses/opentelemetry-sdk.txt new file mode 100644 index 000000000000..261eeb9e9f8b --- /dev/null +++ b/licenses/opentelemetry-sdk.txt @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/licenses/opentelemetry-semantic-conventions.txt b/licenses/opentelemetry-semantic-conventions.txt new file mode 100644 index 000000000000..261eeb9e9f8b --- /dev/null +++ b/licenses/opentelemetry-semantic-conventions.txt @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/licenses/protobuf.txt b/licenses/protobuf.txt new file mode 100644 index 000000000000..19b305b00060 --- /dev/null +++ b/licenses/protobuf.txt @@ -0,0 +1,32 @@ +Copyright 2008 Google Inc. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +Code generated by the Protocol Buffer compiler is owned by the owner +of the input file used when generating it. This code is not +standalone and requires a support library to be linked with it. This +support library is itself covered by the above license. diff --git a/licenses/wrapt.txt b/licenses/wrapt.txt new file mode 100644 index 000000000000..bd8c7124a7ca --- /dev/null +++ b/licenses/wrapt.txt @@ -0,0 +1,24 @@ +Copyright (c) 2013-2023, Graham Dumpleton +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. diff --git a/requirements/requirements.in b/requirements/requirements.in index 87cdd3367a7b..fd1efc773451 100644 --- a/requirements/requirements.in +++ b/requirements/requirements.in @@ -39,6 +39,10 @@ maturin # pydantic-core build dep msgpack<1.0.6 # 1.0.6+ requires cython>=3 msrestazure openshift +opentelemetry-api~=1.24 # new y streams can be drastically different, in a good way +opentelemetry-sdk~=1.24 +opentelemetry-instrumentation-logging +opentelemetry-exporter-otlp pexpect==4.7.0 # see library notes prometheus_client psycopg diff --git a/requirements/requirements.txt b/requirements/requirements.txt index 205a2899fcae..f1516afce56a 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -123,6 +123,11 @@ defusedxml==0.7.1 # via # python3-openid # social-auth-core +deprecated==1.2.14 + # via + # opentelemetry-api + # opentelemetry-exporter-otlp-proto-grpc + # opentelemetry-exporter-otlp-proto-http distro==1.9.0 # via -r /awx_devel/requirements/requirements.in django==4.2.6 @@ -191,6 +196,12 @@ gitpython==3.1.42 # via -r /awx_devel/requirements/requirements.in google-auth==2.28.1 # via kubernetes +googleapis-common-protos==1.63.0 + # via + # opentelemetry-exporter-otlp-proto-grpc + # opentelemetry-exporter-otlp-proto-http +grpcio==1.63.0 + # via opentelemetry-exporter-otlp-proto-grpc hiredis==2.0.0 # via # -r /awx_devel/requirements/requirements.in @@ -209,6 +220,7 @@ importlib-metadata==6.2.1 # via # ansible-runner # markdown + # opentelemetry-api incremental==22.10.0 # via twisted inflect==7.0.0 @@ -302,6 +314,40 @@ oauthlib==3.2.2 # social-auth-core openshift==0.13.2 # via -r /awx_devel/requirements/requirements.in +opentelemetry-api==1.24.0 + # via + # -r /awx_devel/requirements/requirements.in + # opentelemetry-exporter-otlp-proto-grpc + # opentelemetry-exporter-otlp-proto-http + # opentelemetry-instrumentation + # opentelemetry-instrumentation-logging + # opentelemetry-sdk +opentelemetry-exporter-otlp==1.24.0 + # via -r /awx_devel/requirements/requirements.in +opentelemetry-exporter-otlp-proto-common==1.24.0 + # via + # opentelemetry-exporter-otlp-proto-grpc + # opentelemetry-exporter-otlp-proto-http +opentelemetry-exporter-otlp-proto-grpc==1.24.0 + # via opentelemetry-exporter-otlp +opentelemetry-exporter-otlp-proto-http==1.24.0 + # via opentelemetry-exporter-otlp +opentelemetry-instrumentation==0.45b0 + # via opentelemetry-instrumentation-logging +opentelemetry-instrumentation-logging==0.45b0 + # via -r /awx_devel/requirements/requirements.in +opentelemetry-proto==1.24.0 + # via + # opentelemetry-exporter-otlp-proto-common + # opentelemetry-exporter-otlp-proto-grpc + # opentelemetry-exporter-otlp-proto-http +opentelemetry-sdk==1.24.0 + # via + # -r /awx_devel/requirements/requirements.in + # opentelemetry-exporter-otlp-proto-grpc + # opentelemetry-exporter-otlp-proto-http +opentelemetry-semantic-conventions==0.45b0 + # via opentelemetry-sdk packaging==23.2 # via # ansible-runner @@ -319,6 +365,10 @@ portalocker==2.8.2 # via msal-extensions prometheus-client==0.20.0 # via -r /awx_devel/requirements/requirements.in +protobuf==4.25.3 + # via + # googleapis-common-protos + # opentelemetry-proto psutil==5.9.8 # via -r /awx_devel/requirements/requirements.in psycopg==3.1.18 @@ -414,6 +464,7 @@ requests==2.31.0 # kubernetes # msal # msrest + # opentelemetry-exporter-otlp-proto-http # python-dsv-sdk # python-tss-sdk # requests-oauthlib @@ -498,6 +549,7 @@ typing-extensions==4.9.0 # azure-keyvault-secrets # inflect # jwcrypto + # opentelemetry-sdk # psycopg # pydantic # pydantic-core @@ -516,6 +568,10 @@ websocket-client==1.7.0 # via kubernetes wheel==0.42.0 # via -r /awx_devel/requirements/requirements.in +wrapt==1.16.0 + # via + # deprecated + # opentelemetry-instrumentation xmlsec==1.3.13 # via python3-saml yarl==1.9.4 @@ -533,6 +589,7 @@ setuptools==69.0.2 # -r /awx_devel/requirements/requirements.in # asciichartpy # autobahn + # opentelemetry-instrumentation # python-daemon # setuptools-rust # setuptools-scm diff --git a/requirements/requirements_dev.txt b/requirements/requirements_dev.txt index 48437e53f26a..059ada9be3f5 100644 --- a/requirements/requirements_dev.txt +++ b/requirements/requirements_dev.txt @@ -31,8 +31,3 @@ debugpy remote-pdb sdb -# OTEL -opentelemetry-api==1.24.0 -opentelemetry-sdk==1.24.0 -opentelemetry-instrumentation-logging -opentelemetry-exporter-otlp From cae42653bfc9b81ef0ccda93746243dd67713489 Mon Sep 17 00:00:00 2001 From: Chris Meyers Date: Mon, 20 May 2024 15:47:23 -0400 Subject: [PATCH 18/75] Add recording * Always output awx logs to a file via otel * That log file can always be later replayed into a product that supports otlp at a later date. * Useful when you find a problem that you need a time series DB to help find and solve. * Useful if a community member or customer has a problem where a time series db would be helpful. You can take a "remote" users log and replay it locally for analysis. --- licenses/pyzstd.txt | 29 +++++++++++++++++++ requirements/requirements.in | 1 + requirements/requirements.txt | 2 ++ .../sources/templates/docker-compose.yml.j2 | 7 +---- tools/otel/otel-collector-config.yaml | 13 ++++++++- 5 files changed, 45 insertions(+), 7 deletions(-) create mode 100644 licenses/pyzstd.txt diff --git a/licenses/pyzstd.txt b/licenses/pyzstd.txt new file mode 100644 index 000000000000..e69dae4dd49f --- /dev/null +++ b/licenses/pyzstd.txt @@ -0,0 +1,29 @@ +BSD 3-Clause License + +Copyright (c) 2020-present, Ma Lin +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/requirements/requirements.in b/requirements/requirements.in index fd1efc773451..5e25a766da85 100644 --- a/requirements/requirements.in +++ b/requirements/requirements.in @@ -55,6 +55,7 @@ python-dsv-sdk>=1.0.4 python-tss-sdk>=1.2.1 python-ldap pyyaml>=6.0.1 +pyzstd receptorctl social-auth-core[openidconnect]==4.4.2 # see UPGRADE BLOCKERs social-auth-app-django==5.4.0 # see UPGRADE BLOCKERs diff --git a/requirements/requirements.txt b/requirements/requirements.txt index f1516afce56a..502c4a1b526c 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -446,6 +446,8 @@ pyyaml==6.0.1 # djangorestframework-yaml # kubernetes # receptorctl +pyzstd==0.15.10 + # via -r /awx_devel/requirements/requirements.in receptorctl==1.4.4 # via -r /awx_devel/requirements/requirements.in redis==5.0.1 diff --git a/tools/docker-compose/ansible/roles/sources/templates/docker-compose.yml.j2 b/tools/docker-compose/ansible/roles/sources/templates/docker-compose.yml.j2 index c6a0b4ed9095..86f84523a3ec 100644 --- a/tools/docker-compose/ansible/roles/sources/templates/docker-compose.yml.j2 +++ b/tools/docker-compose/ansible/roles/sources/templates/docker-compose.yml.j2 @@ -283,8 +283,7 @@ services: - "55679:55679" # zpages http://localhost:55679/debug/servicez /tracez volumes: - "../../otel/otel-collector-config.yaml:/etc/otel-collector-config.yaml" - depends_on: - - loki + - "../../otel/awx-logs:/awx-logs/" {% endif %} {% if enable_loki|bool %} loki: @@ -298,11 +297,7 @@ services: - awx volumes: - "loki_storage:/loki:rw" - #- "../../docker-compose/loki/volumes/index:/loki/index" - #- "../../docker-compose/loki/volumes/boltdb-cache:/loki/boltdb-cache" - "../../loki/local-config.yaml:/etc/loki/local-config.yaml" - depends_on: - - grafana {% endif %} {% if execution_node_count|int > 0 %} diff --git a/tools/otel/otel-collector-config.yaml b/tools/otel/otel-collector-config.yaml index ebbf0606cb7e..26a330cf5dbb 100644 --- a/tools/otel/otel-collector-config.yaml +++ b/tools/otel/otel-collector-config.yaml @@ -2,11 +2,22 @@ receivers: otlp: protocols: grpc: + http: exporters: debug: verbosity: detailed + file: + path: /awx-logs/awx-logs.json.zstd + rotation: + max_days: 14 + localtime: false + max_megabytes: 300 + max_backups: 200 + format: json + compression: zstd + loki: endpoint: http://loki:3100/loki/api/v1/push tls: @@ -32,7 +43,7 @@ service: logs: receivers: [otlp] processors: [batch] - exporters: [loki] + exporters: [file, loki] extensions: - health_check From 6df47c84490286946bd47c446ad9febab0f1503d Mon Sep 17 00:00:00 2001 From: Chris Meyers Date: Fri, 31 May 2024 10:11:02 -0400 Subject: [PATCH 19/75] Rework which loggers we sent to OTEL * Send all propagate=False loggers to OTEL AND the awx logger --- .../roles/sources/templates/local_settings.py.j2 | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/tools/docker-compose/ansible/roles/sources/templates/local_settings.py.j2 b/tools/docker-compose/ansible/roles/sources/templates/local_settings.py.j2 index fa93ccecc508..42a5d56366f4 100644 --- a/tools/docker-compose/ansible/roles/sources/templates/local_settings.py.j2 +++ b/tools/docker-compose/ansible/roles/sources/templates/local_settings.py.j2 @@ -51,11 +51,18 @@ LOGGING['handlers']['otel'] |= { 'class': 'awx.main.utils.handlers.OTLPHandler', 'endpoint': 'http://otel:4317', } -# Add otel log handler to all log handlers +# Add otel log handler to all log handlers where propagate is False for name in LOGGING['loggers'].keys(): - handler = LOGGING['loggers'][name].get('handlers', []) - if 'otel' not in handler: - LOGGING['loggers'][name].get('handlers', []).append('otel') + if not LOGGING['loggers'][name].get('propagate', True): + handler = LOGGING['loggers'][name].get('handlers', []) + if 'otel' not in handler: + LOGGING['loggers'][name].get('handlers', []).append('otel') + +# Everything without explicit propagate=False ends up logging to 'awx' so add it +handler = LOGGING['loggers']['awx'].get('handlers', []) +if 'otel' not in handler: + LOGGING['loggers']['awx'].get('handlers', []).append('otel') + {% endif %} BROADCAST_WEBSOCKET_PORT = 8013 From 7b3fb2c2a8b30da5e01e0331895e355599a310ae Mon Sep 17 00:00:00 2001 From: Chris Meyers Date: Fri, 31 May 2024 10:19:16 -0400 Subject: [PATCH 20/75] Add example grafana dashboard * Per-service log view --- tools/grafana/dashboards/awx_dashboard.yml | 6 +- .../dashboards/services_dashboard.json | 156 ++++++++++++++++++ 2 files changed, 160 insertions(+), 2 deletions(-) create mode 100644 tools/grafana/dashboards/services_dashboard.json diff --git a/tools/grafana/dashboards/awx_dashboard.yml b/tools/grafana/dashboards/awx_dashboard.yml index 9692ffe1b738..2f25fc523f94 100644 --- a/tools/grafana/dashboards/awx_dashboard.yml +++ b/tools/grafana/dashboards/awx_dashboard.yml @@ -2,7 +2,9 @@ apiVersion: 1 providers: - - name: awx + - name: awx-dashboards + type: file allowUiUpdates: true options: - path: /etc/grafana/provisioning/dashboards/demo_dashboard.json + foldersFromFilesStructure: true + path: /etc/grafana/provisioning/dashboards/ diff --git a/tools/grafana/dashboards/services_dashboard.json b/tools/grafana/dashboards/services_dashboard.json new file mode 100644 index 000000000000..bcf91407244d --- /dev/null +++ b/tools/grafana/dashboards/services_dashboard.json @@ -0,0 +1,156 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "id": 4, + "links": [], + "panels": [ + { + "datasource": { + "type": "loki", + "uid": "P8E80F9AEF21F6940" + }, + "gridPos": { + "h": 22, + "w": 12, + "x": 0, + "y": 0 + }, + "id": 1, + "maxPerRow": 2, + "options": { + "dedupStrategy": "none", + "enableLogDetails": true, + "prettifyLogMessage": false, + "showCommonLabels": false, + "showLabels": false, + "showTime": true, + "sortOrder": "Descending", + "wrapLogMessage": false + }, + "repeat": "service", + "repeatDirection": "h", + "targets": [ + { + "datasource": { + "type": "loki", + "uid": "P8E80F9AEF21F6940" + }, + "editorMode": "code", + "expr": "{instance=~\"${instances:pipe}\", job=~\"${service}\"} | json | line_format \"{{.instance}} {{.body}}\"", + "maxLines": 100, + "queryType": "range", + "refId": "A" + } + ], + "title": "Service ${service}", + "type": "logs" + } + ], + "schemaVersion": 39, + "tags": [], + "templating": { + "list": [ + { + "current": { + "selected": false, + "text": "awx-1", + "value": "awx-1" + }, + "datasource": { + "type": "loki", + "uid": "P8E80F9AEF21F6940" + }, + "definition": "", + "hide": 0, + "includeAll": false, + "multi": true, + "name": "instances", + "options": [], + "query": { + "label": "instance", + "refId": "LokiVariableQueryEditor-VariableQuery", + "stream": "", + "type": 1 + }, + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "type": "query" + }, + { + "current": { + "selected": true, + "text": [ + "-b", + "provision_instance", + "run_callback_receiver", + "run_dispatcher", + "run_rsyslog_configurer", + "run_ws_heartbeat", + "run_wsrelay", + "uwsgi" + ], + "value": [ + "-b", + "provision_instance", + "run_callback_receiver", + "run_dispatcher", + "run_rsyslog_configurer", + "run_ws_heartbeat", + "run_wsrelay", + "uwsgi" + ] + }, + "datasource": { + "type": "loki", + "uid": "P8E80F9AEF21F6940" + }, + "definition": "", + "hide": 0, + "includeAll": false, + "multi": true, + "name": "service", + "options": [], + "query": { + "label": "job", + "refId": "LokiVariableQueryEditor-VariableQuery", + "stream": "", + "type": 1 + }, + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "type": "query" + } + ] + }, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": {}, + "timezone": "browser", + "title": "AWX Service Logs", + "uid": "bdndgnhicrt34c", + "version": 12, + "weekStart": "" +} From a15bcf1d552d5430f5dfe7cdcb71906f327fe57b Mon Sep 17 00:00:00 2001 From: Chris Meyers Date: Fri, 31 May 2024 13:17:13 -0400 Subject: [PATCH 21/75] Add requirements comment --- requirements/requirements.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements/requirements.in b/requirements/requirements.in index 5e25a766da85..243858299aad 100644 --- a/requirements/requirements.in +++ b/requirements/requirements.in @@ -55,7 +55,7 @@ python-dsv-sdk>=1.0.4 python-tss-sdk>=1.2.1 python-ldap pyyaml>=6.0.1 -pyzstd +pyzstd # otel collector log file compression library receptorctl social-auth-core[openidconnect]==4.4.2 # see UPGRADE BLOCKERs social-auth-app-django==5.4.0 # see UPGRADE BLOCKERs From 7845ec7e01ee00c46df1222b9a7e03d5e743ecfc Mon Sep 17 00:00:00 2001 From: Akira Yokochi Date: Sun, 2 Jun 2024 11:36:30 +0900 Subject: [PATCH 22/75] Modify the link to terraform_state inventory plugin (#15241) fix link to terraform_state inventory plugin --- awx/ui/src/screens/Inventory/shared/Inventory.helptext.js | 2 +- docs/docsite/rst/userguide/inventories.rst | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/awx/ui/src/screens/Inventory/shared/Inventory.helptext.js b/awx/ui/src/screens/Inventory/shared/Inventory.helptext.js index 583862114bef..2be9d8b54d5d 100644 --- a/awx/ui/src/screens/Inventory/shared/Inventory.helptext.js +++ b/awx/ui/src/screens/Inventory/shared/Inventory.helptext.js @@ -22,7 +22,7 @@ const ansibleDocUrls = { constructed: 'https://docs.ansible.com/ansible/latest/collections/ansible/builtin/constructed_inventory.html', terraform: - 'https://github.com/ansible-collections/cloud.terraform/blob/stable-statefile-inventory/plugins/inventory/terraform_state.py', + 'https://github.com/ansible-collections/cloud.terraform/blob/main/docs/cloud.terraform.terraform_state_inventory.rst', }; const getInventoryHelpTextStrings = () => ({ diff --git a/docs/docsite/rst/userguide/inventories.rst b/docs/docsite/rst/userguide/inventories.rst index 9a2cfb1a7839..e2b17bdf73d5 100644 --- a/docs/docsite/rst/userguide/inventories.rst +++ b/docs/docsite/rst/userguide/inventories.rst @@ -1096,7 +1096,7 @@ Terraform State pair: inventory source; Terraform state -This inventory source uses the `terraform_state `_ inventory plugin from the `cloud.terraform `_ collection. The plugin will parse a terraform state file and add hosts for AWS EC2, GCE, and Azure instances. +This inventory source uses the `terraform_state `_ inventory plugin from the `cloud.terraform `_ collection. The plugin will parse a terraform state file and add hosts for AWS EC2, GCE, and Azure instances. 1. To configure this type of sourced inventory, select **Terraform State** from the Source field. @@ -1104,7 +1104,7 @@ This inventory source uses the `terraform_state `. For Terraform, enable **Overwrite** and **Update on launch** options. -4. Use the **Source Variables** field to override variables used by the ``controller`` inventory plugin. Enter variables using either JSON or YAML syntax. Use the radio button to toggle between the two. For more information on these variables, see the `terraform_state `_ file for detail. +4. Use the **Source Variables** field to override variables used by the ``controller`` inventory plugin. Enter variables using either JSON or YAML syntax. Use the radio button to toggle between the two. For more information on these variables, see the `terraform_state `_ file for detail. The ``backend_type`` variable is required by the Terraform state inventory plugin. This should match the remote backend configured in the Terraform backend credential, here is an example for an Amazon S3 backend: From 37ad690d097df2d467a6a93d890abe8887481587 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ethem=20Cem=20=C3=96zkan?= Date: Sun, 2 Jun 2024 04:48:56 +0200 Subject: [PATCH 23/75] Add AWS SNS notification support for webhook (#15184) Support for AWS SNS notifications. SNS is a widespread service that is used to integrate with other AWS services(EG lambdas). This support would unlock use cases like triggering lambda functions, especially when AWX is deployed on EKS. Decisions: Data Structure - I preferred using the same structure as Webhook for message body data because it contains all job details. For now, I directly linked to Webhook to avoid duplication, but I am open to suggestions. AWS authentication - To support non-AWS native environments, I added configuration options for AWS secret key, ID, and session tokens. When entered, these values are supplied to the underlining boto3 SNS client. If not entered, it falls back to the default authentication chain to support the native AWS environment. Properly configured EKS pods are created with temporary credentials that the default authentication chain can pick automatically. --------- Signed-off-by: Ethem Cem Ozkan --- awx/api/serializers.py | 6 +- ...notification_notification_type_and_more.py | 51 ++++++++++++++ awx/main/models/notifications.py | 2 + awx/main/notifications/awssns_backend.py | 70 +++++++++++++++++++ .../notifications/custom_notification_base.py | 12 ++++ awx/main/notifications/webhook_backend.py | 12 +--- .../tests/unit/notifications/test_awssns.py | 26 +++++++ .../NotificationList/NotificationList.js | 1 + .../NotificationTemplateDetail.js | 23 +++++- .../NotificationTemplateList.js | 1 + .../screens/NotificationTemplate/constants.js | 1 + .../shared/CustomMessagesSubForm.js | 4 +- .../shared/NotificationTemplateForm.js | 1 + .../shared/TypeInputsSubForm.js | 39 +++++++++++ ...otification-template-default-messages.json | 33 +++++++++ .../shared/typeFieldNames.js | 7 ++ awx/ui/src/types.js | 1 + .../plugins/modules/notification_template.py | 3 +- .../api/pages/notification_templates.py | 7 +- docs/docsite/rst/userguide/notifications.rst | 13 ++++ docs/notification_system.md | 5 ++ 21 files changed, 297 insertions(+), 21 deletions(-) create mode 100644 awx/main/migrations/0193_alter_notification_notification_type_and_more.py create mode 100644 awx/main/notifications/awssns_backend.py create mode 100644 awx/main/tests/unit/notifications/test_awssns.py diff --git a/awx/api/serializers.py b/awx/api/serializers.py index 75844e9d84a9..b42783eb2a4d 100644 --- a/awx/api/serializers.py +++ b/awx/api/serializers.py @@ -5381,7 +5381,7 @@ class Meta: ) def get_body(self, obj): - if obj.notification_type in ('webhook', 'pagerduty'): + if obj.notification_type in ('webhook', 'pagerduty', 'awssns'): if isinstance(obj.body, dict): if 'body' in obj.body: return obj.body['body'] @@ -5403,9 +5403,9 @@ def get_related(self, obj): def to_representation(self, obj): ret = super(NotificationSerializer, self).to_representation(obj) - if obj.notification_type == 'webhook': + if obj.notification_type in ('webhook', 'awssns'): ret.pop('subject') - if obj.notification_type not in ('email', 'webhook', 'pagerduty'): + if obj.notification_type not in ('email', 'webhook', 'pagerduty', 'awssns'): ret.pop('body') return ret diff --git a/awx/main/migrations/0193_alter_notification_notification_type_and_more.py b/awx/main/migrations/0193_alter_notification_notification_type_and_more.py new file mode 100644 index 000000000000..59fde544c8e9 --- /dev/null +++ b/awx/main/migrations/0193_alter_notification_notification_type_and_more.py @@ -0,0 +1,51 @@ +# Generated by Django 4.2.6 on 2024-05-08 07:29 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('main', '0192_custom_roles'), + ] + + operations = [ + migrations.AlterField( + model_name='notification', + name='notification_type', + field=models.CharField( + choices=[ + ('awssns', 'AWS SNS'), + ('email', 'Email'), + ('grafana', 'Grafana'), + ('irc', 'IRC'), + ('mattermost', 'Mattermost'), + ('pagerduty', 'Pagerduty'), + ('rocketchat', 'Rocket.Chat'), + ('slack', 'Slack'), + ('twilio', 'Twilio'), + ('webhook', 'Webhook'), + ], + max_length=32, + ), + ), + migrations.AlterField( + model_name='notificationtemplate', + name='notification_type', + field=models.CharField( + choices=[ + ('awssns', 'AWS SNS'), + ('email', 'Email'), + ('grafana', 'Grafana'), + ('irc', 'IRC'), + ('mattermost', 'Mattermost'), + ('pagerduty', 'Pagerduty'), + ('rocketchat', 'Rocket.Chat'), + ('slack', 'Slack'), + ('twilio', 'Twilio'), + ('webhook', 'Webhook'), + ], + max_length=32, + ), + ), + ] diff --git a/awx/main/models/notifications.py b/awx/main/models/notifications.py index da03a7dd47f3..38fd2f21b0ca 100644 --- a/awx/main/models/notifications.py +++ b/awx/main/models/notifications.py @@ -31,6 +31,7 @@ from awx.main.notifications.grafana_backend import GrafanaBackend from awx.main.notifications.rocketchat_backend import RocketChatBackend from awx.main.notifications.irc_backend import IrcBackend +from awx.main.notifications.awssns_backend import AWSSNSBackend logger = logging.getLogger('awx.main.models.notifications') @@ -40,6 +41,7 @@ class NotificationTemplate(CommonModelNameNotUnique): NOTIFICATION_TYPES = [ + ('awssns', _('AWS SNS'), AWSSNSBackend), ('email', _('Email'), CustomEmailBackend), ('slack', _('Slack'), SlackBackend), ('twilio', _('Twilio'), TwilioBackend), diff --git a/awx/main/notifications/awssns_backend.py b/awx/main/notifications/awssns_backend.py new file mode 100644 index 000000000000..f566e555e909 --- /dev/null +++ b/awx/main/notifications/awssns_backend.py @@ -0,0 +1,70 @@ +# Copyright (c) 2016 Ansible, Inc. +# All Rights Reserved. +import json +import logging + +import boto3 +from botocore.exceptions import ClientError + +from awx.main.notifications.base import AWXBaseEmailBackend +from awx.main.notifications.custom_notification_base import CustomNotificationBase + +logger = logging.getLogger('awx.main.notifications.awssns_backend') +WEBSOCKET_TIMEOUT = 30 + + +class AWSSNSBackend(AWXBaseEmailBackend, CustomNotificationBase): + init_parameters = { + "aws_region": {"label": "AWS Region", "type": "string", "default": ""}, + "aws_access_key_id": {"label": "Access Key ID", "type": "string", "default": ""}, + "aws_secret_access_key": {"label": "Secret Access Key", "type": "password", "default": ""}, + "aws_session_token": {"label": "Session Token", "type": "password", "default": ""}, + "sns_topic_arn": {"label": "SNS Topic ARN", "type": "string", "default": ""}, + } + recipient_parameter = "sns_topic_arn" + sender_parameter = None + + DEFAULT_BODY = "{{ job_metadata }}" + default_messages = CustomNotificationBase.job_metadata_messages + + def __init__(self, aws_region, aws_access_key_id, aws_secret_access_key, aws_session_token, fail_silently=False, **kwargs): + session = boto3.session.Session() + client_config = {"service_name": 'sns'} + if aws_region: + client_config["region_name"] = aws_region + if aws_secret_access_key: + client_config["aws_secret_access_key"] = aws_secret_access_key + if aws_access_key_id: + client_config["aws_access_key_id"] = aws_access_key_id + if aws_session_token: + client_config["aws_session_token"] = aws_session_token + self.client = session.client(**client_config) + super(AWSSNSBackend, self).__init__(fail_silently=fail_silently) + + def _sns_publish(self, topic_arn, message): + self.client.publish(TopicArn=topic_arn, Message=message, MessageAttributes={}) + + def format_body(self, body): + if isinstance(body, str): + try: + body = json.loads(body) + except json.JSONDecodeError: + pass + + if isinstance(body, dict): + body = json.dumps(body) + # convert dict body to json string + return body + + def send_messages(self, messages): + sent_messages = 0 + for message in messages: + sns_topic_arn = str(message.recipients()[0]) + try: + self._sns_publish(topic_arn=sns_topic_arn, message=message.body) + sent_messages += 1 + except ClientError as error: + if not self.fail_silently: + raise error + + return sent_messages diff --git a/awx/main/notifications/custom_notification_base.py b/awx/main/notifications/custom_notification_base.py index 22d04f651156..034ff3ddbf97 100644 --- a/awx/main/notifications/custom_notification_base.py +++ b/awx/main/notifications/custom_notification_base.py @@ -32,3 +32,15 @@ class CustomNotificationBase(object): "denied": {"message": DEFAULT_APPROVAL_DENIED_MSG, "body": None}, }, } + + job_metadata_messages = { + "started": {"body": "{{ job_metadata }}"}, + "success": {"body": "{{ job_metadata }}"}, + "error": {"body": "{{ job_metadata }}"}, + "workflow_approval": { + "running": {"body": '{"body": "The approval node \\"{{ approval_node_name }}\\" needs review. This node can be viewed at: {{ workflow_url }}"}'}, + "approved": {"body": '{"body": "The approval node \\"{{ approval_node_name }}\\" was approved. {{ workflow_url }}"}'}, + "timed_out": {"body": '{"body": "The approval node \\"{{ approval_node_name }}\\" has timed out. {{ workflow_url }}"}'}, + "denied": {"body": '{"body": "The approval node \\"{{ approval_node_name }}\\" was denied. {{ workflow_url }}"}'}, + }, + } diff --git a/awx/main/notifications/webhook_backend.py b/awx/main/notifications/webhook_backend.py index 6cb4c21b8337..615c549fba6a 100644 --- a/awx/main/notifications/webhook_backend.py +++ b/awx/main/notifications/webhook_backend.py @@ -27,17 +27,7 @@ class WebhookBackend(AWXBaseEmailBackend, CustomNotificationBase): sender_parameter = None DEFAULT_BODY = "{{ job_metadata }}" - default_messages = { - "started": {"body": DEFAULT_BODY}, - "success": {"body": DEFAULT_BODY}, - "error": {"body": DEFAULT_BODY}, - "workflow_approval": { - "running": {"body": '{"body": "The approval node \\"{{ approval_node_name }}\\" needs review. This node can be viewed at: {{ workflow_url }}"}'}, - "approved": {"body": '{"body": "The approval node \\"{{ approval_node_name }}\\" was approved. {{ workflow_url }}"}'}, - "timed_out": {"body": '{"body": "The approval node \\"{{ approval_node_name }}\\" has timed out. {{ workflow_url }}"}'}, - "denied": {"body": '{"body": "The approval node \\"{{ approval_node_name }}\\" was denied. {{ workflow_url }}"}'}, - }, - } + default_messages = CustomNotificationBase.job_metadata_messages def __init__(self, http_method, headers, disable_ssl_verification=False, fail_silently=False, username=None, password=None, **kwargs): self.http_method = http_method diff --git a/awx/main/tests/unit/notifications/test_awssns.py b/awx/main/tests/unit/notifications/test_awssns.py new file mode 100644 index 000000000000..0d18821fe3e3 --- /dev/null +++ b/awx/main/tests/unit/notifications/test_awssns.py @@ -0,0 +1,26 @@ +from unittest import mock +from django.core.mail.message import EmailMessage + +import awx.main.notifications.awssns_backend as awssns_backend + + +def test_send_messages(): + with mock.patch('awx.main.notifications.awssns_backend.AWSSNSBackend._sns_publish') as sns_publish_mock: + aws_region = 'us-east-1' + sns_topic = f"arn:aws:sns:{aws_region}:111111111111:topic-mock" + backend = awssns_backend.AWSSNSBackend(aws_region=aws_region, aws_access_key_id=None, aws_secret_access_key=None, aws_session_token=None) + message = EmailMessage( + 'test subject', + {'body': 'test body'}, + [], + [ + sns_topic, + ], + ) + sent_messages = backend.send_messages( + [ + message, + ] + ) + sns_publish_mock.assert_called_once_with(topic_arn=sns_topic, message=message.body) + assert sent_messages == 1 diff --git a/awx/ui/src/components/NotificationList/NotificationList.js b/awx/ui/src/components/NotificationList/NotificationList.js index 3b4171ec9034..e565d7fd1244 100644 --- a/awx/ui/src/components/NotificationList/NotificationList.js +++ b/awx/ui/src/components/NotificationList/NotificationList.js @@ -190,6 +190,7 @@ function NotificationList({ name: t`Notification type`, key: 'or__notification_type', options: [ + ['awssns', t`AWS SNS`], ['email', t`Email`], ['grafana', t`Grafana`], ['hipchat', t`Hipchat`], diff --git a/awx/ui/src/screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js b/awx/ui/src/screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js index aaf12f2cd98b..1cdb565fa735 100644 --- a/awx/ui/src/screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js +++ b/awx/ui/src/screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.js @@ -138,6 +138,25 @@ function NotificationTemplateDetail({ template, defaultMessages }) { } dataCy="nt-detail-type" /> + {template.notification_type === 'awssns' && ( + <> + + + + + )} {template.notification_type === 'email' && ( <> diff --git a/awx/ui/src/screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js b/awx/ui/src/screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js index 5172fa3f38ba..bf5c430f8702 100644 --- a/awx/ui/src/screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js +++ b/awx/ui/src/screens/NotificationTemplate/NotificationTemplateList/NotificationTemplateList.js @@ -131,6 +131,7 @@ function NotificationTemplatesList() { name: t`Notification type`, key: 'or__notification_type', options: [ + ['awssns', t`AWS SNS`], ['email', t`Email`], ['grafana', t`Grafana`], ['hipchat', t`Hipchat`], diff --git a/awx/ui/src/screens/NotificationTemplate/constants.js b/awx/ui/src/screens/NotificationTemplate/constants.js index 5937e4874388..b376bdf2afd4 100644 --- a/awx/ui/src/screens/NotificationTemplate/constants.js +++ b/awx/ui/src/screens/NotificationTemplate/constants.js @@ -1,5 +1,6 @@ /* eslint-disable-next-line import/prefer-default-export */ export const NOTIFICATION_TYPES = { + awssns: 'AWS SNS', email: 'Email', grafana: 'Grafana', irc: 'IRC', diff --git a/awx/ui/src/screens/NotificationTemplate/shared/CustomMessagesSubForm.js b/awx/ui/src/screens/NotificationTemplate/shared/CustomMessagesSubForm.js index efdbd1c6e7db..2ff4e5e04129 100644 --- a/awx/ui/src/screens/NotificationTemplate/shared/CustomMessagesSubForm.js +++ b/awx/ui/src/screens/NotificationTemplate/shared/CustomMessagesSubForm.js @@ -11,8 +11,8 @@ import getDocsBaseUrl from 'util/getDocsBaseUrl'; function CustomMessagesSubForm({ defaultMessages, type }) { const [useCustomField, , useCustomHelpers] = useField('useCustomMessages'); - const showMessages = type !== 'webhook'; - const showBodies = ['email', 'pagerduty', 'webhook'].includes(type); + const showMessages = !['webhook', 'awssns'].includes(type); + const showBodies = ['email', 'pagerduty', 'webhook', 'awssns'].includes(type); const { setFieldValue } = useFormikContext(); const config = useConfig(); diff --git a/awx/ui/src/screens/NotificationTemplate/shared/NotificationTemplateForm.js b/awx/ui/src/screens/NotificationTemplate/shared/NotificationTemplateForm.js index 6c8a3493bb8f..75c4390ab826 100644 --- a/awx/ui/src/screens/NotificationTemplate/shared/NotificationTemplateForm.js +++ b/awx/ui/src/screens/NotificationTemplate/shared/NotificationTemplateForm.js @@ -78,6 +78,7 @@ function NotificationTemplateFormFields({ defaultMessages, template }) { label: t`Choose a Notification Type`, isDisabled: true, }, + { value: 'awssns', key: 'awssns', label: t`AWS SNS` }, { value: 'email', key: 'email', label: t`E-mail` }, { value: 'grafana', key: 'grafana', label: 'Grafana' }, { value: 'irc', key: 'irc', label: 'IRC' }, diff --git a/awx/ui/src/screens/NotificationTemplate/shared/TypeInputsSubForm.js b/awx/ui/src/screens/NotificationTemplate/shared/TypeInputsSubForm.js index 270737169fc8..187acbe4fed7 100644 --- a/awx/ui/src/screens/NotificationTemplate/shared/TypeInputsSubForm.js +++ b/awx/ui/src/screens/NotificationTemplate/shared/TypeInputsSubForm.js @@ -29,6 +29,7 @@ import Popover from '../../../components/Popover/Popover'; import getHelpText from './Notifications.helptext'; const TypeFields = { + awssns: AWSSNSFields, email: EmailFields, grafana: GrafanaFields, irc: IRCFields, @@ -58,6 +59,44 @@ TypeInputsSubForm.propTypes = { export default TypeInputsSubForm; +function AWSSNSFields() { + return ( + <> + + + + + + + ); +} + function EmailFields() { const helpText = getHelpText(); return ( diff --git a/awx/ui/src/screens/NotificationTemplate/shared/notification-template-default-messages.json b/awx/ui/src/screens/NotificationTemplate/shared/notification-template-default-messages.json index e2bd0ccaedba..47ee1d01bb2f 100644 --- a/awx/ui/src/screens/NotificationTemplate/shared/notification-template-default-messages.json +++ b/awx/ui/src/screens/NotificationTemplate/shared/notification-template-default-messages.json @@ -203,6 +203,39 @@ } } }, + "awssns": { + "started": { + "body": "{{ job_metadata }}" + }, + "success": { + "body": "{{ job_metadata }}" + }, + "error": { + "body": "{{ job_metadata }}" + }, + "workflow_approval": { + "running": { + "body": { + "body": "The approval node \"{{ approval_node_name }}\" needs review. This node can be viewed at: {{ workflow_url }}" + } + }, + "approved": { + "body": { + "body": "The approval node \"{{ approval_node_name }}\" was approved. {{ workflow_url }}" + } + }, + "timed_out": { + "body": { + "body": "The approval node \"{{ approval_node_name }}\" has timed out. {{ workflow_url }}" + } + }, + "denied": { + "body": { + "body": "The approval node \"{{ approval_node_name }}\" was denied. {{ workflow_url }}" + } + } + } + }, "mattermost": { "started": { "message": "{{ job_friendly_name }} #{{ job.id }} '{{ job.name }}' {{ job.status }}: {{ url }}", diff --git a/awx/ui/src/screens/NotificationTemplate/shared/typeFieldNames.js b/awx/ui/src/screens/NotificationTemplate/shared/typeFieldNames.js index acfbd88097f8..1acbd981e388 100644 --- a/awx/ui/src/screens/NotificationTemplate/shared/typeFieldNames.js +++ b/awx/ui/src/screens/NotificationTemplate/shared/typeFieldNames.js @@ -1,4 +1,11 @@ const typeFieldNames = { + awssns: [ + 'aws_region', + 'aws_access_key_id', + 'aws_secret_access_key', + 'aws_session_token', + 'sns_topic_arn', + ], email: [ 'username', 'password', diff --git a/awx/ui/src/types.js b/awx/ui/src/types.js index e81ee356ab9b..610dc33713eb 100644 --- a/awx/ui/src/types.js +++ b/awx/ui/src/types.js @@ -374,6 +374,7 @@ export const CredentialType = shape({ }); export const NotificationType = oneOf([ + 'awssns', 'email', 'grafana', 'irc', diff --git a/awx_collection/plugins/modules/notification_template.py b/awx_collection/plugins/modules/notification_template.py index bb1df60d3857..e44e2be5e06c 100644 --- a/awx_collection/plugins/modules/notification_template.py +++ b/awx_collection/plugins/modules/notification_template.py @@ -50,6 +50,7 @@ description: - The type of notification to be sent. choices: + - 'awssns' - 'email' - 'grafana' - 'irc' @@ -219,7 +220,7 @@ def main(): copy_from=dict(), description=dict(), organization=dict(), - notification_type=dict(choices=['email', 'grafana', 'irc', 'mattermost', 'pagerduty', 'rocketchat', 'slack', 'twilio', 'webhook']), + notification_type=dict(choices=['awssns', 'email', 'grafana', 'irc', 'mattermost', 'pagerduty', 'rocketchat', 'slack', 'twilio', 'webhook']), notification_configuration=dict(type='dict'), messages=dict(type='dict'), state=dict(choices=['present', 'absent', 'exists'], default='present'), diff --git a/awxkit/awxkit/api/pages/notification_templates.py b/awxkit/awxkit/api/pages/notification_templates.py index 3d33300ce87e..b12bbc8c9704 100644 --- a/awxkit/awxkit/api/pages/notification_templates.py +++ b/awxkit/awxkit/api/pages/notification_templates.py @@ -11,7 +11,7 @@ job_results = ('any', 'error', 'success') -notification_types = ('email', 'irc', 'pagerduty', 'slack', 'twilio', 'webhook', 'mattermost', 'grafana', 'rocketchat') +notification_types = ('awssns', 'email', 'irc', 'pagerduty', 'slack', 'twilio', 'webhook', 'mattermost', 'grafana', 'rocketchat') class NotificationTemplate(HasCopy, HasCreate, base.Base): @@ -58,7 +58,10 @@ def payload(self, organization, notification_type='slack', messages=not_provided if payload.notification_configuration == {}: services = config.credentials.notification_services - if notification_type == 'email': + if notification_type == 'awssns': + fields = ('aws_region', 'aws_access_key_id', 'aws_secret_access_key', 'aws_session_token', 'sns_topic_arn') + cred = services.awssns + elif notification_type == 'email': fields = ('host', 'username', 'password', 'port', 'use_ssl', 'use_tls', 'sender', 'recipients') cred = services.email elif notification_type == 'irc': diff --git a/docs/docsite/rst/userguide/notifications.rst b/docs/docsite/rst/userguide/notifications.rst index e7e823411188..ff7691c6fd19 100644 --- a/docs/docsite/rst/userguide/notifications.rst +++ b/docs/docsite/rst/userguide/notifications.rst @@ -84,6 +84,7 @@ Notification Types .. index:: pair: notifications; types + triple: notifications; types; AWS SNS triple: notifications; types; Email triple: notifications; types; Grafana triple: notifications; types; IRC @@ -101,6 +102,18 @@ Notification types supported with AWX: Each of these have their own configuration and behavioral semantics and testing them may need to be approached in different ways. Additionally, you can customize each type of notification down to a specific detail, or a set of criteria to trigger a notification. See :ref:`ug_custom_notifications` for more detail on configuring custom notifications. The following sections will give as much detail as possible on each type of notification. +AWS SNS +------- + +The AWS SNS(https://aws.amazon.com/sns/) notification type supports sending messages into an SNS topic. + +You must provide the following details to setup a SNS notification: + +- AWS Region +- AWS Access Key ID +- AWS Secret Access Key +- AWS SNS Topic ARN + Email ------- diff --git a/docs/notification_system.md b/docs/notification_system.md index edd64275fe67..96502edfa9a9 100644 --- a/docs/notification_system.md +++ b/docs/notification_system.md @@ -70,6 +70,7 @@ Once a Notification Template has been created, its configuration can be tested b The currently-defined Notification Types are: +* AWS SNS * Email * Slack * Mattermost @@ -82,6 +83,10 @@ The currently-defined Notification Types are: Each of these have their own configuration and behavioral semantics and testing them may need to be approached in different ways. The following sections will give as much detail as possible. +## AWS SNS + +The AWS SNS notification type supports sending messages into an SNS topic. + ## Email The email notification type supports a wide variety of SMTP servers and has support for SSL/TLS connections and timeouts. From 659c3b64dec5fb8dd6eeabcb47e794cbbae9e8c4 Mon Sep 17 00:00:00 2001 From: Hao Liu <44379968+TheRealHaoLiu@users.noreply.github.com> Date: Mon, 3 Jun 2024 11:41:56 -0400 Subject: [PATCH 24/75] Unpin cypthon (#15246) * Unpin cython * Remove unused asyncpg * Remove asyncpg license file --- licenses/asyncpg.txt | 204 ---------------------------------- requirements/requirements.in | 4 +- requirements/requirements.txt | 11 +- 3 files changed, 4 insertions(+), 215 deletions(-) delete mode 100644 licenses/asyncpg.txt diff --git a/licenses/asyncpg.txt b/licenses/asyncpg.txt deleted file mode 100644 index d931386122bb..000000000000 --- a/licenses/asyncpg.txt +++ /dev/null @@ -1,204 +0,0 @@ -Copyright (C) 2016-present the asyncpg authors and contributors. - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright (C) 2016-present the asyncpg authors and contributors - - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/requirements/requirements.in b/requirements/requirements.in index 243858299aad..ba5758611f57 100644 --- a/requirements/requirements.in +++ b/requirements/requirements.in @@ -2,7 +2,6 @@ aiohttp>=3.8.6 # CVE-2023-47627 ansiconv==1.0.0 # UPGRADE BLOCKER: from 2013, consider replacing instead of upgrading asciichartpy asn1 -asyncpg azure-identity azure-keyvault boto3 @@ -10,7 +9,7 @@ botocore channels channels-redis==3.4.1 # see UPGRADE BLOCKERs cryptography>=41.0.7 # CVE-2023-49083 -Cython<3 # this is needed as a build dependency, one day we may have separated build deps +Cython # this is needed as a build dependency, one day we may have separated build deps daphne distro django==4.2.6 # CVE-2023-43665 @@ -36,7 +35,6 @@ JSON-log-formatter jsonschema Markdown # used for formatting API help maturin # pydantic-core build dep -msgpack<1.0.6 # 1.0.6+ requires cython>=3 msrestazure openshift opentelemetry-api~=1.24 # new y streams can be drastically different, in a good way diff --git a/requirements/requirements.txt b/requirements/requirements.txt index 502c4a1b526c..10cc646d76b5 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -31,10 +31,7 @@ async-timeout==4.0.3 # via # aiohttp # aioredis - # asyncpg # redis -asyncpg==0.29.0 - # via -r /awx_devel/requirements/requirements.in attrs==23.2.0 # via # aiohttp @@ -113,7 +110,7 @@ cryptography==41.0.7 # pyopenssl # service-identity # social-auth-core -cython==0.29.37 +cython==3.0.10 # via -r /awx_devel/requirements/requirements.in daphne==3.0.2 # via @@ -292,10 +289,8 @@ msal==1.26.0 # msal-extensions msal-extensions==1.1.0 # via azure-identity -msgpack==1.0.5 - # via - # -r /awx_devel/requirements/requirements.in - # channels-redis +msgpack==1.0.8 + # via channels-redis msrest==0.7.1 # via msrestazure msrestazure==0.6.4 From cf09a4220dea979c9e3eed9c279545dca39d5e88 Mon Sep 17 00:00:00 2001 From: Hao Liu <44379968+TheRealHaoLiu@users.noreply.github.com> Date: Mon, 3 Jun 2024 15:42:20 -0400 Subject: [PATCH 25/75] Repin cython due to https://github.com/yaml/pyyaml/pull/702 (#15248) * Revert "Unpin cypthon (#15246)" This reverts commit 659c3b64dec5fb8dd6eeabcb47e794cbbae9e8c4. * Pin grpcio Avoid cython 3 due to https://github.com/yaml/pyyaml/pull/702 * Delete asyncpg.txt --- requirements/requirements.in | 4 +++- requirements/requirements.txt | 14 +++++++++----- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/requirements/requirements.in b/requirements/requirements.in index ba5758611f57..6344d0c1402b 100644 --- a/requirements/requirements.in +++ b/requirements/requirements.in @@ -9,7 +9,7 @@ botocore channels channels-redis==3.4.1 # see UPGRADE BLOCKERs cryptography>=41.0.7 # CVE-2023-49083 -Cython # this is needed as a build dependency, one day we may have separated build deps +Cython<3 # due to https://github.com/yaml/pyyaml/pull/702 daphne distro django==4.2.6 # CVE-2023-43665 @@ -28,6 +28,7 @@ djangorestframework>=3.15.0 djangorestframework-yaml filelock GitPython>=3.1.37 # CVE-2023-41040 +grpcio<1.63.0 # 1.63.0+ requires cython>=3 hiredis==2.0.0 # see UPGRADE BLOCKERs irc jinja2>=3.1.3 # CVE-2024-22195 @@ -35,6 +36,7 @@ JSON-log-formatter jsonschema Markdown # used for formatting API help maturin # pydantic-core build dep +msgpack<1.0.6 # 1.0.6+ requires cython>=3 msrestazure openshift opentelemetry-api~=1.24 # new y streams can be drastically different, in a good way diff --git a/requirements/requirements.txt b/requirements/requirements.txt index 10cc646d76b5..727004584d3f 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -110,7 +110,7 @@ cryptography==41.0.7 # pyopenssl # service-identity # social-auth-core -cython==3.0.10 +cython==0.29.37 # via -r /awx_devel/requirements/requirements.in daphne==3.0.2 # via @@ -197,8 +197,10 @@ googleapis-common-protos==1.63.0 # via # opentelemetry-exporter-otlp-proto-grpc # opentelemetry-exporter-otlp-proto-http -grpcio==1.63.0 - # via opentelemetry-exporter-otlp-proto-grpc +grpcio==1.62.2 + # via + # -r /awx_devel/requirements/requirements.in + # opentelemetry-exporter-otlp-proto-grpc hiredis==2.0.0 # via # -r /awx_devel/requirements/requirements.in @@ -289,8 +291,10 @@ msal==1.26.0 # msal-extensions msal-extensions==1.1.0 # via azure-identity -msgpack==1.0.8 - # via channels-redis +msgpack==1.0.5 + # via + # -r /awx_devel/requirements/requirements.in + # channels-redis msrest==0.7.1 # via msrestazure msrestazure==0.6.4 From 6dc4a4508da011cfa15a187ec3fd919f033008ac Mon Sep 17 00:00:00 2001 From: Jake Jackson Date: Tue, 4 Jun 2024 15:44:09 -0400 Subject: [PATCH 26/75] fix cve 2024-24680 (#15250) --- requirements/requirements.in | 2 +- requirements/requirements.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements/requirements.in b/requirements/requirements.in index 6344d0c1402b..0911f59c40b0 100644 --- a/requirements/requirements.in +++ b/requirements/requirements.in @@ -12,7 +12,7 @@ cryptography>=41.0.7 # CVE-2023-49083 Cython<3 # due to https://github.com/yaml/pyyaml/pull/702 daphne distro -django==4.2.6 # CVE-2023-43665 +django==4.2.10 # CVE-2024-24680 django-auth-ldap django-cors-headers django-crum diff --git a/requirements/requirements.txt b/requirements/requirements.txt index 727004584d3f..447dd5ec3610 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -127,7 +127,7 @@ deprecated==1.2.14 # opentelemetry-exporter-otlp-proto-http distro==1.9.0 # via -r /awx_devel/requirements/requirements.in -django==4.2.6 +django==4.2.10 # via # -r /awx_devel/requirements/requirements.in # channels From 793777bec7806f1eebcf0f0a0ce72ca85916e7c1 Mon Sep 17 00:00:00 2001 From: Satoe Imaishi Date: Wed, 5 Jun 2024 11:04:15 -0400 Subject: [PATCH 27/75] Add cython to VENV_BOOTSTRAP for grpcio (#15256) --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 5df99c544af1..8a476581598e 100644 --- a/Makefile +++ b/Makefile @@ -69,7 +69,7 @@ RECEPTOR_IMAGE ?= quay.io/ansible/receptor:devel SRC_ONLY_PKGS ?= cffi,pycparser,psycopg,twilio # These should be upgraded in the AWX and Ansible venv before attempting # to install the actual requirements -VENV_BOOTSTRAP ?= pip==21.2.4 setuptools==69.0.2 setuptools_scm[toml]==8.0.4 wheel==0.42.0 +VENV_BOOTSTRAP ?= pip==21.2.4 setuptools==69.0.2 setuptools_scm[toml]==8.0.4 wheel==0.42.0 cython==0.29.37 NAME ?= awx From b470ca32af59a58413b553fd30a73b5fed7f1edc Mon Sep 17 00:00:00 2001 From: Seth Foster Date: Wed, 5 Jun 2024 12:44:01 -0400 Subject: [PATCH 28/75] Prevent modifying shared resources when using platform ingress (#15234) * Prevent modifying shared resources Adds a class decorator to prevent modifying shared resources when gateway is being used. AWX_DIRECT_SHARED_RESOURCE_MANAGEMENT_ENABLED is the setting to enable/disable this feature. Works by overriding these view methods: - create - delete - perform_update create and delete are overridden to raise a PermissionDenied exception. perform_update is overridden to check if any shared fields are being modified, and raise a PermissionDenied exception if so. Additional changes: Prevent sso conf from registering external authentication related settings if AWX_DIRECT_SHARED_RESOURCE_MANAGEMENT_ENABLED is False Signed-off-by: Seth Foster Co-authored-by: Hao Liu <44379968+TheRealHaoLiu@users.noreply.github.com> --- awx/api/views/__init__.py | 90 +- awx/api/views/organization.py | 6 + .../api/test_immutablesharedfields.py | 66 + awx/settings/defaults.py | 4 + awx/sso/conf.py | 2826 +++++++++-------- 5 files changed, 1581 insertions(+), 1411 deletions(-) create mode 100644 awx/main/tests/functional/api/test_immutablesharedfields.py diff --git a/awx/api/views/__init__.py b/awx/api/views/__init__.py index 9bc8bad28621..4ed8f8b2f645 100644 --- a/awx/api/views/__init__.py +++ b/awx/api/views/__init__.py @@ -62,6 +62,7 @@ # django-ansible-base from ansible_base.rbac.models import RoleEvaluation, ObjectRole +from ansible_base.resource_registry.shared_types import OrganizationType, TeamType, UserType # AWX from awx.main.tasks.system import send_notifications, update_inventory_computed_fields @@ -128,6 +129,7 @@ from awx.api.pagination import UnifiedJobEventPagination from awx.main.utils import set_environ + logger = logging.getLogger('awx.api.views') @@ -710,16 +712,81 @@ def get(self, request): return Response(data) +def immutablesharedfields(cls): + ''' + Class decorator to prevent modifying shared resources when AWX_DIRECT_SHARED_RESOURCE_MANAGEMENT_ENABLED setting is set to False. + + Works by overriding these view methods: + - create + - delete + - perform_update + create and delete are overridden to raise a PermissionDenied exception. + perform_update is overridden to check if any shared fields are being modified, + and raise a PermissionDenied exception if so. + ''' + # create instead of perform_create because some of our views + # override create instead of perform_create + if hasattr(cls, 'create'): + cls.original_create = cls.create + + @functools.wraps(cls.create) + def create_wrapper(*args, **kwargs): + if settings.AWX_DIRECT_SHARED_RESOURCE_MANAGEMENT_ENABLED: + return cls.original_create(*args, **kwargs) + raise PermissionDenied({'detail': _('Creation of this resource is not allowed. Create this resource via the platform ingress.')}) + + cls.create = create_wrapper + + if hasattr(cls, 'delete'): + cls.original_delete = cls.delete + + @functools.wraps(cls.delete) + def delete_wrapper(*args, **kwargs): + if settings.AWX_DIRECT_SHARED_RESOURCE_MANAGEMENT_ENABLED: + return cls.original_delete(*args, **kwargs) + raise PermissionDenied({'detail': _('Deletion of this resource is not allowed. Delete this resource via the platform ingress.')}) + + cls.delete = delete_wrapper + + if hasattr(cls, 'perform_update'): + cls.original_perform_update = cls.perform_update + + @functools.wraps(cls.perform_update) + def update_wrapper(*args, **kwargs): + if not settings.AWX_DIRECT_SHARED_RESOURCE_MANAGEMENT_ENABLED: + view, serializer = args + instance = view.get_object() + if instance: + if isinstance(instance, models.Organization): + shared_fields = OrganizationType._declared_fields.keys() + elif isinstance(instance, models.User): + shared_fields = UserType._declared_fields.keys() + elif isinstance(instance, models.Team): + shared_fields = TeamType._declared_fields.keys() + attrs = serializer.validated_data + for field in shared_fields: + if field in attrs and getattr(instance, field) != attrs[field]: + raise PermissionDenied({field: _(f"Cannot change shared field '{field}'. Alter this field via the platform ingress.")}) + return cls.original_perform_update(*args, **kwargs) + + cls.perform_update = update_wrapper + + return cls + + +@immutablesharedfields class TeamList(ListCreateAPIView): model = models.Team serializer_class = serializers.TeamSerializer +@immutablesharedfields class TeamDetail(RetrieveUpdateDestroyAPIView): model = models.Team serializer_class = serializers.TeamSerializer +@immutablesharedfields class TeamUsersList(BaseUsersList): model = models.User serializer_class = serializers.UserSerializer @@ -1101,6 +1168,7 @@ class ProjectCopy(CopyAPIView): copy_return_serializer_class = serializers.ProjectSerializer +@immutablesharedfields class UserList(ListCreateAPIView): model = models.User serializer_class = serializers.UserSerializer @@ -1271,7 +1339,16 @@ def post(self, request, *args, **kwargs): user = get_object_or_400(models.User, pk=self.kwargs['pk']) role = get_object_or_400(models.Role, pk=sub_id) - credential_content_type = ContentType.objects.get_for_model(models.Credential) + content_types = ContentType.objects.get_for_models(models.Organization, models.Team, models.Credential) # dict of {model: content_type} + # Prevent user to be associated with team/org when AWX_DIRECT_SHARED_RESOURCE_MANAGEMENT_ENABLED is False + if not settings.AWX_DIRECT_SHARED_RESOURCE_MANAGEMENT_ENABLED: + for model in [models.Organization, models.Team]: + ct = content_types[model] + if role.content_type == ct and role.role_field in ['member_role', 'admin_role']: + data = dict(msg=_(f"Cannot directly modify user membership to {ct.model}. Direct shared resource management disabled")) + return Response(data, status=status.HTTP_403_FORBIDDEN) + + credential_content_type = content_types[models.Credential] if role.content_type == credential_content_type: if 'disassociate' not in request.data and role.content_object.organization and user not in role.content_object.organization.member_role: data = dict(msg=_("You cannot grant credential access to a user not in the credentials' organization")) @@ -1343,6 +1420,7 @@ def get_queryset(self): return qs.filter(Q(actor=parent) | Q(user__in=[parent])) +@immutablesharedfields class UserDetail(RetrieveUpdateDestroyAPIView): model = models.User serializer_class = serializers.UserSerializer @@ -4295,7 +4373,15 @@ def post(self, request, *args, **kwargs): user = get_object_or_400(models.User, pk=sub_id) role = self.get_parent_object() - credential_content_type = ContentType.objects.get_for_model(models.Credential) + content_types = ContentType.objects.get_for_models(models.Organization, models.Team, models.Credential) # dict of {model: content_type} + if not settings.AWX_DIRECT_SHARED_RESOURCE_MANAGEMENT_ENABLED: + for model in [models.Organization, models.Team]: + ct = content_types[model] + if role.content_type == ct and role.role_field in ['member_role', 'admin_role']: + data = dict(msg=_(f"Cannot directly modify user membership to {ct.model}. Direct shared resource management disabled")) + return Response(data, status=status.HTTP_403_FORBIDDEN) + + credential_content_type = content_types[models.Credential] if role.content_type == credential_content_type: if 'disassociate' not in request.data and role.content_object.organization and user not in role.content_object.organization.member_role: data = dict(msg=_("You cannot grant credential access to a user not in the credentials' organization")) diff --git a/awx/api/views/organization.py b/awx/api/views/organization.py index b82f4b3a4be0..9b93ac8406a6 100644 --- a/awx/api/views/organization.py +++ b/awx/api/views/organization.py @@ -53,15 +53,18 @@ CredentialSerializer, ) from awx.api.views.mixin import RelatedJobsPreventDeleteMixin, OrganizationCountsMixin +from awx.api.views import immutablesharedfields logger = logging.getLogger('awx.api.views.organization') +@immutablesharedfields class OrganizationList(OrganizationCountsMixin, ListCreateAPIView): model = Organization serializer_class = OrganizationSerializer +@immutablesharedfields class OrganizationDetail(RelatedJobsPreventDeleteMixin, RetrieveUpdateDestroyAPIView): model = Organization serializer_class = OrganizationSerializer @@ -104,6 +107,7 @@ class OrganizationInventoriesList(SubListAPIView): relationship = 'inventories' +@immutablesharedfields class OrganizationUsersList(BaseUsersList): model = User serializer_class = UserSerializer @@ -112,6 +116,7 @@ class OrganizationUsersList(BaseUsersList): ordering = ('username',) +@immutablesharedfields class OrganizationAdminsList(BaseUsersList): model = User serializer_class = UserSerializer @@ -150,6 +155,7 @@ class OrganizationWorkflowJobTemplatesList(SubListCreateAPIView): parent_key = 'organization' +@immutablesharedfields class OrganizationTeamsList(SubListCreateAttachDetachAPIView): model = Team serializer_class = TeamSerializer diff --git a/awx/main/tests/functional/api/test_immutablesharedfields.py b/awx/main/tests/functional/api/test_immutablesharedfields.py new file mode 100644 index 000000000000..9b72abcb1613 --- /dev/null +++ b/awx/main/tests/functional/api/test_immutablesharedfields.py @@ -0,0 +1,66 @@ +import pytest + +from awx.api.versioning import reverse +from awx.main.models import Organization + + +@pytest.mark.django_db +class TestImmutableSharedFields: + @pytest.fixture(autouse=True) + def configure_settings(self, settings): + settings.AWX_DIRECT_SHARED_RESOURCE_MANAGEMENT_ENABLED = False + + def test_create_raises_permission_denied(self, admin_user, post): + orgA = Organization.objects.create(name='orgA') + resp = post( + url=reverse('api:team_list'), + data={'name': 'teamA', 'organization': orgA.id}, + user=admin_user, + expect=403, + ) + assert "Creation of this resource is not allowed" in resp.data['detail'] + + def test_perform_delete_raises_permission_denied(self, admin_user, delete): + orgA = Organization.objects.create(name='orgA') + team = orgA.teams.create(name='teamA') + resp = delete( + url=reverse('api:team_detail', kwargs={'pk': team.id}), + user=admin_user, + expect=403, + ) + assert "Deletion of this resource is not allowed" in resp.data['detail'] + + def test_perform_update(self, admin_user, patch): + orgA = Organization.objects.create(name='orgA') + team = orgA.teams.create(name='teamA') + # allow patching non-shared fields + patch( + url=reverse('api:team_detail', kwargs={'pk': team.id}), + data={"description": "can change this field"}, + user=admin_user, + expect=200, + ) + orgB = Organization.objects.create(name='orgB') + # prevent patching shared fields + resp = patch(url=reverse('api:team_detail', kwargs={'pk': team.id}), data={"organization": orgB.id}, user=admin_user, expect=403) + assert "Cannot change shared field" in resp.data['organization'] + + @pytest.mark.parametrize( + 'role', + ['admin_role', 'member_role'], + ) + @pytest.mark.parametrize('resource', ['organization', 'team']) + def test_prevent_assigning_member_to_organization_or_team(self, admin_user, post, resource, role): + orgA = Organization.objects.create(name='orgA') + if resource == 'organization': + role = getattr(orgA, role) + elif resource == 'team': + teamA = orgA.teams.create(name='teamA') + role = getattr(teamA, role) + resp = post( + url=reverse('api:user_roles_list', kwargs={'pk': admin_user.id}), + data={'id': role.id}, + user=admin_user, + expect=403, + ) + assert f"Cannot directly modify user membership to {resource}." in resp.data['msg'] diff --git a/awx/settings/defaults.py b/awx/settings/defaults.py index 12a880a63409..9db1312ad74a 100644 --- a/awx/settings/defaults.py +++ b/awx/settings/defaults.py @@ -656,6 +656,10 @@ # Automatically remove nodes that have missed their heartbeats after some time AWX_AUTO_DEPROVISION_INSTANCES = False +# If False, do not allow creation of resources that are shared with the platform ingress +# e.g. organizations, teams, and users +AWX_DIRECT_SHARED_RESOURCE_MANAGEMENT_ENABLED = True + # Enable Pendo on the UI, possible values are 'off', 'anonymous', and 'detailed' # Note: This setting may be overridden by database settings. PENDO_TRACKING_STATE = "off" diff --git a/awx/sso/conf.py b/awx/sso/conf.py index 655640d9d765..9190f47c0db4 100644 --- a/awx/sso/conf.py +++ b/awx/sso/conf.py @@ -92,1568 +92,1576 @@ def __call__(self): ] ) -############################################################################### -# AUTHENTICATION BACKENDS DYNAMIC SETTING -############################################################################### - -register( - 'AUTHENTICATION_BACKENDS', - field_class=AuthenticationBackendsField, - label=_('Authentication Backends'), - help_text=_('List of authentication backends that are enabled based on license features and other authentication settings.'), - read_only=True, - depends_on=AuthenticationBackendsField.get_all_required_settings(), - category=_('Authentication'), - category_slug='authentication', -) +if settings.AWX_DIRECT_SHARED_RESOURCE_MANAGEMENT_ENABLED: + ############################################################################### + # AUTHENTICATION BACKENDS DYNAMIC SETTING + ############################################################################### -register( - 'SOCIAL_AUTH_ORGANIZATION_MAP', - field_class=SocialOrganizationMapField, - allow_null=True, - default=None, - label=_('Social Auth Organization Map'), - help_text=SOCIAL_AUTH_ORGANIZATION_MAP_HELP_TEXT, - category=_('Authentication'), - category_slug='authentication', - placeholder=SOCIAL_AUTH_ORGANIZATION_MAP_PLACEHOLDER, -) + register( + 'AUTHENTICATION_BACKENDS', + field_class=AuthenticationBackendsField, + label=_('Authentication Backends'), + help_text=_('List of authentication backends that are enabled based on license features and other authentication settings.'), + read_only=True, + depends_on=AuthenticationBackendsField.get_all_required_settings(), + category=_('Authentication'), + category_slug='authentication', + ) -register( - 'SOCIAL_AUTH_TEAM_MAP', - field_class=SocialTeamMapField, - allow_null=True, - default=None, - label=_('Social Auth Team Map'), - help_text=SOCIAL_AUTH_TEAM_MAP_HELP_TEXT, - category=_('Authentication'), - category_slug='authentication', - placeholder=SOCIAL_AUTH_TEAM_MAP_PLACEHOLDER, -) + register( + 'SOCIAL_AUTH_ORGANIZATION_MAP', + field_class=SocialOrganizationMapField, + allow_null=True, + default=None, + label=_('Social Auth Organization Map'), + help_text=SOCIAL_AUTH_ORGANIZATION_MAP_HELP_TEXT, + category=_('Authentication'), + category_slug='authentication', + placeholder=SOCIAL_AUTH_ORGANIZATION_MAP_PLACEHOLDER, + ) -register( - 'SOCIAL_AUTH_USER_FIELDS', - field_class=fields.StringListField, - allow_null=True, - default=None, - label=_('Social Auth User Fields'), - help_text=_( - 'When set to an empty list `[]`, this setting prevents new user ' - 'accounts from being created. Only users who have previously ' - 'logged in using social auth or have a user account with a ' - 'matching email address will be able to login.' - ), - category=_('Authentication'), - category_slug='authentication', - placeholder=['username', 'email'], -) + register( + 'SOCIAL_AUTH_TEAM_MAP', + field_class=SocialTeamMapField, + allow_null=True, + default=None, + label=_('Social Auth Team Map'), + help_text=SOCIAL_AUTH_TEAM_MAP_HELP_TEXT, + category=_('Authentication'), + category_slug='authentication', + placeholder=SOCIAL_AUTH_TEAM_MAP_PLACEHOLDER, + ) -register( - 'SOCIAL_AUTH_USERNAME_IS_FULL_EMAIL', - field_class=fields.BooleanField, - default=False, - label=_('Use Email address for usernames'), - help_text=_('Enabling this setting will tell social auth to use the full Email as username instead of the full name'), - category=_('Authentication'), - category_slug='authentication', -) + register( + 'SOCIAL_AUTH_USER_FIELDS', + field_class=fields.StringListField, + allow_null=True, + default=None, + label=_('Social Auth User Fields'), + help_text=_( + 'When set to an empty list `[]`, this setting prevents new user ' + 'accounts from being created. Only users who have previously ' + 'logged in using social auth or have a user account with a ' + 'matching email address will be able to login.' + ), + category=_('Authentication'), + category_slug='authentication', + placeholder=['username', 'email'], + ) + + register( + 'SOCIAL_AUTH_USERNAME_IS_FULL_EMAIL', + field_class=fields.BooleanField, + default=False, + label=_('Use Email address for usernames'), + help_text=_('Enabling this setting will tell social auth to use the full Email as username instead of the full name'), + category=_('Authentication'), + category_slug='authentication', + ) -############################################################################### -# LDAP AUTHENTICATION SETTINGS -############################################################################### + ############################################################################### + # LDAP AUTHENTICATION SETTINGS + ############################################################################### + + def _register_ldap(append=None): + append_str = '_{}'.format(append) if append else '' + + register( + 'AUTH_LDAP{}_SERVER_URI'.format(append_str), + field_class=LDAPServerURIField, + allow_blank=True, + default='', + label=_('LDAP Server URI'), + help_text=_( + 'URI to connect to LDAP server, such as "ldap://ldap.example.com:389" ' + '(non-SSL) or "ldaps://ldap.example.com:636" (SSL). Multiple LDAP ' + 'servers may be specified by separating with spaces or commas. LDAP ' + 'authentication is disabled if this parameter is empty.' + ), + category=_('LDAP'), + category_slug='ldap', + placeholder='ldaps://ldap.example.com:636', + ) + + register( + 'AUTH_LDAP{}_BIND_DN'.format(append_str), + field_class=fields.CharField, + allow_blank=True, + default='', + validators=[validate_ldap_bind_dn], + label=_('LDAP Bind DN'), + help_text=_( + 'DN (Distinguished Name) of user to bind for all search queries. This' + ' is the system user account we will use to login to query LDAP for other' + ' user information. Refer to the documentation for example syntax.' + ), + category=_('LDAP'), + category_slug='ldap', + ) + + register( + 'AUTH_LDAP{}_BIND_PASSWORD'.format(append_str), + field_class=fields.CharField, + allow_blank=True, + default='', + label=_('LDAP Bind Password'), + help_text=_('Password used to bind LDAP user account.'), + category=_('LDAP'), + category_slug='ldap', + encrypted=True, + ) + + register( + 'AUTH_LDAP{}_START_TLS'.format(append_str), + field_class=fields.BooleanField, + default=False, + label=_('LDAP Start TLS'), + help_text=_('Whether to enable TLS when the LDAP connection is not using SSL.'), + category=_('LDAP'), + category_slug='ldap', + ) + + register( + 'AUTH_LDAP{}_CONNECTION_OPTIONS'.format(append_str), + field_class=LDAPConnectionOptionsField, + default={'OPT_REFERRALS': 0, 'OPT_NETWORK_TIMEOUT': 30}, + label=_('LDAP Connection Options'), + help_text=_( + 'Additional options to set for the LDAP connection. LDAP ' + 'referrals are disabled by default (to prevent certain LDAP ' + 'queries from hanging with AD). Option names should be strings ' + '(e.g. "OPT_REFERRALS"). Refer to ' + 'https://www.python-ldap.org/doc/html/ldap.html#options for ' + 'possible options and values that can be set.' + ), + category=_('LDAP'), + category_slug='ldap', + placeholder=collections.OrderedDict([('OPT_REFERRALS', 0), ('OPT_NETWORK_TIMEOUT', 30)]), + ) + + register( + 'AUTH_LDAP{}_USER_SEARCH'.format(append_str), + field_class=LDAPSearchUnionField, + default=[], + label=_('LDAP User Search'), + help_text=_( + 'LDAP search query to find users. Any user that matches the given ' + 'pattern will be able to login to the service. The user should also be ' + 'mapped into an organization (as defined in the ' + 'AUTH_LDAP_ORGANIZATION_MAP setting). If multiple search queries ' + 'need to be supported use of "LDAPUnion" is possible. See ' + 'the documentation for details.' + ), + category=_('LDAP'), + category_slug='ldap', + placeholder=('OU=Users,DC=example,DC=com', 'SCOPE_SUBTREE', '(sAMAccountName=%(user)s)'), + ) + + register( + 'AUTH_LDAP{}_USER_DN_TEMPLATE'.format(append_str), + field_class=LDAPDNWithUserField, + allow_blank=True, + allow_null=True, + default=None, + label=_('LDAP User DN Template'), + help_text=_( + 'Alternative to user search, if user DNs are all of the same ' + 'format. This approach is more efficient for user lookups than ' + 'searching if it is usable in your organizational environment. If ' + 'this setting has a value it will be used instead of ' + 'AUTH_LDAP_USER_SEARCH.' + ), + category=_('LDAP'), + category_slug='ldap', + placeholder='uid=%(user)s,OU=Users,DC=example,DC=com', + ) + + register( + 'AUTH_LDAP{}_USER_ATTR_MAP'.format(append_str), + field_class=LDAPUserAttrMapField, + default={}, + label=_('LDAP User Attribute Map'), + help_text=_( + 'Mapping of LDAP user schema to API user attributes. The default' + ' setting is valid for ActiveDirectory but users with other LDAP' + ' configurations may need to change the values. Refer to the' + ' documentation for additional details.' + ), + category=_('LDAP'), + category_slug='ldap', + placeholder=collections.OrderedDict([('first_name', 'givenName'), ('last_name', 'sn'), ('email', 'mail')]), + ) + + register( + 'AUTH_LDAP{}_GROUP_SEARCH'.format(append_str), + field_class=LDAPSearchField, + default=[], + label=_('LDAP Group Search'), + help_text=_( + 'Users are mapped to organizations based on their membership in LDAP' + ' groups. This setting defines the LDAP search query to find groups. ' + 'Unlike the user search, group search does not support LDAPSearchUnion.' + ), + category=_('LDAP'), + category_slug='ldap', + placeholder=('DC=example,DC=com', 'SCOPE_SUBTREE', '(objectClass=group)'), + ) + + register( + 'AUTH_LDAP{}_GROUP_TYPE'.format(append_str), + field_class=LDAPGroupTypeField, + label=_('LDAP Group Type'), + help_text=_( + 'The group type may need to be changed based on the type of the ' + 'LDAP server. Values are listed at: ' + 'https://django-auth-ldap.readthedocs.io/en/stable/groups.html#types-of-groups' + ), + category=_('LDAP'), + category_slug='ldap', + default='MemberDNGroupType', + depends_on=['AUTH_LDAP{}_GROUP_TYPE_PARAMS'.format(append_str)], + ) + + register( + 'AUTH_LDAP{}_GROUP_TYPE_PARAMS'.format(append_str), + field_class=LDAPGroupTypeParamsField, + label=_('LDAP Group Type Parameters'), + help_text=_('Key value parameters to send the chosen group type init method.'), + category=_('LDAP'), + category_slug='ldap', + default=collections.OrderedDict([('member_attr', 'member'), ('name_attr', 'cn')]), + placeholder=collections.OrderedDict([('ldap_group_user_attr', 'legacyuid'), ('member_attr', 'member'), ('name_attr', 'cn')]), + depends_on=['AUTH_LDAP{}_GROUP_TYPE'.format(append_str)], + ) + + register( + 'AUTH_LDAP{}_REQUIRE_GROUP'.format(append_str), + field_class=LDAPDNField, + allow_blank=True, + allow_null=True, + default=None, + label=_('LDAP Require Group'), + help_text=_( + 'Group DN required to login. If specified, user must be a member ' + 'of this group to login via LDAP. If not set, everyone in LDAP ' + 'that matches the user search will be able to login to the service. ' + 'Only one require group is supported.' + ), + category=_('LDAP'), + category_slug='ldap', + placeholder='CN=Service Users,OU=Users,DC=example,DC=com', + ) + + register( + 'AUTH_LDAP{}_DENY_GROUP'.format(append_str), + field_class=LDAPDNField, + allow_blank=True, + allow_null=True, + default=None, + label=_('LDAP Deny Group'), + help_text=_( + 'Group DN denied from login. If specified, user will not be allowed to login if a member of this group. Only one deny group is supported.' + ), + category=_('LDAP'), + category_slug='ldap', + placeholder='CN=Disabled Users,OU=Users,DC=example,DC=com', + ) + + register( + 'AUTH_LDAP{}_USER_FLAGS_BY_GROUP'.format(append_str), + field_class=LDAPUserFlagsField, + default={}, + label=_('LDAP User Flags By Group'), + help_text=_( + 'Retrieve users from a given group. At this time, superuser and system' + ' auditors are the only groups supported. Refer to the' + ' documentation for more detail.' + ), + category=_('LDAP'), + category_slug='ldap', + placeholder=collections.OrderedDict( + [('is_superuser', 'CN=Domain Admins,CN=Users,DC=example,DC=com'), ('is_system_auditor', 'CN=Domain Auditors,CN=Users,DC=example,DC=com')] + ), + ) + + register( + 'AUTH_LDAP{}_ORGANIZATION_MAP'.format(append_str), + field_class=LDAPOrganizationMapField, + default={}, + label=_('LDAP Organization Map'), + help_text=_( + 'Mapping between organization admins/users and LDAP groups. This ' + 'controls which users are placed into which organizations ' + 'relative to their LDAP group memberships. Configuration details ' + 'are available in the documentation.' + ), + category=_('LDAP'), + category_slug='ldap', + placeholder=collections.OrderedDict( + [ + ( + 'Test Org', + collections.OrderedDict( + [ + ('admins', 'CN=Domain Admins,CN=Users,DC=example,DC=com'), + ('auditors', 'CN=Domain Auditors,CN=Users,DC=example,DC=com'), + ('users', ['CN=Domain Users,CN=Users,DC=example,DC=com']), + ('remove_users', True), + ('remove_admins', True), + ] + ), + ), + ( + 'Test Org 2', + collections.OrderedDict( + [('admins', 'CN=Administrators,CN=Builtin,DC=example,DC=com'), ('users', True), ('remove_users', True), ('remove_admins', True)] + ), + ), + ] + ), + ) + + register( + 'AUTH_LDAP{}_TEAM_MAP'.format(append_str), + field_class=LDAPTeamMapField, + default={}, + label=_('LDAP Team Map'), + help_text=_('Mapping between team members (users) and LDAP groups. Configuration details are available in the documentation.'), + category=_('LDAP'), + category_slug='ldap', + placeholder=collections.OrderedDict( + [ + ( + 'My Team', + collections.OrderedDict([('organization', 'Test Org'), ('users', ['CN=Domain Users,CN=Users,DC=example,DC=com']), ('remove', True)]), + ), + ( + 'Other Team', + collections.OrderedDict([('organization', 'Test Org 2'), ('users', 'CN=Other Users,CN=Users,DC=example,DC=com'), ('remove', False)]), + ), + ] + ), + ) + _register_ldap() + _register_ldap('1') + _register_ldap('2') + _register_ldap('3') + _register_ldap('4') + _register_ldap('5') -def _register_ldap(append=None): - append_str = '_{}'.format(append) if append else '' + ############################################################################### + # RADIUS AUTHENTICATION SETTINGS + ############################################################################### register( - 'AUTH_LDAP{}_SERVER_URI'.format(append_str), - field_class=LDAPServerURIField, + 'RADIUS_SERVER', + field_class=fields.CharField, allow_blank=True, default='', - label=_('LDAP Server URI'), - help_text=_( - 'URI to connect to LDAP server, such as "ldap://ldap.example.com:389" ' - '(non-SSL) or "ldaps://ldap.example.com:636" (SSL). Multiple LDAP ' - 'servers may be specified by separating with spaces or commas. LDAP ' - 'authentication is disabled if this parameter is empty.' - ), - category=_('LDAP'), - category_slug='ldap', - placeholder='ldaps://ldap.example.com:636', + label=_('RADIUS Server'), + help_text=_('Hostname/IP of RADIUS server. RADIUS authentication is disabled if this setting is empty.'), + category=_('RADIUS'), + category_slug='radius', + placeholder='radius.example.com', ) register( - 'AUTH_LDAP{}_BIND_DN'.format(append_str), + 'RADIUS_PORT', + field_class=fields.IntegerField, + min_value=1, + max_value=65535, + default=1812, + label=_('RADIUS Port'), + help_text=_('Port of RADIUS server.'), + category=_('RADIUS'), + category_slug='radius', + ) + + register( + 'RADIUS_SECRET', field_class=fields.CharField, allow_blank=True, default='', - validators=[validate_ldap_bind_dn], - label=_('LDAP Bind DN'), - help_text=_( - 'DN (Distinguished Name) of user to bind for all search queries. This' - ' is the system user account we will use to login to query LDAP for other' - ' user information. Refer to the documentation for example syntax.' - ), - category=_('LDAP'), - category_slug='ldap', + label=_('RADIUS Secret'), + help_text=_('Shared secret for authenticating to RADIUS server.'), + category=_('RADIUS'), + category_slug='radius', + encrypted=True, + ) + + ############################################################################### + # TACACSPLUS AUTHENTICATION SETTINGS + ############################################################################### + + register( + 'TACACSPLUS_HOST', + field_class=fields.CharField, + allow_blank=True, + default='', + label=_('TACACS+ Server'), + help_text=_('Hostname of TACACS+ server.'), + category=_('TACACS+'), + category_slug='tacacsplus', + ) + + register( + 'TACACSPLUS_PORT', + field_class=fields.IntegerField, + min_value=1, + max_value=65535, + default=49, + label=_('TACACS+ Port'), + help_text=_('Port number of TACACS+ server.'), + category=_('TACACS+'), + category_slug='tacacsplus', ) register( - 'AUTH_LDAP{}_BIND_PASSWORD'.format(append_str), + 'TACACSPLUS_SECRET', field_class=fields.CharField, allow_blank=True, default='', - label=_('LDAP Bind Password'), - help_text=_('Password used to bind LDAP user account.'), - category=_('LDAP'), - category_slug='ldap', + validators=[validate_tacacsplus_disallow_nonascii], + label=_('TACACS+ Secret'), + help_text=_('Shared secret for authenticating to TACACS+ server.'), + category=_('TACACS+'), + category_slug='tacacsplus', encrypted=True, ) register( - 'AUTH_LDAP{}_START_TLS'.format(append_str), + 'TACACSPLUS_SESSION_TIMEOUT', + field_class=fields.IntegerField, + min_value=0, + default=5, + label=_('TACACS+ Auth Session Timeout'), + help_text=_('TACACS+ session timeout value in seconds, 0 disables timeout.'), + category=_('TACACS+'), + category_slug='tacacsplus', + unit=_('seconds'), + ) + + register( + 'TACACSPLUS_AUTH_PROTOCOL', + field_class=fields.ChoiceField, + choices=['ascii', 'pap'], + default='ascii', + label=_('TACACS+ Authentication Protocol'), + help_text=_('Choose the authentication protocol used by TACACS+ client.'), + category=_('TACACS+'), + category_slug='tacacsplus', + ) + + register( + 'TACACSPLUS_REM_ADDR', field_class=fields.BooleanField, - default=False, - label=_('LDAP Start TLS'), - help_text=_('Whether to enable TLS when the LDAP connection is not using SSL.'), - category=_('LDAP'), - category_slug='ldap', + default=True, + label=_('TACACS+ client address sending enabled'), + help_text=_('Enable the client address sending by TACACS+ client.'), + category=_('TACACS+'), + category_slug='tacacsplus', ) + ############################################################################### + # GOOGLE OAUTH2 AUTHENTICATION SETTINGS + ############################################################################### + register( - 'AUTH_LDAP{}_CONNECTION_OPTIONS'.format(append_str), - field_class=LDAPConnectionOptionsField, - default={'OPT_REFERRALS': 0, 'OPT_NETWORK_TIMEOUT': 30}, - label=_('LDAP Connection Options'), + 'SOCIAL_AUTH_GOOGLE_OAUTH2_CALLBACK_URL', + field_class=fields.CharField, + read_only=True, + default=SocialAuthCallbackURL('google-oauth2'), + label=_('Google OAuth2 Callback URL'), help_text=_( - 'Additional options to set for the LDAP connection. LDAP ' - 'referrals are disabled by default (to prevent certain LDAP ' - 'queries from hanging with AD). Option names should be strings ' - '(e.g. "OPT_REFERRALS"). Refer to ' - 'https://www.python-ldap.org/doc/html/ldap.html#options for ' - 'possible options and values that can be set.' + 'Provide this URL as the callback URL for your application as part of your registration process. Refer to the documentation for more detail.' ), - category=_('LDAP'), - category_slug='ldap', - placeholder=collections.OrderedDict([('OPT_REFERRALS', 0), ('OPT_NETWORK_TIMEOUT', 30)]), + category=_('Google OAuth2'), + category_slug='google-oauth2', + depends_on=['TOWER_URL_BASE'], ) register( - 'AUTH_LDAP{}_USER_SEARCH'.format(append_str), - field_class=LDAPSearchUnionField, - default=[], - label=_('LDAP User Search'), - help_text=_( - 'LDAP search query to find users. Any user that matches the given ' - 'pattern will be able to login to the service. The user should also be ' - 'mapped into an organization (as defined in the ' - 'AUTH_LDAP_ORGANIZATION_MAP setting). If multiple search queries ' - 'need to be supported use of "LDAPUnion" is possible. See ' - 'the documentation for details.' - ), - category=_('LDAP'), - category_slug='ldap', - placeholder=('OU=Users,DC=example,DC=com', 'SCOPE_SUBTREE', '(sAMAccountName=%(user)s)'), + 'SOCIAL_AUTH_GOOGLE_OAUTH2_KEY', + field_class=fields.CharField, + allow_blank=True, + default='', + label=_('Google OAuth2 Key'), + help_text=_('The OAuth2 key from your web application.'), + category=_('Google OAuth2'), + category_slug='google-oauth2', + placeholder='528620852399-gm2dt4hrl2tsj67fqamk09k1e0ad6gd8.apps.googleusercontent.com', ) register( - 'AUTH_LDAP{}_USER_DN_TEMPLATE'.format(append_str), - field_class=LDAPDNWithUserField, + 'SOCIAL_AUTH_GOOGLE_OAUTH2_SECRET', + field_class=fields.CharField, allow_blank=True, - allow_null=True, - default=None, - label=_('LDAP User DN Template'), - help_text=_( - 'Alternative to user search, if user DNs are all of the same ' - 'format. This approach is more efficient for user lookups than ' - 'searching if it is usable in your organizational environment. If ' - 'this setting has a value it will be used instead of ' - 'AUTH_LDAP_USER_SEARCH.' - ), - category=_('LDAP'), - category_slug='ldap', - placeholder='uid=%(user)s,OU=Users,DC=example,DC=com', + default='', + label=_('Google OAuth2 Secret'), + help_text=_('The OAuth2 secret from your web application.'), + category=_('Google OAuth2'), + category_slug='google-oauth2', + placeholder='q2fMVCmEregbg-drvebPp8OW', + encrypted=True, + ) + + register( + 'SOCIAL_AUTH_GOOGLE_OAUTH2_WHITELISTED_DOMAINS', + field_class=fields.StringListField, + default=[], + label=_('Google OAuth2 Allowed Domains'), + help_text=_('Update this setting to restrict the domains who are allowed to login using Google OAuth2.'), + category=_('Google OAuth2'), + category_slug='google-oauth2', + placeholder=['example.com'], ) register( - 'AUTH_LDAP{}_USER_ATTR_MAP'.format(append_str), - field_class=LDAPUserAttrMapField, + 'SOCIAL_AUTH_GOOGLE_OAUTH2_AUTH_EXTRA_ARGUMENTS', + field_class=fields.DictField, default={}, - label=_('LDAP User Attribute Map'), + label=_('Google OAuth2 Extra Arguments'), help_text=_( - 'Mapping of LDAP user schema to API user attributes. The default' - ' setting is valid for ActiveDirectory but users with other LDAP' - ' configurations may need to change the values. Refer to the' - ' documentation for additional details.' + 'Extra arguments for Google OAuth2 login. You can restrict it to' + ' only allow a single domain to authenticate, even if the user is' + ' logged in with multple Google accounts. Refer to the' + ' documentation for more detail.' ), - category=_('LDAP'), - category_slug='ldap', - placeholder=collections.OrderedDict([('first_name', 'givenName'), ('last_name', 'sn'), ('email', 'mail')]), + category=_('Google OAuth2'), + category_slug='google-oauth2', + placeholder={'hd': 'example.com'}, ) register( - 'AUTH_LDAP{}_GROUP_SEARCH'.format(append_str), - field_class=LDAPSearchField, - default=[], - label=_('LDAP Group Search'), - help_text=_( - 'Users are mapped to organizations based on their membership in LDAP' - ' groups. This setting defines the LDAP search query to find groups. ' - 'Unlike the user search, group search does not support LDAPSearchUnion.' - ), - category=_('LDAP'), - category_slug='ldap', - placeholder=('DC=example,DC=com', 'SCOPE_SUBTREE', '(objectClass=group)'), + 'SOCIAL_AUTH_GOOGLE_OAUTH2_ORGANIZATION_MAP', + field_class=SocialOrganizationMapField, + allow_null=True, + default=None, + label=_('Google OAuth2 Organization Map'), + help_text=SOCIAL_AUTH_ORGANIZATION_MAP_HELP_TEXT, + category=_('Google OAuth2'), + category_slug='google-oauth2', + placeholder=SOCIAL_AUTH_ORGANIZATION_MAP_PLACEHOLDER, + ) + + register( + 'SOCIAL_AUTH_GOOGLE_OAUTH2_TEAM_MAP', + field_class=SocialTeamMapField, + allow_null=True, + default=None, + label=_('Google OAuth2 Team Map'), + help_text=SOCIAL_AUTH_TEAM_MAP_HELP_TEXT, + category=_('Google OAuth2'), + category_slug='google-oauth2', + placeholder=SOCIAL_AUTH_TEAM_MAP_PLACEHOLDER, ) + ############################################################################### + # GITHUB OAUTH2 AUTHENTICATION SETTINGS + ############################################################################### + register( - 'AUTH_LDAP{}_GROUP_TYPE'.format(append_str), - field_class=LDAPGroupTypeField, - label=_('LDAP Group Type'), + 'SOCIAL_AUTH_GITHUB_CALLBACK_URL', + field_class=fields.CharField, + read_only=True, + default=SocialAuthCallbackURL('github'), + label=_('GitHub OAuth2 Callback URL'), help_text=_( - 'The group type may need to be changed based on the type of the ' - 'LDAP server. Values are listed at: ' - 'https://django-auth-ldap.readthedocs.io/en/stable/groups.html#types-of-groups' + 'Provide this URL as the callback URL for your application as part of your registration process. Refer to the documentation for more detail.' ), - category=_('LDAP'), - category_slug='ldap', - default='MemberDNGroupType', - depends_on=['AUTH_LDAP{}_GROUP_TYPE_PARAMS'.format(append_str)], + category=_('GitHub OAuth2'), + category_slug='github', + depends_on=['TOWER_URL_BASE'], ) register( - 'AUTH_LDAP{}_GROUP_TYPE_PARAMS'.format(append_str), - field_class=LDAPGroupTypeParamsField, - label=_('LDAP Group Type Parameters'), - help_text=_('Key value parameters to send the chosen group type init method.'), - category=_('LDAP'), - category_slug='ldap', - default=collections.OrderedDict([('member_attr', 'member'), ('name_attr', 'cn')]), - placeholder=collections.OrderedDict([('ldap_group_user_attr', 'legacyuid'), ('member_attr', 'member'), ('name_attr', 'cn')]), - depends_on=['AUTH_LDAP{}_GROUP_TYPE'.format(append_str)], + 'SOCIAL_AUTH_GITHUB_KEY', + field_class=fields.CharField, + allow_blank=True, + default='', + label=_('GitHub OAuth2 Key'), + help_text=_('The OAuth2 key (Client ID) from your GitHub developer application.'), + category=_('GitHub OAuth2'), + category_slug='github', ) register( - 'AUTH_LDAP{}_REQUIRE_GROUP'.format(append_str), - field_class=LDAPDNField, + 'SOCIAL_AUTH_GITHUB_SECRET', + field_class=fields.CharField, allow_blank=True, + default='', + label=_('GitHub OAuth2 Secret'), + help_text=_('The OAuth2 secret (Client Secret) from your GitHub developer application.'), + category=_('GitHub OAuth2'), + category_slug='github', + encrypted=True, + ) + + register( + 'SOCIAL_AUTH_GITHUB_ORGANIZATION_MAP', + field_class=SocialOrganizationMapField, allow_null=True, default=None, - label=_('LDAP Require Group'), - help_text=_( - 'Group DN required to login. If specified, user must be a member ' - 'of this group to login via LDAP. If not set, everyone in LDAP ' - 'that matches the user search will be able to login to the service. ' - 'Only one require group is supported.' - ), - category=_('LDAP'), - category_slug='ldap', - placeholder='CN=Service Users,OU=Users,DC=example,DC=com', + label=_('GitHub OAuth2 Organization Map'), + help_text=SOCIAL_AUTH_ORGANIZATION_MAP_HELP_TEXT, + category=_('GitHub OAuth2'), + category_slug='github', + placeholder=SOCIAL_AUTH_ORGANIZATION_MAP_PLACEHOLDER, ) register( - 'AUTH_LDAP{}_DENY_GROUP'.format(append_str), - field_class=LDAPDNField, - allow_blank=True, + 'SOCIAL_AUTH_GITHUB_TEAM_MAP', + field_class=SocialTeamMapField, allow_null=True, default=None, - label=_('LDAP Deny Group'), - help_text=_( - 'Group DN denied from login. If specified, user will not be allowed to login if a member of this group. Only one deny group is supported.' - ), - category=_('LDAP'), - category_slug='ldap', - placeholder='CN=Disabled Users,OU=Users,DC=example,DC=com', + label=_('GitHub OAuth2 Team Map'), + help_text=SOCIAL_AUTH_TEAM_MAP_HELP_TEXT, + category=_('GitHub OAuth2'), + category_slug='github', + placeholder=SOCIAL_AUTH_TEAM_MAP_PLACEHOLDER, ) + ############################################################################### + # GITHUB ORG OAUTH2 AUTHENTICATION SETTINGS + ############################################################################### + register( - 'AUTH_LDAP{}_USER_FLAGS_BY_GROUP'.format(append_str), - field_class=LDAPUserFlagsField, - default={}, - label=_('LDAP User Flags By Group'), + 'SOCIAL_AUTH_GITHUB_ORG_CALLBACK_URL', + field_class=fields.CharField, + read_only=True, + default=SocialAuthCallbackURL('github-org'), + label=_('GitHub Organization OAuth2 Callback URL'), help_text=_( - 'Retrieve users from a given group. At this time, superuser and system' - ' auditors are the only groups supported. Refer to the' - ' documentation for more detail.' - ), - category=_('LDAP'), - category_slug='ldap', - placeholder=collections.OrderedDict( - [('is_superuser', 'CN=Domain Admins,CN=Users,DC=example,DC=com'), ('is_system_auditor', 'CN=Domain Auditors,CN=Users,DC=example,DC=com')] + 'Provide this URL as the callback URL for your application as part of your registration process. Refer to the documentation for more detail.' ), + category=_('GitHub Organization OAuth2'), + category_slug='github-org', + depends_on=['TOWER_URL_BASE'], ) register( - 'AUTH_LDAP{}_ORGANIZATION_MAP'.format(append_str), - field_class=LDAPOrganizationMapField, - default={}, - label=_('LDAP Organization Map'), - help_text=_( - 'Mapping between organization admins/users and LDAP groups. This ' - 'controls which users are placed into which organizations ' - 'relative to their LDAP group memberships. Configuration details ' - 'are available in the documentation.' - ), - category=_('LDAP'), - category_slug='ldap', - placeholder=collections.OrderedDict( - [ - ( - 'Test Org', - collections.OrderedDict( - [ - ('admins', 'CN=Domain Admins,CN=Users,DC=example,DC=com'), - ('auditors', 'CN=Domain Auditors,CN=Users,DC=example,DC=com'), - ('users', ['CN=Domain Users,CN=Users,DC=example,DC=com']), - ('remove_users', True), - ('remove_admins', True), - ] - ), - ), - ( - 'Test Org 2', - collections.OrderedDict( - [('admins', 'CN=Administrators,CN=Builtin,DC=example,DC=com'), ('users', True), ('remove_users', True), ('remove_admins', True)] - ), - ), - ] - ), + 'SOCIAL_AUTH_GITHUB_ORG_KEY', + field_class=fields.CharField, + allow_blank=True, + default='', + label=_('GitHub Organization OAuth2 Key'), + help_text=_('The OAuth2 key (Client ID) from your GitHub organization application.'), + category=_('GitHub Organization OAuth2'), + category_slug='github-org', ) register( - 'AUTH_LDAP{}_TEAM_MAP'.format(append_str), - field_class=LDAPTeamMapField, - default={}, - label=_('LDAP Team Map'), - help_text=_('Mapping between team members (users) and LDAP groups. Configuration details are available in the documentation.'), - category=_('LDAP'), - category_slug='ldap', - placeholder=collections.OrderedDict( - [ - ( - 'My Team', - collections.OrderedDict([('organization', 'Test Org'), ('users', ['CN=Domain Users,CN=Users,DC=example,DC=com']), ('remove', True)]), - ), - ( - 'Other Team', - collections.OrderedDict([('organization', 'Test Org 2'), ('users', 'CN=Other Users,CN=Users,DC=example,DC=com'), ('remove', False)]), - ), - ] - ), + 'SOCIAL_AUTH_GITHUB_ORG_SECRET', + field_class=fields.CharField, + allow_blank=True, + default='', + label=_('GitHub Organization OAuth2 Secret'), + help_text=_('The OAuth2 secret (Client Secret) from your GitHub organization application.'), + category=_('GitHub Organization OAuth2'), + category_slug='github-org', + encrypted=True, ) + register( + 'SOCIAL_AUTH_GITHUB_ORG_NAME', + field_class=fields.CharField, + allow_blank=True, + default='', + label=_('GitHub Organization Name'), + help_text=_('The name of your GitHub organization, as used in your organization\'s URL: https://github.com//.'), + category=_('GitHub Organization OAuth2'), + category_slug='github-org', + ) -_register_ldap() -_register_ldap('1') -_register_ldap('2') -_register_ldap('3') -_register_ldap('4') -_register_ldap('5') - -############################################################################### -# RADIUS AUTHENTICATION SETTINGS -############################################################################### - -register( - 'RADIUS_SERVER', - field_class=fields.CharField, - allow_blank=True, - default='', - label=_('RADIUS Server'), - help_text=_('Hostname/IP of RADIUS server. RADIUS authentication is disabled if this setting is empty.'), - category=_('RADIUS'), - category_slug='radius', - placeholder='radius.example.com', -) + register( + 'SOCIAL_AUTH_GITHUB_ORG_ORGANIZATION_MAP', + field_class=SocialOrganizationMapField, + allow_null=True, + default=None, + label=_('GitHub Organization OAuth2 Organization Map'), + help_text=SOCIAL_AUTH_ORGANIZATION_MAP_HELP_TEXT, + category=_('GitHub Organization OAuth2'), + category_slug='github-org', + placeholder=SOCIAL_AUTH_ORGANIZATION_MAP_PLACEHOLDER, + ) -register( - 'RADIUS_PORT', - field_class=fields.IntegerField, - min_value=1, - max_value=65535, - default=1812, - label=_('RADIUS Port'), - help_text=_('Port of RADIUS server.'), - category=_('RADIUS'), - category_slug='radius', -) + register( + 'SOCIAL_AUTH_GITHUB_ORG_TEAM_MAP', + field_class=SocialTeamMapField, + allow_null=True, + default=None, + label=_('GitHub Organization OAuth2 Team Map'), + help_text=SOCIAL_AUTH_TEAM_MAP_HELP_TEXT, + category=_('GitHub Organization OAuth2'), + category_slug='github-org', + placeholder=SOCIAL_AUTH_TEAM_MAP_PLACEHOLDER, + ) -register( - 'RADIUS_SECRET', - field_class=fields.CharField, - allow_blank=True, - default='', - label=_('RADIUS Secret'), - help_text=_('Shared secret for authenticating to RADIUS server.'), - category=_('RADIUS'), - category_slug='radius', - encrypted=True, -) + ############################################################################### + # GITHUB TEAM OAUTH2 AUTHENTICATION SETTINGS + ############################################################################### -############################################################################### -# TACACSPLUS AUTHENTICATION SETTINGS -############################################################################### - -register( - 'TACACSPLUS_HOST', - field_class=fields.CharField, - allow_blank=True, - default='', - label=_('TACACS+ Server'), - help_text=_('Hostname of TACACS+ server.'), - category=_('TACACS+'), - category_slug='tacacsplus', -) + register( + 'SOCIAL_AUTH_GITHUB_TEAM_CALLBACK_URL', + field_class=fields.CharField, + read_only=True, + default=SocialAuthCallbackURL('github-team'), + label=_('GitHub Team OAuth2 Callback URL'), + help_text=_( + 'Create an organization-owned application at ' + 'https://github.com/organizations//settings/applications ' + 'and obtain an OAuth2 key (Client ID) and secret (Client Secret). ' + 'Provide this URL as the callback URL for your application.' + ), + category=_('GitHub Team OAuth2'), + category_slug='github-team', + depends_on=['TOWER_URL_BASE'], + ) -register( - 'TACACSPLUS_PORT', - field_class=fields.IntegerField, - min_value=1, - max_value=65535, - default=49, - label=_('TACACS+ Port'), - help_text=_('Port number of TACACS+ server.'), - category=_('TACACS+'), - category_slug='tacacsplus', -) + register( + 'SOCIAL_AUTH_GITHUB_TEAM_KEY', + field_class=fields.CharField, + allow_blank=True, + default='', + label=_('GitHub Team OAuth2 Key'), + help_text=_('The OAuth2 key (Client ID) from your GitHub organization application.'), + category=_('GitHub Team OAuth2'), + category_slug='github-team', + ) -register( - 'TACACSPLUS_SECRET', - field_class=fields.CharField, - allow_blank=True, - default='', - validators=[validate_tacacsplus_disallow_nonascii], - label=_('TACACS+ Secret'), - help_text=_('Shared secret for authenticating to TACACS+ server.'), - category=_('TACACS+'), - category_slug='tacacsplus', - encrypted=True, -) + register( + 'SOCIAL_AUTH_GITHUB_TEAM_SECRET', + field_class=fields.CharField, + allow_blank=True, + default='', + label=_('GitHub Team OAuth2 Secret'), + help_text=_('The OAuth2 secret (Client Secret) from your GitHub organization application.'), + category=_('GitHub Team OAuth2'), + category_slug='github-team', + encrypted=True, + ) -register( - 'TACACSPLUS_SESSION_TIMEOUT', - field_class=fields.IntegerField, - min_value=0, - default=5, - label=_('TACACS+ Auth Session Timeout'), - help_text=_('TACACS+ session timeout value in seconds, 0 disables timeout.'), - category=_('TACACS+'), - category_slug='tacacsplus', - unit=_('seconds'), -) + register( + 'SOCIAL_AUTH_GITHUB_TEAM_ID', + field_class=fields.CharField, + allow_blank=True, + default='', + label=_('GitHub Team ID'), + help_text=_('Find the numeric team ID using the Github API: http://fabian-kostadinov.github.io/2015/01/16/how-to-find-a-github-team-id/.'), + category=_('GitHub Team OAuth2'), + category_slug='github-team', + ) -register( - 'TACACSPLUS_AUTH_PROTOCOL', - field_class=fields.ChoiceField, - choices=['ascii', 'pap'], - default='ascii', - label=_('TACACS+ Authentication Protocol'), - help_text=_('Choose the authentication protocol used by TACACS+ client.'), - category=_('TACACS+'), - category_slug='tacacsplus', -) + register( + 'SOCIAL_AUTH_GITHUB_TEAM_ORGANIZATION_MAP', + field_class=SocialOrganizationMapField, + allow_null=True, + default=None, + label=_('GitHub Team OAuth2 Organization Map'), + help_text=SOCIAL_AUTH_ORGANIZATION_MAP_HELP_TEXT, + category=_('GitHub Team OAuth2'), + category_slug='github-team', + placeholder=SOCIAL_AUTH_ORGANIZATION_MAP_PLACEHOLDER, + ) -register( - 'TACACSPLUS_REM_ADDR', - field_class=fields.BooleanField, - default=True, - label=_('TACACS+ client address sending enabled'), - help_text=_('Enable the client address sending by TACACS+ client.'), - category=_('TACACS+'), - category_slug='tacacsplus', -) + register( + 'SOCIAL_AUTH_GITHUB_TEAM_TEAM_MAP', + field_class=SocialTeamMapField, + allow_null=True, + default=None, + label=_('GitHub Team OAuth2 Team Map'), + help_text=SOCIAL_AUTH_TEAM_MAP_HELP_TEXT, + category=_('GitHub Team OAuth2'), + category_slug='github-team', + placeholder=SOCIAL_AUTH_TEAM_MAP_PLACEHOLDER, + ) -############################################################################### -# GOOGLE OAUTH2 AUTHENTICATION SETTINGS -############################################################################### - -register( - 'SOCIAL_AUTH_GOOGLE_OAUTH2_CALLBACK_URL', - field_class=fields.CharField, - read_only=True, - default=SocialAuthCallbackURL('google-oauth2'), - label=_('Google OAuth2 Callback URL'), - help_text=_('Provide this URL as the callback URL for your application as part of your registration process. Refer to the documentation for more detail.'), - category=_('Google OAuth2'), - category_slug='google-oauth2', - depends_on=['TOWER_URL_BASE'], -) + ############################################################################### + # GITHUB ENTERPRISE OAUTH2 AUTHENTICATION SETTINGS + ############################################################################### -register( - 'SOCIAL_AUTH_GOOGLE_OAUTH2_KEY', - field_class=fields.CharField, - allow_blank=True, - default='', - label=_('Google OAuth2 Key'), - help_text=_('The OAuth2 key from your web application.'), - category=_('Google OAuth2'), - category_slug='google-oauth2', - placeholder='528620852399-gm2dt4hrl2tsj67fqamk09k1e0ad6gd8.apps.googleusercontent.com', -) + register( + 'SOCIAL_AUTH_GITHUB_ENTERPRISE_CALLBACK_URL', + field_class=fields.CharField, + read_only=True, + default=SocialAuthCallbackURL('github-enterprise'), + label=_('GitHub Enterprise OAuth2 Callback URL'), + help_text=_( + 'Provide this URL as the callback URL for your application as part of your registration process. Refer to the documentation for more detail.' + ), + category=_('GitHub Enterprise OAuth2'), + category_slug='github-enterprise', + depends_on=['TOWER_URL_BASE'], + ) -register( - 'SOCIAL_AUTH_GOOGLE_OAUTH2_SECRET', - field_class=fields.CharField, - allow_blank=True, - default='', - label=_('Google OAuth2 Secret'), - help_text=_('The OAuth2 secret from your web application.'), - category=_('Google OAuth2'), - category_slug='google-oauth2', - placeholder='q2fMVCmEregbg-drvebPp8OW', - encrypted=True, -) + register( + 'SOCIAL_AUTH_GITHUB_ENTERPRISE_URL', + field_class=fields.CharField, + allow_blank=True, + default='', + label=_('GitHub Enterprise URL'), + help_text=_('The URL for your Github Enterprise instance, e.g.: http(s)://hostname/. Refer to Github Enterprise documentation for more details.'), + category=_('GitHub Enterprise OAuth2'), + category_slug='github-enterprise', + ) -register( - 'SOCIAL_AUTH_GOOGLE_OAUTH2_WHITELISTED_DOMAINS', - field_class=fields.StringListField, - default=[], - label=_('Google OAuth2 Allowed Domains'), - help_text=_('Update this setting to restrict the domains who are allowed to login using Google OAuth2.'), - category=_('Google OAuth2'), - category_slug='google-oauth2', - placeholder=['example.com'], -) + register( + 'SOCIAL_AUTH_GITHUB_ENTERPRISE_API_URL', + field_class=fields.CharField, + allow_blank=True, + default='', + label=_('GitHub Enterprise API URL'), + help_text=_( + 'The API URL for your GitHub Enterprise instance, e.g.: http(s)://hostname/api/v3/. Refer to Github Enterprise documentation for more details.' + ), + category=_('GitHub Enterprise OAuth2'), + category_slug='github-enterprise', + ) -register( - 'SOCIAL_AUTH_GOOGLE_OAUTH2_AUTH_EXTRA_ARGUMENTS', - field_class=fields.DictField, - default={}, - label=_('Google OAuth2 Extra Arguments'), - help_text=_( - 'Extra arguments for Google OAuth2 login. You can restrict it to' - ' only allow a single domain to authenticate, even if the user is' - ' logged in with multple Google accounts. Refer to the' - ' documentation for more detail.' - ), - category=_('Google OAuth2'), - category_slug='google-oauth2', - placeholder={'hd': 'example.com'}, -) + register( + 'SOCIAL_AUTH_GITHUB_ENTERPRISE_KEY', + field_class=fields.CharField, + allow_blank=True, + default='', + label=_('GitHub Enterprise OAuth2 Key'), + help_text=_('The OAuth2 key (Client ID) from your GitHub Enterprise developer application.'), + category=_('GitHub Enterprise OAuth2'), + category_slug='github-enterprise', + ) -register( - 'SOCIAL_AUTH_GOOGLE_OAUTH2_ORGANIZATION_MAP', - field_class=SocialOrganizationMapField, - allow_null=True, - default=None, - label=_('Google OAuth2 Organization Map'), - help_text=SOCIAL_AUTH_ORGANIZATION_MAP_HELP_TEXT, - category=_('Google OAuth2'), - category_slug='google-oauth2', - placeholder=SOCIAL_AUTH_ORGANIZATION_MAP_PLACEHOLDER, -) + register( + 'SOCIAL_AUTH_GITHUB_ENTERPRISE_SECRET', + field_class=fields.CharField, + allow_blank=True, + default='', + label=_('GitHub Enterprise OAuth2 Secret'), + help_text=_('The OAuth2 secret (Client Secret) from your GitHub Enterprise developer application.'), + category=_('GitHub OAuth2'), + category_slug='github-enterprise', + encrypted=True, + ) -register( - 'SOCIAL_AUTH_GOOGLE_OAUTH2_TEAM_MAP', - field_class=SocialTeamMapField, - allow_null=True, - default=None, - label=_('Google OAuth2 Team Map'), - help_text=SOCIAL_AUTH_TEAM_MAP_HELP_TEXT, - category=_('Google OAuth2'), - category_slug='google-oauth2', - placeholder=SOCIAL_AUTH_TEAM_MAP_PLACEHOLDER, -) + register( + 'SOCIAL_AUTH_GITHUB_ENTERPRISE_ORGANIZATION_MAP', + field_class=SocialOrganizationMapField, + allow_null=True, + default=None, + label=_('GitHub Enterprise OAuth2 Organization Map'), + help_text=SOCIAL_AUTH_ORGANIZATION_MAP_HELP_TEXT, + category=_('GitHub Enterprise OAuth2'), + category_slug='github-enterprise', + placeholder=SOCIAL_AUTH_ORGANIZATION_MAP_PLACEHOLDER, + ) -############################################################################### -# GITHUB OAUTH2 AUTHENTICATION SETTINGS -############################################################################### - -register( - 'SOCIAL_AUTH_GITHUB_CALLBACK_URL', - field_class=fields.CharField, - read_only=True, - default=SocialAuthCallbackURL('github'), - label=_('GitHub OAuth2 Callback URL'), - help_text=_('Provide this URL as the callback URL for your application as part of your registration process. Refer to the documentation for more detail.'), - category=_('GitHub OAuth2'), - category_slug='github', - depends_on=['TOWER_URL_BASE'], -) + register( + 'SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_MAP', + field_class=SocialTeamMapField, + allow_null=True, + default=None, + label=_('GitHub Enterprise OAuth2 Team Map'), + help_text=SOCIAL_AUTH_TEAM_MAP_HELP_TEXT, + category=_('GitHub Enterprise OAuth2'), + category_slug='github-enterprise', + placeholder=SOCIAL_AUTH_TEAM_MAP_PLACEHOLDER, + ) -register( - 'SOCIAL_AUTH_GITHUB_KEY', - field_class=fields.CharField, - allow_blank=True, - default='', - label=_('GitHub OAuth2 Key'), - help_text=_('The OAuth2 key (Client ID) from your GitHub developer application.'), - category=_('GitHub OAuth2'), - category_slug='github', -) + ############################################################################### + # GITHUB ENTERPRISE ORG OAUTH2 AUTHENTICATION SETTINGS + ############################################################################### -register( - 'SOCIAL_AUTH_GITHUB_SECRET', - field_class=fields.CharField, - allow_blank=True, - default='', - label=_('GitHub OAuth2 Secret'), - help_text=_('The OAuth2 secret (Client Secret) from your GitHub developer application.'), - category=_('GitHub OAuth2'), - category_slug='github', - encrypted=True, -) - -register( - 'SOCIAL_AUTH_GITHUB_ORGANIZATION_MAP', - field_class=SocialOrganizationMapField, - allow_null=True, - default=None, - label=_('GitHub OAuth2 Organization Map'), - help_text=SOCIAL_AUTH_ORGANIZATION_MAP_HELP_TEXT, - category=_('GitHub OAuth2'), - category_slug='github', - placeholder=SOCIAL_AUTH_ORGANIZATION_MAP_PLACEHOLDER, -) - -register( - 'SOCIAL_AUTH_GITHUB_TEAM_MAP', - field_class=SocialTeamMapField, - allow_null=True, - default=None, - label=_('GitHub OAuth2 Team Map'), - help_text=SOCIAL_AUTH_TEAM_MAP_HELP_TEXT, - category=_('GitHub OAuth2'), - category_slug='github', - placeholder=SOCIAL_AUTH_TEAM_MAP_PLACEHOLDER, -) - -############################################################################### -# GITHUB ORG OAUTH2 AUTHENTICATION SETTINGS -############################################################################### - -register( - 'SOCIAL_AUTH_GITHUB_ORG_CALLBACK_URL', - field_class=fields.CharField, - read_only=True, - default=SocialAuthCallbackURL('github-org'), - label=_('GitHub Organization OAuth2 Callback URL'), - help_text=_('Provide this URL as the callback URL for your application as part of your registration process. Refer to the documentation for more detail.'), - category=_('GitHub Organization OAuth2'), - category_slug='github-org', - depends_on=['TOWER_URL_BASE'], -) - -register( - 'SOCIAL_AUTH_GITHUB_ORG_KEY', - field_class=fields.CharField, - allow_blank=True, - default='', - label=_('GitHub Organization OAuth2 Key'), - help_text=_('The OAuth2 key (Client ID) from your GitHub organization application.'), - category=_('GitHub Organization OAuth2'), - category_slug='github-org', -) - -register( - 'SOCIAL_AUTH_GITHUB_ORG_SECRET', - field_class=fields.CharField, - allow_blank=True, - default='', - label=_('GitHub Organization OAuth2 Secret'), - help_text=_('The OAuth2 secret (Client Secret) from your GitHub organization application.'), - category=_('GitHub Organization OAuth2'), - category_slug='github-org', - encrypted=True, -) - -register( - 'SOCIAL_AUTH_GITHUB_ORG_NAME', - field_class=fields.CharField, - allow_blank=True, - default='', - label=_('GitHub Organization Name'), - help_text=_('The name of your GitHub organization, as used in your organization\'s URL: https://github.com//.'), - category=_('GitHub Organization OAuth2'), - category_slug='github-org', -) - -register( - 'SOCIAL_AUTH_GITHUB_ORG_ORGANIZATION_MAP', - field_class=SocialOrganizationMapField, - allow_null=True, - default=None, - label=_('GitHub Organization OAuth2 Organization Map'), - help_text=SOCIAL_AUTH_ORGANIZATION_MAP_HELP_TEXT, - category=_('GitHub Organization OAuth2'), - category_slug='github-org', - placeholder=SOCIAL_AUTH_ORGANIZATION_MAP_PLACEHOLDER, -) - -register( - 'SOCIAL_AUTH_GITHUB_ORG_TEAM_MAP', - field_class=SocialTeamMapField, - allow_null=True, - default=None, - label=_('GitHub Organization OAuth2 Team Map'), - help_text=SOCIAL_AUTH_TEAM_MAP_HELP_TEXT, - category=_('GitHub Organization OAuth2'), - category_slug='github-org', - placeholder=SOCIAL_AUTH_TEAM_MAP_PLACEHOLDER, -) - -############################################################################### -# GITHUB TEAM OAUTH2 AUTHENTICATION SETTINGS -############################################################################### - -register( - 'SOCIAL_AUTH_GITHUB_TEAM_CALLBACK_URL', - field_class=fields.CharField, - read_only=True, - default=SocialAuthCallbackURL('github-team'), - label=_('GitHub Team OAuth2 Callback URL'), - help_text=_( - 'Create an organization-owned application at ' - 'https://github.com/organizations//settings/applications ' - 'and obtain an OAuth2 key (Client ID) and secret (Client Secret). ' - 'Provide this URL as the callback URL for your application.' - ), - category=_('GitHub Team OAuth2'), - category_slug='github-team', - depends_on=['TOWER_URL_BASE'], -) - -register( - 'SOCIAL_AUTH_GITHUB_TEAM_KEY', - field_class=fields.CharField, - allow_blank=True, - default='', - label=_('GitHub Team OAuth2 Key'), - help_text=_('The OAuth2 key (Client ID) from your GitHub organization application.'), - category=_('GitHub Team OAuth2'), - category_slug='github-team', -) - -register( - 'SOCIAL_AUTH_GITHUB_TEAM_SECRET', - field_class=fields.CharField, - allow_blank=True, - default='', - label=_('GitHub Team OAuth2 Secret'), - help_text=_('The OAuth2 secret (Client Secret) from your GitHub organization application.'), - category=_('GitHub Team OAuth2'), - category_slug='github-team', - encrypted=True, -) - -register( - 'SOCIAL_AUTH_GITHUB_TEAM_ID', - field_class=fields.CharField, - allow_blank=True, - default='', - label=_('GitHub Team ID'), - help_text=_('Find the numeric team ID using the Github API: http://fabian-kostadinov.github.io/2015/01/16/how-to-find-a-github-team-id/.'), - category=_('GitHub Team OAuth2'), - category_slug='github-team', -) - -register( - 'SOCIAL_AUTH_GITHUB_TEAM_ORGANIZATION_MAP', - field_class=SocialOrganizationMapField, - allow_null=True, - default=None, - label=_('GitHub Team OAuth2 Organization Map'), - help_text=SOCIAL_AUTH_ORGANIZATION_MAP_HELP_TEXT, - category=_('GitHub Team OAuth2'), - category_slug='github-team', - placeholder=SOCIAL_AUTH_ORGANIZATION_MAP_PLACEHOLDER, -) - -register( - 'SOCIAL_AUTH_GITHUB_TEAM_TEAM_MAP', - field_class=SocialTeamMapField, - allow_null=True, - default=None, - label=_('GitHub Team OAuth2 Team Map'), - help_text=SOCIAL_AUTH_TEAM_MAP_HELP_TEXT, - category=_('GitHub Team OAuth2'), - category_slug='github-team', - placeholder=SOCIAL_AUTH_TEAM_MAP_PLACEHOLDER, -) - -############################################################################### -# GITHUB ENTERPRISE OAUTH2 AUTHENTICATION SETTINGS -############################################################################### - -register( - 'SOCIAL_AUTH_GITHUB_ENTERPRISE_CALLBACK_URL', - field_class=fields.CharField, - read_only=True, - default=SocialAuthCallbackURL('github-enterprise'), - label=_('GitHub Enterprise OAuth2 Callback URL'), - help_text=_('Provide this URL as the callback URL for your application as part of your registration process. Refer to the documentation for more detail.'), - category=_('GitHub Enterprise OAuth2'), - category_slug='github-enterprise', - depends_on=['TOWER_URL_BASE'], -) - -register( - 'SOCIAL_AUTH_GITHUB_ENTERPRISE_URL', - field_class=fields.CharField, - allow_blank=True, - default='', - label=_('GitHub Enterprise URL'), - help_text=_('The URL for your Github Enterprise instance, e.g.: http(s)://hostname/. Refer to Github Enterprise documentation for more details.'), - category=_('GitHub Enterprise OAuth2'), - category_slug='github-enterprise', -) - -register( - 'SOCIAL_AUTH_GITHUB_ENTERPRISE_API_URL', - field_class=fields.CharField, - allow_blank=True, - default='', - label=_('GitHub Enterprise API URL'), - help_text=_( - 'The API URL for your GitHub Enterprise instance, e.g.: http(s)://hostname/api/v3/. Refer to Github Enterprise documentation for more details.' - ), - category=_('GitHub Enterprise OAuth2'), - category_slug='github-enterprise', -) - -register( - 'SOCIAL_AUTH_GITHUB_ENTERPRISE_KEY', - field_class=fields.CharField, - allow_blank=True, - default='', - label=_('GitHub Enterprise OAuth2 Key'), - help_text=_('The OAuth2 key (Client ID) from your GitHub Enterprise developer application.'), - category=_('GitHub Enterprise OAuth2'), - category_slug='github-enterprise', -) - -register( - 'SOCIAL_AUTH_GITHUB_ENTERPRISE_SECRET', - field_class=fields.CharField, - allow_blank=True, - default='', - label=_('GitHub Enterprise OAuth2 Secret'), - help_text=_('The OAuth2 secret (Client Secret) from your GitHub Enterprise developer application.'), - category=_('GitHub OAuth2'), - category_slug='github-enterprise', - encrypted=True, -) - -register( - 'SOCIAL_AUTH_GITHUB_ENTERPRISE_ORGANIZATION_MAP', - field_class=SocialOrganizationMapField, - allow_null=True, - default=None, - label=_('GitHub Enterprise OAuth2 Organization Map'), - help_text=SOCIAL_AUTH_ORGANIZATION_MAP_HELP_TEXT, - category=_('GitHub Enterprise OAuth2'), - category_slug='github-enterprise', - placeholder=SOCIAL_AUTH_ORGANIZATION_MAP_PLACEHOLDER, -) - -register( - 'SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_MAP', - field_class=SocialTeamMapField, - allow_null=True, - default=None, - label=_('GitHub Enterprise OAuth2 Team Map'), - help_text=SOCIAL_AUTH_TEAM_MAP_HELP_TEXT, - category=_('GitHub Enterprise OAuth2'), - category_slug='github-enterprise', - placeholder=SOCIAL_AUTH_TEAM_MAP_PLACEHOLDER, -) - -############################################################################### -# GITHUB ENTERPRISE ORG OAUTH2 AUTHENTICATION SETTINGS -############################################################################### - -register( - 'SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_CALLBACK_URL', - field_class=fields.CharField, - read_only=True, - default=SocialAuthCallbackURL('github-enterprise-org'), - label=_('GitHub Enterprise Organization OAuth2 Callback URL'), - help_text=_('Provide this URL as the callback URL for your application as part of your registration process. Refer to the documentation for more detail.'), - category=_('GitHub Enterprise Organization OAuth2'), - category_slug='github-enterprise-org', - depends_on=['TOWER_URL_BASE'], -) + register( + 'SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_CALLBACK_URL', + field_class=fields.CharField, + read_only=True, + default=SocialAuthCallbackURL('github-enterprise-org'), + label=_('GitHub Enterprise Organization OAuth2 Callback URL'), + help_text=_( + 'Provide this URL as the callback URL for your application as part of your registration process. Refer to the documentation for more detail.' + ), + category=_('GitHub Enterprise Organization OAuth2'), + category_slug='github-enterprise-org', + depends_on=['TOWER_URL_BASE'], + ) -register( - 'SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_URL', - field_class=fields.CharField, - allow_blank=True, - default='', - label=_('GitHub Enterprise Organization URL'), - help_text=_('The URL for your Github Enterprise instance, e.g.: http(s)://hostname/. Refer to Github Enterprise documentation for more details.'), - category=_('GitHub Enterprise OAuth2'), - category_slug='github-enterprise-org', -) + register( + 'SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_URL', + field_class=fields.CharField, + allow_blank=True, + default='', + label=_('GitHub Enterprise Organization URL'), + help_text=_('The URL for your Github Enterprise instance, e.g.: http(s)://hostname/. Refer to Github Enterprise documentation for more details.'), + category=_('GitHub Enterprise OAuth2'), + category_slug='github-enterprise-org', + ) -register( - 'SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_API_URL', - field_class=fields.CharField, - allow_blank=True, - default='', - label=_('GitHub Enterprise Organization API URL'), - help_text=_( - 'The API URL for your GitHub Enterprise instance, e.g.: http(s)://hostname/api/v3/. Refer to Github Enterprise documentation for more details.' - ), - category=_('GitHub Enterprise OAuth2'), - category_slug='github-enterprise-org', -) + register( + 'SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_API_URL', + field_class=fields.CharField, + allow_blank=True, + default='', + label=_('GitHub Enterprise Organization API URL'), + help_text=_( + 'The API URL for your GitHub Enterprise instance, e.g.: http(s)://hostname/api/v3/. Refer to Github Enterprise documentation for more details.' + ), + category=_('GitHub Enterprise OAuth2'), + category_slug='github-enterprise-org', + ) -register( - 'SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_KEY', - field_class=fields.CharField, - allow_blank=True, - default='', - label=_('GitHub Enterprise Organization OAuth2 Key'), - help_text=_('The OAuth2 key (Client ID) from your GitHub Enterprise organization application.'), - category=_('GitHub Enterprise Organization OAuth2'), - category_slug='github-enterprise-org', -) + register( + 'SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_KEY', + field_class=fields.CharField, + allow_blank=True, + default='', + label=_('GitHub Enterprise Organization OAuth2 Key'), + help_text=_('The OAuth2 key (Client ID) from your GitHub Enterprise organization application.'), + category=_('GitHub Enterprise Organization OAuth2'), + category_slug='github-enterprise-org', + ) -register( - 'SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_SECRET', - field_class=fields.CharField, - allow_blank=True, - default='', - label=_('GitHub Enterprise Organization OAuth2 Secret'), - help_text=_('The OAuth2 secret (Client Secret) from your GitHub Enterprise organization application.'), - category=_('GitHub Enterprise Organization OAuth2'), - category_slug='github-enterprise-org', - encrypted=True, -) + register( + 'SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_SECRET', + field_class=fields.CharField, + allow_blank=True, + default='', + label=_('GitHub Enterprise Organization OAuth2 Secret'), + help_text=_('The OAuth2 secret (Client Secret) from your GitHub Enterprise organization application.'), + category=_('GitHub Enterprise Organization OAuth2'), + category_slug='github-enterprise-org', + encrypted=True, + ) -register( - 'SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_NAME', - field_class=fields.CharField, - allow_blank=True, - default='', - label=_('GitHub Enterprise Organization Name'), - help_text=_('The name of your GitHub Enterprise organization, as used in your organization\'s URL: https://github.com//.'), - category=_('GitHub Enterprise Organization OAuth2'), - category_slug='github-enterprise-org', -) + register( + 'SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_NAME', + field_class=fields.CharField, + allow_blank=True, + default='', + label=_('GitHub Enterprise Organization Name'), + help_text=_('The name of your GitHub Enterprise organization, as used in your organization\'s URL: https://github.com//.'), + category=_('GitHub Enterprise Organization OAuth2'), + category_slug='github-enterprise-org', + ) -register( - 'SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_ORGANIZATION_MAP', - field_class=SocialOrganizationMapField, - allow_null=True, - default=None, - label=_('GitHub Enterprise Organization OAuth2 Organization Map'), - help_text=SOCIAL_AUTH_ORGANIZATION_MAP_HELP_TEXT, - category=_('GitHub Enterprise Organization OAuth2'), - category_slug='github-enterprise-org', - placeholder=SOCIAL_AUTH_ORGANIZATION_MAP_PLACEHOLDER, -) + register( + 'SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_ORGANIZATION_MAP', + field_class=SocialOrganizationMapField, + allow_null=True, + default=None, + label=_('GitHub Enterprise Organization OAuth2 Organization Map'), + help_text=SOCIAL_AUTH_ORGANIZATION_MAP_HELP_TEXT, + category=_('GitHub Enterprise Organization OAuth2'), + category_slug='github-enterprise-org', + placeholder=SOCIAL_AUTH_ORGANIZATION_MAP_PLACEHOLDER, + ) -register( - 'SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_TEAM_MAP', - field_class=SocialTeamMapField, - allow_null=True, - default=None, - label=_('GitHub Enterprise Organization OAuth2 Team Map'), - help_text=SOCIAL_AUTH_TEAM_MAP_HELP_TEXT, - category=_('GitHub Enterprise Organization OAuth2'), - category_slug='github-enterprise-org', - placeholder=SOCIAL_AUTH_TEAM_MAP_PLACEHOLDER, -) + register( + 'SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_TEAM_MAP', + field_class=SocialTeamMapField, + allow_null=True, + default=None, + label=_('GitHub Enterprise Organization OAuth2 Team Map'), + help_text=SOCIAL_AUTH_TEAM_MAP_HELP_TEXT, + category=_('GitHub Enterprise Organization OAuth2'), + category_slug='github-enterprise-org', + placeholder=SOCIAL_AUTH_TEAM_MAP_PLACEHOLDER, + ) -############################################################################### -# GITHUB ENTERPRISE TEAM OAUTH2 AUTHENTICATION SETTINGS -############################################################################### - -register( - 'SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_CALLBACK_URL', - field_class=fields.CharField, - read_only=True, - default=SocialAuthCallbackURL('github-enterprise-team'), - label=_('GitHub Enterprise Team OAuth2 Callback URL'), - help_text=_( - 'Create an organization-owned application at ' - 'https://github.com/organizations//settings/applications ' - 'and obtain an OAuth2 key (Client ID) and secret (Client Secret). ' - 'Provide this URL as the callback URL for your application.' - ), - category=_('GitHub Enterprise Team OAuth2'), - category_slug='github-enterprise-team', - depends_on=['TOWER_URL_BASE'], -) + ############################################################################### + # GITHUB ENTERPRISE TEAM OAUTH2 AUTHENTICATION SETTINGS + ############################################################################### -register( - 'SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_URL', - field_class=fields.CharField, - allow_blank=True, - default='', - label=_('GitHub Enterprise Team URL'), - help_text=_('The URL for your Github Enterprise instance, e.g.: http(s)://hostname/. Refer to Github Enterprise documentation for more details.'), - category=_('GitHub Enterprise OAuth2'), - category_slug='github-enterprise-team', -) + register( + 'SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_CALLBACK_URL', + field_class=fields.CharField, + read_only=True, + default=SocialAuthCallbackURL('github-enterprise-team'), + label=_('GitHub Enterprise Team OAuth2 Callback URL'), + help_text=_( + 'Create an organization-owned application at ' + 'https://github.com/organizations//settings/applications ' + 'and obtain an OAuth2 key (Client ID) and secret (Client Secret). ' + 'Provide this URL as the callback URL for your application.' + ), + category=_('GitHub Enterprise Team OAuth2'), + category_slug='github-enterprise-team', + depends_on=['TOWER_URL_BASE'], + ) -register( - 'SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_API_URL', - field_class=fields.CharField, - allow_blank=True, - default='', - label=_('GitHub Enterprise Team API URL'), - help_text=_( - 'The API URL for your GitHub Enterprise instance, e.g.: http(s)://hostname/api/v3/. Refer to Github Enterprise documentation for more details.' - ), - category=_('GitHub Enterprise OAuth2'), - category_slug='github-enterprise-team', -) + register( + 'SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_URL', + field_class=fields.CharField, + allow_blank=True, + default='', + label=_('GitHub Enterprise Team URL'), + help_text=_('The URL for your Github Enterprise instance, e.g.: http(s)://hostname/. Refer to Github Enterprise documentation for more details.'), + category=_('GitHub Enterprise OAuth2'), + category_slug='github-enterprise-team', + ) -register( - 'SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_KEY', - field_class=fields.CharField, - allow_blank=True, - default='', - label=_('GitHub Enterprise Team OAuth2 Key'), - help_text=_('The OAuth2 key (Client ID) from your GitHub Enterprise organization application.'), - category=_('GitHub Enterprise Team OAuth2'), - category_slug='github-enterprise-team', -) + register( + 'SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_API_URL', + field_class=fields.CharField, + allow_blank=True, + default='', + label=_('GitHub Enterprise Team API URL'), + help_text=_( + 'The API URL for your GitHub Enterprise instance, e.g.: http(s)://hostname/api/v3/. Refer to Github Enterprise documentation for more details.' + ), + category=_('GitHub Enterprise OAuth2'), + category_slug='github-enterprise-team', + ) -register( - 'SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_SECRET', - field_class=fields.CharField, - allow_blank=True, - default='', - label=_('GitHub Enterprise Team OAuth2 Secret'), - help_text=_('The OAuth2 secret (Client Secret) from your GitHub Enterprise organization application.'), - category=_('GitHub Enterprise Team OAuth2'), - category_slug='github-enterprise-team', - encrypted=True, -) + register( + 'SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_KEY', + field_class=fields.CharField, + allow_blank=True, + default='', + label=_('GitHub Enterprise Team OAuth2 Key'), + help_text=_('The OAuth2 key (Client ID) from your GitHub Enterprise organization application.'), + category=_('GitHub Enterprise Team OAuth2'), + category_slug='github-enterprise-team', + ) -register( - 'SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_ID', - field_class=fields.CharField, - allow_blank=True, - default='', - label=_('GitHub Enterprise Team ID'), - help_text=_('Find the numeric team ID using the Github Enterprise API: http://fabian-kostadinov.github.io/2015/01/16/how-to-find-a-github-team-id/.'), - category=_('GitHub Enterprise Team OAuth2'), - category_slug='github-enterprise-team', -) + register( + 'SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_SECRET', + field_class=fields.CharField, + allow_blank=True, + default='', + label=_('GitHub Enterprise Team OAuth2 Secret'), + help_text=_('The OAuth2 secret (Client Secret) from your GitHub Enterprise organization application.'), + category=_('GitHub Enterprise Team OAuth2'), + category_slug='github-enterprise-team', + encrypted=True, + ) -register( - 'SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_ORGANIZATION_MAP', - field_class=SocialOrganizationMapField, - allow_null=True, - default=None, - label=_('GitHub Enterprise Team OAuth2 Organization Map'), - help_text=SOCIAL_AUTH_ORGANIZATION_MAP_HELP_TEXT, - category=_('GitHub Enterprise Team OAuth2'), - category_slug='github-enterprise-team', - placeholder=SOCIAL_AUTH_ORGANIZATION_MAP_PLACEHOLDER, -) + register( + 'SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_ID', + field_class=fields.CharField, + allow_blank=True, + default='', + label=_('GitHub Enterprise Team ID'), + help_text=_('Find the numeric team ID using the Github Enterprise API: http://fabian-kostadinov.github.io/2015/01/16/how-to-find-a-github-team-id/.'), + category=_('GitHub Enterprise Team OAuth2'), + category_slug='github-enterprise-team', + ) -register( - 'SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_TEAM_MAP', - field_class=SocialTeamMapField, - allow_null=True, - default=None, - label=_('GitHub Enterprise Team OAuth2 Team Map'), - help_text=SOCIAL_AUTH_TEAM_MAP_HELP_TEXT, - category=_('GitHub Enterprise Team OAuth2'), - category_slug='github-enterprise-team', - placeholder=SOCIAL_AUTH_TEAM_MAP_PLACEHOLDER, -) + register( + 'SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_ORGANIZATION_MAP', + field_class=SocialOrganizationMapField, + allow_null=True, + default=None, + label=_('GitHub Enterprise Team OAuth2 Organization Map'), + help_text=SOCIAL_AUTH_ORGANIZATION_MAP_HELP_TEXT, + category=_('GitHub Enterprise Team OAuth2'), + category_slug='github-enterprise-team', + placeholder=SOCIAL_AUTH_ORGANIZATION_MAP_PLACEHOLDER, + ) -############################################################################### -# MICROSOFT AZURE ACTIVE DIRECTORY SETTINGS -############################################################################### - -register( - 'SOCIAL_AUTH_AZUREAD_OAUTH2_CALLBACK_URL', - field_class=fields.CharField, - read_only=True, - default=SocialAuthCallbackURL('azuread-oauth2'), - label=_('Azure AD OAuth2 Callback URL'), - help_text=_('Provide this URL as the callback URL for your application as part of your registration process. Refer to the documentation for more detail. '), - category=_('Azure AD OAuth2'), - category_slug='azuread-oauth2', - depends_on=['TOWER_URL_BASE'], -) + register( + 'SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_TEAM_MAP', + field_class=SocialTeamMapField, + allow_null=True, + default=None, + label=_('GitHub Enterprise Team OAuth2 Team Map'), + help_text=SOCIAL_AUTH_TEAM_MAP_HELP_TEXT, + category=_('GitHub Enterprise Team OAuth2'), + category_slug='github-enterprise-team', + placeholder=SOCIAL_AUTH_TEAM_MAP_PLACEHOLDER, + ) -register( - 'SOCIAL_AUTH_AZUREAD_OAUTH2_KEY', - field_class=fields.CharField, - allow_blank=True, - default='', - label=_('Azure AD OAuth2 Key'), - help_text=_('The OAuth2 key (Client ID) from your Azure AD application.'), - category=_('Azure AD OAuth2'), - category_slug='azuread-oauth2', -) + ############################################################################### + # MICROSOFT AZURE ACTIVE DIRECTORY SETTINGS + ############################################################################### -register( - 'SOCIAL_AUTH_AZUREAD_OAUTH2_SECRET', - field_class=fields.CharField, - allow_blank=True, - default='', - label=_('Azure AD OAuth2 Secret'), - help_text=_('The OAuth2 secret (Client Secret) from your Azure AD application.'), - category=_('Azure AD OAuth2'), - category_slug='azuread-oauth2', - encrypted=True, -) + register( + 'SOCIAL_AUTH_AZUREAD_OAUTH2_CALLBACK_URL', + field_class=fields.CharField, + read_only=True, + default=SocialAuthCallbackURL('azuread-oauth2'), + label=_('Azure AD OAuth2 Callback URL'), + help_text=_( + 'Provide this URL as the callback URL for your application as part of your registration process. Refer to the documentation for more detail. ' + ), + category=_('Azure AD OAuth2'), + category_slug='azuread-oauth2', + depends_on=['TOWER_URL_BASE'], + ) -register( - 'SOCIAL_AUTH_AZUREAD_OAUTH2_ORGANIZATION_MAP', - field_class=SocialOrganizationMapField, - allow_null=True, - default=None, - label=_('Azure AD OAuth2 Organization Map'), - help_text=SOCIAL_AUTH_ORGANIZATION_MAP_HELP_TEXT, - category=_('Azure AD OAuth2'), - category_slug='azuread-oauth2', - placeholder=SOCIAL_AUTH_ORGANIZATION_MAP_PLACEHOLDER, -) + register( + 'SOCIAL_AUTH_AZUREAD_OAUTH2_KEY', + field_class=fields.CharField, + allow_blank=True, + default='', + label=_('Azure AD OAuth2 Key'), + help_text=_('The OAuth2 key (Client ID) from your Azure AD application.'), + category=_('Azure AD OAuth2'), + category_slug='azuread-oauth2', + ) -register( - 'SOCIAL_AUTH_AZUREAD_OAUTH2_TEAM_MAP', - field_class=SocialTeamMapField, - allow_null=True, - default=None, - label=_('Azure AD OAuth2 Team Map'), - help_text=SOCIAL_AUTH_TEAM_MAP_HELP_TEXT, - category=_('Azure AD OAuth2'), - category_slug='azuread-oauth2', - placeholder=SOCIAL_AUTH_TEAM_MAP_PLACEHOLDER, -) + register( + 'SOCIAL_AUTH_AZUREAD_OAUTH2_SECRET', + field_class=fields.CharField, + allow_blank=True, + default='', + label=_('Azure AD OAuth2 Secret'), + help_text=_('The OAuth2 secret (Client Secret) from your Azure AD application.'), + category=_('Azure AD OAuth2'), + category_slug='azuread-oauth2', + encrypted=True, + ) -############################################################################### -# Generic OIDC AUTHENTICATION SETTINGS -############################################################################### - -register( - 'SOCIAL_AUTH_OIDC_KEY', - field_class=fields.CharField, - allow_null=False, - default=None, - label=_('OIDC Key'), - help_text='The OIDC key (Client ID) from your IDP.', - category=_('Generic OIDC'), - category_slug='oidc', -) + register( + 'SOCIAL_AUTH_AZUREAD_OAUTH2_ORGANIZATION_MAP', + field_class=SocialOrganizationMapField, + allow_null=True, + default=None, + label=_('Azure AD OAuth2 Organization Map'), + help_text=SOCIAL_AUTH_ORGANIZATION_MAP_HELP_TEXT, + category=_('Azure AD OAuth2'), + category_slug='azuread-oauth2', + placeholder=SOCIAL_AUTH_ORGANIZATION_MAP_PLACEHOLDER, + ) -register( - 'SOCIAL_AUTH_OIDC_SECRET', - field_class=fields.CharField, - allow_blank=True, - default='', - label=_('OIDC Secret'), - help_text=_('The OIDC secret (Client Secret) from your IDP.'), - category=_('Generic OIDC'), - category_slug='oidc', - encrypted=True, -) + register( + 'SOCIAL_AUTH_AZUREAD_OAUTH2_TEAM_MAP', + field_class=SocialTeamMapField, + allow_null=True, + default=None, + label=_('Azure AD OAuth2 Team Map'), + help_text=SOCIAL_AUTH_TEAM_MAP_HELP_TEXT, + category=_('Azure AD OAuth2'), + category_slug='azuread-oauth2', + placeholder=SOCIAL_AUTH_TEAM_MAP_PLACEHOLDER, + ) -register( - 'SOCIAL_AUTH_OIDC_OIDC_ENDPOINT', - field_class=fields.CharField, - allow_blank=True, - default='', - label=_('OIDC Provider URL'), - help_text=_('The URL for your OIDC provider including the path up to /.well-known/openid-configuration'), - category=_('Generic OIDC'), - category_slug='oidc', -) + ############################################################################### + # Generic OIDC AUTHENTICATION SETTINGS + ############################################################################### -register( - 'SOCIAL_AUTH_OIDC_VERIFY_SSL', - field_class=fields.BooleanField, - default=True, - label=_('Verify OIDC Provider Certificate'), - help_text=_('Verify the OIDC provider ssl certificate.'), - category=_('Generic OIDC'), - category_slug='oidc', -) + register( + 'SOCIAL_AUTH_OIDC_KEY', + field_class=fields.CharField, + allow_null=False, + default=None, + label=_('OIDC Key'), + help_text='The OIDC key (Client ID) from your IDP.', + category=_('Generic OIDC'), + category_slug='oidc', + ) -############################################################################### -# SAML AUTHENTICATION SETTINGS -############################################################################### + register( + 'SOCIAL_AUTH_OIDC_SECRET', + field_class=fields.CharField, + allow_blank=True, + default='', + label=_('OIDC Secret'), + help_text=_('The OIDC secret (Client Secret) from your IDP.'), + category=_('Generic OIDC'), + category_slug='oidc', + encrypted=True, + ) + register( + 'SOCIAL_AUTH_OIDC_OIDC_ENDPOINT', + field_class=fields.CharField, + allow_blank=True, + default='', + label=_('OIDC Provider URL'), + help_text=_('The URL for your OIDC provider including the path up to /.well-known/openid-configuration'), + category=_('Generic OIDC'), + category_slug='oidc', + ) -def get_saml_metadata_url(): - return urlparse.urljoin(settings.TOWER_URL_BASE, reverse('sso:saml_metadata')) + register( + 'SOCIAL_AUTH_OIDC_VERIFY_SSL', + field_class=fields.BooleanField, + default=True, + label=_('Verify OIDC Provider Certificate'), + help_text=_('Verify the OIDC provider ssl certificate.'), + category=_('Generic OIDC'), + category_slug='oidc', + ) + ############################################################################### + # SAML AUTHENTICATION SETTINGS + ############################################################################### -def get_saml_entity_id(): - return settings.TOWER_URL_BASE + def get_saml_metadata_url(): + return urlparse.urljoin(settings.TOWER_URL_BASE, reverse('sso:saml_metadata')) + def get_saml_entity_id(): + return settings.TOWER_URL_BASE -register( - 'SAML_AUTO_CREATE_OBJECTS', - field_class=fields.BooleanField, - default=True, - label=_('Automatically Create Organizations and Teams on SAML Login'), - help_text=_('When enabled (the default), mapped Organizations and Teams will be created automatically on successful SAML login.'), - category=_('SAML'), - category_slug='saml', -) + register( + 'SAML_AUTO_CREATE_OBJECTS', + field_class=fields.BooleanField, + default=True, + label=_('Automatically Create Organizations and Teams on SAML Login'), + help_text=_('When enabled (the default), mapped Organizations and Teams will be created automatically on successful SAML login.'), + category=_('SAML'), + category_slug='saml', + ) -register( - 'SOCIAL_AUTH_SAML_CALLBACK_URL', - field_class=fields.CharField, - read_only=True, - default=SocialAuthCallbackURL('saml'), - label=_('SAML Assertion Consumer Service (ACS) URL'), - help_text=_( - 'Register the service as a service provider (SP) with each identity ' - 'provider (IdP) you have configured. Provide your SP Entity ID ' - 'and this ACS URL for your application.' - ), - category=_('SAML'), - category_slug='saml', - depends_on=['TOWER_URL_BASE'], -) + register( + 'SOCIAL_AUTH_SAML_CALLBACK_URL', + field_class=fields.CharField, + read_only=True, + default=SocialAuthCallbackURL('saml'), + label=_('SAML Assertion Consumer Service (ACS) URL'), + help_text=_( + 'Register the service as a service provider (SP) with each identity ' + 'provider (IdP) you have configured. Provide your SP Entity ID ' + 'and this ACS URL for your application.' + ), + category=_('SAML'), + category_slug='saml', + depends_on=['TOWER_URL_BASE'], + ) -register( - 'SOCIAL_AUTH_SAML_METADATA_URL', - field_class=fields.CharField, - read_only=True, - default=get_saml_metadata_url, - label=_('SAML Service Provider Metadata URL'), - help_text=_('If your identity provider (IdP) allows uploading an XML metadata file, you can download one from this URL.'), - category=_('SAML'), - category_slug='saml', -) + register( + 'SOCIAL_AUTH_SAML_METADATA_URL', + field_class=fields.CharField, + read_only=True, + default=get_saml_metadata_url, + label=_('SAML Service Provider Metadata URL'), + help_text=_('If your identity provider (IdP) allows uploading an XML metadata file, you can download one from this URL.'), + category=_('SAML'), + category_slug='saml', + ) -register( - 'SOCIAL_AUTH_SAML_SP_ENTITY_ID', - field_class=fields.CharField, - allow_blank=True, - default=get_saml_entity_id, - label=_('SAML Service Provider Entity ID'), - help_text=_( - 'The application-defined unique identifier used as the ' - 'audience of the SAML service provider (SP) configuration. ' - 'This is usually the URL for the service.' - ), - category=_('SAML'), - category_slug='saml', - depends_on=['TOWER_URL_BASE'], -) + register( + 'SOCIAL_AUTH_SAML_SP_ENTITY_ID', + field_class=fields.CharField, + allow_blank=True, + default=get_saml_entity_id, + label=_('SAML Service Provider Entity ID'), + help_text=_( + 'The application-defined unique identifier used as the ' + 'audience of the SAML service provider (SP) configuration. ' + 'This is usually the URL for the service.' + ), + category=_('SAML'), + category_slug='saml', + depends_on=['TOWER_URL_BASE'], + ) -register( - 'SOCIAL_AUTH_SAML_SP_PUBLIC_CERT', - field_class=fields.CharField, - allow_blank=True, - validators=[validate_certificate], - label=_('SAML Service Provider Public Certificate'), - help_text=_('Create a keypair to use as a service provider (SP) and include the certificate content here.'), - category=_('SAML'), - category_slug='saml', -) + register( + 'SOCIAL_AUTH_SAML_SP_PUBLIC_CERT', + field_class=fields.CharField, + allow_blank=True, + validators=[validate_certificate], + label=_('SAML Service Provider Public Certificate'), + help_text=_('Create a keypair to use as a service provider (SP) and include the certificate content here.'), + category=_('SAML'), + category_slug='saml', + ) -register( - 'SOCIAL_AUTH_SAML_SP_PRIVATE_KEY', - field_class=fields.CharField, - allow_blank=True, - validators=[validate_private_key], - label=_('SAML Service Provider Private Key'), - help_text=_('Create a keypair to use as a service provider (SP) and include the private key content here.'), - category=_('SAML'), - category_slug='saml', - encrypted=True, -) + register( + 'SOCIAL_AUTH_SAML_SP_PRIVATE_KEY', + field_class=fields.CharField, + allow_blank=True, + validators=[validate_private_key], + label=_('SAML Service Provider Private Key'), + help_text=_('Create a keypair to use as a service provider (SP) and include the private key content here.'), + category=_('SAML'), + category_slug='saml', + encrypted=True, + ) -register( - 'SOCIAL_AUTH_SAML_ORG_INFO', - field_class=SAMLOrgInfoField, - label=_('SAML Service Provider Organization Info'), - help_text=_('Provide the URL, display name, and the name of your app. Refer to the documentation for example syntax.'), - category=_('SAML'), - category_slug='saml', - placeholder=collections.OrderedDict( - [('en-US', collections.OrderedDict([('name', 'example'), ('displayname', 'Example'), ('url', 'http://www.example.com')]))] - ), -) + register( + 'SOCIAL_AUTH_SAML_ORG_INFO', + field_class=SAMLOrgInfoField, + label=_('SAML Service Provider Organization Info'), + help_text=_('Provide the URL, display name, and the name of your app. Refer to the documentation for example syntax.'), + category=_('SAML'), + category_slug='saml', + placeholder=collections.OrderedDict( + [('en-US', collections.OrderedDict([('name', 'example'), ('displayname', 'Example'), ('url', 'http://www.example.com')]))] + ), + ) -register( - 'SOCIAL_AUTH_SAML_TECHNICAL_CONTACT', - field_class=SAMLContactField, - allow_blank=True, - label=_('SAML Service Provider Technical Contact'), - help_text=_('Provide the name and email address of the technical contact for your service provider. Refer to the documentation for example syntax.'), - category=_('SAML'), - category_slug='saml', - placeholder=collections.OrderedDict([('givenName', 'Technical Contact'), ('emailAddress', 'techsup@example.com')]), -) + register( + 'SOCIAL_AUTH_SAML_TECHNICAL_CONTACT', + field_class=SAMLContactField, + allow_blank=True, + label=_('SAML Service Provider Technical Contact'), + help_text=_('Provide the name and email address of the technical contact for your service provider. Refer to the documentation for example syntax.'), + category=_('SAML'), + category_slug='saml', + placeholder=collections.OrderedDict([('givenName', 'Technical Contact'), ('emailAddress', 'techsup@example.com')]), + ) -register( - 'SOCIAL_AUTH_SAML_SUPPORT_CONTACT', - field_class=SAMLContactField, - allow_blank=True, - label=_('SAML Service Provider Support Contact'), - help_text=_('Provide the name and email address of the support contact for your service provider. Refer to the documentation for example syntax.'), - category=_('SAML'), - category_slug='saml', - placeholder=collections.OrderedDict([('givenName', 'Support Contact'), ('emailAddress', 'support@example.com')]), -) + register( + 'SOCIAL_AUTH_SAML_SUPPORT_CONTACT', + field_class=SAMLContactField, + allow_blank=True, + label=_('SAML Service Provider Support Contact'), + help_text=_('Provide the name and email address of the support contact for your service provider. Refer to the documentation for example syntax.'), + category=_('SAML'), + category_slug='saml', + placeholder=collections.OrderedDict([('givenName', 'Support Contact'), ('emailAddress', 'support@example.com')]), + ) -register( - 'SOCIAL_AUTH_SAML_ENABLED_IDPS', - field_class=SAMLEnabledIdPsField, - default={}, - label=_('SAML Enabled Identity Providers'), - help_text=_( - 'Configure the Entity ID, SSO URL and certificate for each identity' - ' provider (IdP) in use. Multiple SAML IdPs are supported. Some IdPs' - ' may provide user data using attribute names that differ from the' - ' default OIDs. Attribute names may be overridden for each IdP. Refer' - ' to the Ansible documentation for additional details and syntax.' - ), - category=_('SAML'), - category_slug='saml', - placeholder=collections.OrderedDict( - [ - ( - 'Okta', - collections.OrderedDict( - [ - ('entity_id', 'http://www.okta.com/HHniyLkaxk9e76wD0Thh'), - ('url', 'https://dev-123456.oktapreview.com/app/ansibletower/HHniyLkaxk9e76wD0Thh/sso/saml'), - ('x509cert', 'MIIDpDCCAoygAwIBAgIGAVVZ4rPzMA0GCSqGSIb3...'), - ('attr_user_permanent_id', 'username'), - ('attr_first_name', 'first_name'), - ('attr_last_name', 'last_name'), - ('attr_username', 'username'), - ('attr_email', 'email'), - ] + register( + 'SOCIAL_AUTH_SAML_ENABLED_IDPS', + field_class=SAMLEnabledIdPsField, + default={}, + label=_('SAML Enabled Identity Providers'), + help_text=_( + 'Configure the Entity ID, SSO URL and certificate for each identity' + ' provider (IdP) in use. Multiple SAML IdPs are supported. Some IdPs' + ' may provide user data using attribute names that differ from the' + ' default OIDs. Attribute names may be overridden for each IdP. Refer' + ' to the Ansible documentation for additional details and syntax.' + ), + category=_('SAML'), + category_slug='saml', + placeholder=collections.OrderedDict( + [ + ( + 'Okta', + collections.OrderedDict( + [ + ('entity_id', 'http://www.okta.com/HHniyLkaxk9e76wD0Thh'), + ('url', 'https://dev-123456.oktapreview.com/app/ansibletower/HHniyLkaxk9e76wD0Thh/sso/saml'), + ('x509cert', 'MIIDpDCCAoygAwIBAgIGAVVZ4rPzMA0GCSqGSIb3...'), + ('attr_user_permanent_id', 'username'), + ('attr_first_name', 'first_name'), + ('attr_last_name', 'last_name'), + ('attr_username', 'username'), + ('attr_email', 'email'), + ] + ), ), - ), - ( - 'OneLogin', - collections.OrderedDict( - [ - ('entity_id', 'https://app.onelogin.com/saml/metadata/123456'), - ('url', 'https://example.onelogin.com/trust/saml2/http-post/sso/123456'), - ('x509cert', 'MIIEJjCCAw6gAwIBAgIUfuSD54OPSBhndDHh3gZo...'), - ('attr_user_permanent_id', 'name_id'), - ('attr_first_name', 'User.FirstName'), - ('attr_last_name', 'User.LastName'), - ('attr_username', 'User.email'), - ('attr_email', 'User.email'), - ] + ( + 'OneLogin', + collections.OrderedDict( + [ + ('entity_id', 'https://app.onelogin.com/saml/metadata/123456'), + ('url', 'https://example.onelogin.com/trust/saml2/http-post/sso/123456'), + ('x509cert', 'MIIEJjCCAw6gAwIBAgIUfuSD54OPSBhndDHh3gZo...'), + ('attr_user_permanent_id', 'name_id'), + ('attr_first_name', 'User.FirstName'), + ('attr_last_name', 'User.LastName'), + ('attr_username', 'User.email'), + ('attr_email', 'User.email'), + ] + ), ), - ), - ] - ), -) - -register( - 'SOCIAL_AUTH_SAML_SECURITY_CONFIG', - field_class=SAMLSecurityField, - allow_null=True, - default={'requestedAuthnContext': False}, - label=_('SAML Security Config'), - help_text=_('A dict of key value pairs that are passed to the underlying python-saml security setting https://github.com/onelogin/python-saml#settings'), - category=_('SAML'), - category_slug='saml', - placeholder=collections.OrderedDict( - [ - ("nameIdEncrypted", False), - ("authnRequestsSigned", False), - ("logoutRequestSigned", False), - ("logoutResponseSigned", False), - ("signMetadata", False), - ("wantMessagesSigned", False), - ("wantAssertionsSigned", False), - ("wantAssertionsEncrypted", False), - ("wantNameId", True), - ("wantNameIdEncrypted", False), - ("wantAttributeStatement", True), - ("requestedAuthnContext", True), - ("requestedAuthnContextComparison", "exact"), - ("metadataValidUntil", "2015-06-26T20:00:00Z"), - ("metadataCacheDuration", "PT518400S"), - ("signatureAlgorithm", "http://www.w3.org/2000/09/xmldsig#rsa-sha1"), - ("digestAlgorithm", "http://www.w3.org/2000/09/xmldsig#sha1"), - ] - ), -) + ] + ), + ) -register( - 'SOCIAL_AUTH_SAML_SP_EXTRA', - field_class=fields.DictField, - allow_null=True, - default=None, - label=_('SAML Service Provider extra configuration data'), - help_text=_('A dict of key value pairs to be passed to the underlying python-saml Service Provider configuration setting.'), - category=_('SAML'), - category_slug='saml', - placeholder=collections.OrderedDict(), -) + register( + 'SOCIAL_AUTH_SAML_SECURITY_CONFIG', + field_class=SAMLSecurityField, + allow_null=True, + default={'requestedAuthnContext': False}, + label=_('SAML Security Config'), + help_text=_( + 'A dict of key value pairs that are passed to the underlying python-saml security setting https://github.com/onelogin/python-saml#settings' + ), + category=_('SAML'), + category_slug='saml', + placeholder=collections.OrderedDict( + [ + ("nameIdEncrypted", False), + ("authnRequestsSigned", False), + ("logoutRequestSigned", False), + ("logoutResponseSigned", False), + ("signMetadata", False), + ("wantMessagesSigned", False), + ("wantAssertionsSigned", False), + ("wantAssertionsEncrypted", False), + ("wantNameId", True), + ("wantNameIdEncrypted", False), + ("wantAttributeStatement", True), + ("requestedAuthnContext", True), + ("requestedAuthnContextComparison", "exact"), + ("metadataValidUntil", "2015-06-26T20:00:00Z"), + ("metadataCacheDuration", "PT518400S"), + ("signatureAlgorithm", "http://www.w3.org/2000/09/xmldsig#rsa-sha1"), + ("digestAlgorithm", "http://www.w3.org/2000/09/xmldsig#sha1"), + ] + ), + ) -register( - 'SOCIAL_AUTH_SAML_EXTRA_DATA', - field_class=fields.ListTuplesField, - allow_null=True, - default=None, - label=_('SAML IDP to extra_data attribute mapping'), - help_text=_('A list of tuples that maps IDP attributes to extra_attributes.' ' Each attribute will be a list of values, even if only 1 value.'), - category=_('SAML'), - category_slug='saml', - placeholder=[('attribute_name', 'extra_data_name_for_attribute'), ('department', 'department'), ('manager_full_name', 'manager_full_name')], -) + register( + 'SOCIAL_AUTH_SAML_SP_EXTRA', + field_class=fields.DictField, + allow_null=True, + default=None, + label=_('SAML Service Provider extra configuration data'), + help_text=_('A dict of key value pairs to be passed to the underlying python-saml Service Provider configuration setting.'), + category=_('SAML'), + category_slug='saml', + placeholder=collections.OrderedDict(), + ) -register( - 'SOCIAL_AUTH_SAML_ORGANIZATION_MAP', - field_class=SocialOrganizationMapField, - allow_null=True, - default=None, - label=_('SAML Organization Map'), - help_text=SOCIAL_AUTH_ORGANIZATION_MAP_HELP_TEXT, - category=_('SAML'), - category_slug='saml', - placeholder=SOCIAL_AUTH_ORGANIZATION_MAP_PLACEHOLDER, -) + register( + 'SOCIAL_AUTH_SAML_EXTRA_DATA', + field_class=fields.ListTuplesField, + allow_null=True, + default=None, + label=_('SAML IDP to extra_data attribute mapping'), + help_text=_('A list of tuples that maps IDP attributes to extra_attributes.' ' Each attribute will be a list of values, even if only 1 value.'), + category=_('SAML'), + category_slug='saml', + placeholder=[('attribute_name', 'extra_data_name_for_attribute'), ('department', 'department'), ('manager_full_name', 'manager_full_name')], + ) -register( - 'SOCIAL_AUTH_SAML_TEAM_MAP', - field_class=SocialTeamMapField, - allow_null=True, - default=None, - label=_('SAML Team Map'), - help_text=SOCIAL_AUTH_TEAM_MAP_HELP_TEXT, - category=_('SAML'), - category_slug='saml', - placeholder=SOCIAL_AUTH_TEAM_MAP_PLACEHOLDER, -) + register( + 'SOCIAL_AUTH_SAML_ORGANIZATION_MAP', + field_class=SocialOrganizationMapField, + allow_null=True, + default=None, + label=_('SAML Organization Map'), + help_text=SOCIAL_AUTH_ORGANIZATION_MAP_HELP_TEXT, + category=_('SAML'), + category_slug='saml', + placeholder=SOCIAL_AUTH_ORGANIZATION_MAP_PLACEHOLDER, + ) -register( - 'SOCIAL_AUTH_SAML_ORGANIZATION_ATTR', - field_class=SAMLOrgAttrField, - allow_null=True, - default=None, - label=_('SAML Organization Attribute Mapping'), - help_text=_('Used to translate user organization membership.'), - category=_('SAML'), - category_slug='saml', - placeholder=collections.OrderedDict( - [ - ('saml_attr', 'organization'), - ('saml_admin_attr', 'organization_admin'), - ('saml_auditor_attr', 'organization_auditor'), - ('remove', True), - ('remove_admins', True), - ('remove_auditors', True), - ] - ), -) + register( + 'SOCIAL_AUTH_SAML_TEAM_MAP', + field_class=SocialTeamMapField, + allow_null=True, + default=None, + label=_('SAML Team Map'), + help_text=SOCIAL_AUTH_TEAM_MAP_HELP_TEXT, + category=_('SAML'), + category_slug='saml', + placeholder=SOCIAL_AUTH_TEAM_MAP_PLACEHOLDER, + ) -register( - 'SOCIAL_AUTH_SAML_TEAM_ATTR', - field_class=SAMLTeamAttrField, - allow_null=True, - default=None, - label=_('SAML Team Attribute Mapping'), - help_text=_('Used to translate user team membership.'), - category=_('SAML'), - category_slug='saml', - placeholder=collections.OrderedDict( - [ - ('saml_attr', 'team'), - ('remove', True), - ( - 'team_org_map', - [ - collections.OrderedDict([('team', 'Marketing'), ('organization', 'Red Hat')]), - collections.OrderedDict([('team', 'Human Resources'), ('organization', 'Red Hat')]), - collections.OrderedDict([('team', 'Engineering'), ('organization', 'Red Hat')]), - collections.OrderedDict([('team', 'Engineering'), ('organization', 'Ansible')]), - collections.OrderedDict([('team', 'Quality Engineering'), ('organization', 'Ansible')]), - collections.OrderedDict([('team', 'Sales'), ('organization', 'Ansible')]), - ], - ), - ] - ), -) + register( + 'SOCIAL_AUTH_SAML_ORGANIZATION_ATTR', + field_class=SAMLOrgAttrField, + allow_null=True, + default=None, + label=_('SAML Organization Attribute Mapping'), + help_text=_('Used to translate user organization membership.'), + category=_('SAML'), + category_slug='saml', + placeholder=collections.OrderedDict( + [ + ('saml_attr', 'organization'), + ('saml_admin_attr', 'organization_admin'), + ('saml_auditor_attr', 'organization_auditor'), + ('remove', True), + ('remove_admins', True), + ('remove_auditors', True), + ] + ), + ) -register( - 'SOCIAL_AUTH_SAML_USER_FLAGS_BY_ATTR', - field_class=SAMLUserFlagsAttrField, - allow_null=True, - default=None, - label=_('SAML User Flags Attribute Mapping'), - help_text=_('Used to map super users and system auditors from SAML.'), - category=_('SAML'), - category_slug='saml', - placeholder=[ - ('is_superuser_attr', 'saml_attr'), - ('is_superuser_value', ['value']), - ('is_superuser_role', ['saml_role']), - ('remove_superusers', True), - ('is_system_auditor_attr', 'saml_attr'), - ('is_system_auditor_value', ['value']), - ('is_system_auditor_role', ['saml_role']), - ('remove_system_auditors', True), - ], -) + register( + 'SOCIAL_AUTH_SAML_TEAM_ATTR', + field_class=SAMLTeamAttrField, + allow_null=True, + default=None, + label=_('SAML Team Attribute Mapping'), + help_text=_('Used to translate user team membership.'), + category=_('SAML'), + category_slug='saml', + placeholder=collections.OrderedDict( + [ + ('saml_attr', 'team'), + ('remove', True), + ( + 'team_org_map', + [ + collections.OrderedDict([('team', 'Marketing'), ('organization', 'Red Hat')]), + collections.OrderedDict([('team', 'Human Resources'), ('organization', 'Red Hat')]), + collections.OrderedDict([('team', 'Engineering'), ('organization', 'Red Hat')]), + collections.OrderedDict([('team', 'Engineering'), ('organization', 'Ansible')]), + collections.OrderedDict([('team', 'Quality Engineering'), ('organization', 'Ansible')]), + collections.OrderedDict([('team', 'Sales'), ('organization', 'Ansible')]), + ], + ), + ] + ), + ) -register( - 'LOCAL_PASSWORD_MIN_LENGTH', - field_class=fields.IntegerField, - min_value=0, - default=0, - label=_('Minimum number of characters in local password'), - help_text=_('Minimum number of characters required in a local password. 0 means no minimum'), - category=_('Authentication'), - category_slug='authentication', -) + register( + 'SOCIAL_AUTH_SAML_USER_FLAGS_BY_ATTR', + field_class=SAMLUserFlagsAttrField, + allow_null=True, + default=None, + label=_('SAML User Flags Attribute Mapping'), + help_text=_('Used to map super users and system auditors from SAML.'), + category=_('SAML'), + category_slug='saml', + placeholder=[ + ('is_superuser_attr', 'saml_attr'), + ('is_superuser_value', ['value']), + ('is_superuser_role', ['saml_role']), + ('remove_superusers', True), + ('is_system_auditor_attr', 'saml_attr'), + ('is_system_auditor_value', ['value']), + ('is_system_auditor_role', ['saml_role']), + ('remove_system_auditors', True), + ], + ) -register( - 'LOCAL_PASSWORD_MIN_DIGITS', - field_class=fields.IntegerField, - min_value=0, - default=0, - label=_('Minimum number of digit characters in local password'), - help_text=_('Minimum number of digit characters required in a local password. 0 means no minimum'), - category=_('Authentication'), - category_slug='authentication', -) + register( + 'LOCAL_PASSWORD_MIN_LENGTH', + field_class=fields.IntegerField, + min_value=0, + default=0, + label=_('Minimum number of characters in local password'), + help_text=_('Minimum number of characters required in a local password. 0 means no minimum'), + category=_('Authentication'), + category_slug='authentication', + ) -register( - 'LOCAL_PASSWORD_MIN_UPPER', - field_class=fields.IntegerField, - min_value=0, - default=0, - label=_('Minimum number of uppercase characters in local password'), - help_text=_('Minimum number of uppercase characters required in a local password. 0 means no minimum'), - category=_('Authentication'), - category_slug='authentication', -) + register( + 'LOCAL_PASSWORD_MIN_DIGITS', + field_class=fields.IntegerField, + min_value=0, + default=0, + label=_('Minimum number of digit characters in local password'), + help_text=_('Minimum number of digit characters required in a local password. 0 means no minimum'), + category=_('Authentication'), + category_slug='authentication', + ) -register( - 'LOCAL_PASSWORD_MIN_SPECIAL', - field_class=fields.IntegerField, - min_value=0, - default=0, - label=_('Minimum number of special characters in local password'), - help_text=_('Minimum number of special characters required in a local password. 0 means no minimum'), - category=_('Authentication'), - category_slug='authentication', -) + register( + 'LOCAL_PASSWORD_MIN_UPPER', + field_class=fields.IntegerField, + min_value=0, + default=0, + label=_('Minimum number of uppercase characters in local password'), + help_text=_('Minimum number of uppercase characters required in a local password. 0 means no minimum'), + category=_('Authentication'), + category_slug='authentication', + ) + register( + 'LOCAL_PASSWORD_MIN_SPECIAL', + field_class=fields.IntegerField, + min_value=0, + default=0, + label=_('Minimum number of special characters in local password'), + help_text=_('Minimum number of special characters required in a local password. 0 means no minimum'), + category=_('Authentication'), + category_slug='authentication', + ) -def tacacs_validate(serializer, attrs): - if not serializer.instance or not hasattr(serializer.instance, 'TACACSPLUS_HOST') or not hasattr(serializer.instance, 'TACACSPLUS_SECRET'): + def tacacs_validate(serializer, attrs): + if not serializer.instance or not hasattr(serializer.instance, 'TACACSPLUS_HOST') or not hasattr(serializer.instance, 'TACACSPLUS_SECRET'): + return attrs + errors = [] + host = serializer.instance.TACACSPLUS_HOST + if 'TACACSPLUS_HOST' in attrs: + host = attrs['TACACSPLUS_HOST'] + secret = serializer.instance.TACACSPLUS_SECRET + if 'TACACSPLUS_SECRET' in attrs: + secret = attrs['TACACSPLUS_SECRET'] + if host and not secret: + errors.append('TACACSPLUS_SECRET is required when TACACSPLUS_HOST is provided.') + if errors: + raise serializers.ValidationError(_('\n'.join(errors))) return attrs - errors = [] - host = serializer.instance.TACACSPLUS_HOST - if 'TACACSPLUS_HOST' in attrs: - host = attrs['TACACSPLUS_HOST'] - secret = serializer.instance.TACACSPLUS_SECRET - if 'TACACSPLUS_SECRET' in attrs: - secret = attrs['TACACSPLUS_SECRET'] - if host and not secret: - errors.append('TACACSPLUS_SECRET is required when TACACSPLUS_HOST is provided.') - if errors: - raise serializers.ValidationError(_('\n'.join(errors))) - return attrs - - -register_validate('tacacsplus', tacacs_validate) + + register_validate('tacacsplus', tacacs_validate) From d43c91e1a5bcb98e012ab754c4ec58d8f88a2a4e Mon Sep 17 00:00:00 2001 From: Seth Foster Date: Wed, 5 Jun 2024 12:48:08 -0400 Subject: [PATCH 29/75] Option for dev env to enable ssl for postgres (#15151) PG_TLS=true make docker-compose This will add some extra startup commands for the postgres container to generate a key and cert to use for postgres connections. It will also mount in pgssl.conf which has ssl configuration. This can be useful for debugging issues that only surface when using ssl postgres connections. --- Makefile | 3 +++ .../ansible/roles/sources/defaults/main.yml | 1 + .../ansible/roles/sources/templates/database.py.j2 | 3 +++ .../roles/sources/templates/docker-compose.yml.j2 | 13 ++++++++++++- tools/docker-compose/pgssl.conf | 5 +++++ 5 files changed, 24 insertions(+), 1 deletion(-) create mode 100644 tools/docker-compose/pgssl.conf diff --git a/Makefile b/Makefile index 8a476581598e..1b6ff5156d34 100644 --- a/Makefile +++ b/Makefile @@ -53,6 +53,8 @@ OTEL ?= false LOKI ?= false # If set to true docker-compose will install editable dependencies EDITABLE_DEPENDENCIES ?= false +# If set to true, use tls for postgres connection +PG_TLS ?= false VENV_BASE ?= /var/lib/awx/venv @@ -542,6 +544,7 @@ docker-compose-sources: .git/hooks/pre-commit -e enable_otel=$(OTEL) \ -e enable_loki=$(LOKI) \ -e install_editable_dependencies=$(EDITABLE_DEPENDENCIES) \ + -e pg_tls=$(PG_TLS) \ $(EXTRA_SOURCES_ANSIBLE_OPTS) docker-compose: awx/projects docker-compose-sources diff --git a/tools/docker-compose/ansible/roles/sources/defaults/main.yml b/tools/docker-compose/ansible/roles/sources/defaults/main.yml index d336f29fc427..669f2cfe2002 100644 --- a/tools/docker-compose/ansible/roles/sources/defaults/main.yml +++ b/tools/docker-compose/ansible/roles/sources/defaults/main.yml @@ -4,6 +4,7 @@ awx_image: 'ghcr.io/ansible/awx_devel' pg_port: 5432 pg_username: 'awx' pg_database: 'awx' +pg_tls: false control_plane_node_count: 1 minikube_container_group: false receptor_socket_file: /var/run/awx-receptor/receptor.sock diff --git a/tools/docker-compose/ansible/roles/sources/templates/database.py.j2 b/tools/docker-compose/ansible/roles/sources/templates/database.py.j2 index 76bebff1591e..120a5c0d0a49 100644 --- a/tools/docker-compose/ansible/roles/sources/templates/database.py.j2 +++ b/tools/docker-compose/ansible/roles/sources/templates/database.py.j2 @@ -5,6 +5,9 @@ DATABASES = { 'NAME': "{{ pg_database }}", 'USER': "{{ pg_username }}", 'PASSWORD': "{{ pg_password }}", +{% if pg_tls|bool %} + 'OPTIONS': {'sslmode': 'require'}, +{% endif %} {% if enable_pgbouncer|bool %} 'HOST': "pgbouncer", 'PORT': "{{ pgbouncer_port }}", diff --git a/tools/docker-compose/ansible/roles/sources/templates/docker-compose.yml.j2 b/tools/docker-compose/ansible/roles/sources/templates/docker-compose.yml.j2 index 86f84523a3ec..734b3ba47e32 100644 --- a/tools/docker-compose/ansible/roles/sources/templates/docker-compose.yml.j2 +++ b/tools/docker-compose/ansible/roles/sources/templates/docker-compose.yml.j2 @@ -237,13 +237,24 @@ services: image: quay.io/sclorg/postgresql-15-c9s container_name: tools_postgres_1 # additional logging settings for postgres can be found https://www.postgresql.org/docs/current/runtime-config-logging.html - command: run-postgresql -c log_destination=stderr -c log_min_messages=info -c log_min_duration_statement={{ pg_log_min_duration_statement|default(1000) }} -c max_connections={{ pg_max_connections|default(1024) }} + command: > + bash -c " +{% if pg_tls|bool %} + mkdir -p /opt/app-root/src/certs + && openssl genrsa -out /opt/app-root/src/certs/tls.key 2048 + && openssl req -new -x509 -key /opt/app-root/src/certs/tls.key -out /opt/app-root/src/certs/tls.crt -subj '/CN=postgres' + && chmod 600 /opt/app-root/src/certs/tls.crt /opt/app-root/src/certs/tls.key && +{% endif %} + run-postgresql -c log_destination=stderr -c log_min_messages=info -c log_min_duration_statement={{ pg_log_min_duration_statement|default(1000) }} -c max_connections={{ pg_max_connections|default(1024) }}" environment: POSTGRESQL_USER: {{ pg_username }} POSTGRESQL_DATABASE: {{ pg_database }} POSTGRESQL_PASSWORD: {{ pg_password }} volumes: - "awx_db_15:/var/lib/pgsql/data" +{% if pg_tls|bool %} + - "../../docker-compose/pgssl.conf:/opt/app-root/src/postgresql-cfg/pgssl.conf" +{% endif %} networks: - awx ports: diff --git a/tools/docker-compose/pgssl.conf b/tools/docker-compose/pgssl.conf new file mode 100644 index 000000000000..d34917d1ff75 --- /dev/null +++ b/tools/docker-compose/pgssl.conf @@ -0,0 +1,5 @@ +ssl = on +ssl_cert_file = '/opt/app-root/src/certs/tls.crt' # server certificate +ssl_key_file = '/opt/app-root/src/certs/tls.key' # server private key +#ssl_ca_file # trusted certificate authorities +#ssl_crl_file # certificates revoked by certificate authorities From 4915262af13f4cffe762f1ee834cdef2d5719d85 Mon Sep 17 00:00:00 2001 From: Jeff Bradberry Date: Tue, 4 Jun 2024 12:41:45 -0400 Subject: [PATCH 30/75] Do each batch of the HostMetric updates in a transaction It looks like we can't do upserts currently without dropping to raw SQL, but if we wrap each batch in a transaction, that should insure that each is updated with the correct count. --- awx/main/models/events.py | 31 ++++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/awx/main/models/events.py b/awx/main/models/events.py index a794152c0537..fb19de554b6d 100644 --- a/awx/main/models/events.py +++ b/awx/main/models/events.py @@ -4,11 +4,12 @@ from datetime import timezone import logging from collections import defaultdict +import itertools import time from django.conf import settings from django.core.exceptions import ObjectDoesNotExist -from django.db import models, DatabaseError +from django.db import models, DatabaseError, transaction from django.db.models.functions import Cast from django.utils.dateparse import parse_datetime from django.utils.text import Truncator @@ -605,19 +606,23 @@ def _update_host_summary_from_stats(self, hostnames): def _update_host_metrics(updated_hosts_list): from awx.main.models import HostMetric # circular import - # bulk-create current_time = now() - HostMetric.objects.bulk_create( - [HostMetric(hostname=hostname, last_automation=current_time) for hostname in updated_hosts_list], ignore_conflicts=True, batch_size=100 - ) - # bulk-update - batch_start, batch_size = 0, 1000 - while batch_start <= len(updated_hosts_list): - batched_host_list = updated_hosts_list[batch_start : (batch_start + batch_size)] - HostMetric.objects.filter(hostname__in=batched_host_list).update( - last_automation=current_time, automated_counter=models.F('automated_counter') + 1, deleted=False - ) - batch_start += batch_size + + # FUTURE: + # - Hand-rolled implementation of itertools.batched(), introduced in Python 3.12. Replace. + # - Ability to do ORM upserts *may* have been introduced in Django 5.0. + # See the entry about `create_defaults` in https://docs.djangoproject.com/en/5.0/releases/5.0/#models. + # Hopefully this will be fully ready for batch use by 5.2 LTS. + + args = [iter(updated_hosts_list)] * 500 + for hosts in itertools.zip_longest(*args): + with transaction.atomic(): + HostMetric.objects.bulk_create( + [HostMetric(hostname=hostname, last_automation=current_time) for hostname in hosts if hostname is not None], ignore_conflicts=True + ) + HostMetric.objects.filter(hostname__in=hosts).update( + last_automation=current_time, automated_counter=models.F('automated_counter') + 1, deleted=False + ) @property def job_verbosity(self): From 8827ae75547deb7a9b2d468a9f8cd48a9b411cfe Mon Sep 17 00:00:00 2001 From: Dave Date: Thu, 6 Jun 2024 14:47:04 +0100 Subject: [PATCH 31/75] Replace REMOTE_ADDR with ansible_base.lib.utils.requests.get_remote_host (#15175) --- awx/api/generics.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/awx/api/generics.py b/awx/api/generics.py index 5b9ea1e1766e..2597e2452ac2 100644 --- a/awx/api/generics.py +++ b/awx/api/generics.py @@ -33,6 +33,7 @@ # django-ansible-base from ansible_base.rest_filters.rest_framework.field_lookup_backend import FieldLookupBackend from ansible_base.lib.utils.models import get_all_field_names +from ansible_base.lib.utils.requests import get_remote_host from ansible_base.rbac.models import RoleEvaluation, RoleDefinition from ansible_base.rbac.permission_registry import permission_registry @@ -93,8 +94,9 @@ def get(self, request, *args, **kwargs): def post(self, request, *args, **kwargs): ret = super(LoggedLoginView, self).post(request, *args, **kwargs) + ip = get_remote_host(request) # request.META.get('REMOTE_ADDR', None) if request.user.is_authenticated: - logger.info(smart_str(u"User {} logged in from {}".format(self.request.user.username, request.META.get('REMOTE_ADDR', None)))) + logger.info(smart_str(u"User {} logged in from {}".format(self.request.user.username, ip))) ret.set_cookie( 'userLoggedIn', 'true', secure=getattr(settings, 'SESSION_COOKIE_SECURE', False), samesite=getattr(settings, 'USER_COOKIE_SAMESITE', 'Lax') ) @@ -103,7 +105,7 @@ def post(self, request, *args, **kwargs): return ret else: if 'username' in self.request.POST: - logger.warning(smart_str(u"Login failed for user {} from {}".format(self.request.POST.get('username'), request.META.get('REMOTE_ADDR', None)))) + logger.warning(smart_str(u"Login failed for user {} from {}".format(self.request.POST.get('username'), ip))) ret.status_code = 401 return ret @@ -211,11 +213,12 @@ def finalize_response(self, request, response, *args, **kwargs): return response if response.status_code >= 400: + ip = get_remote_host(request) # request.META.get('REMOTE_ADDR', None) msg_data = { 'status_code': response.status_code, 'user_name': request.user, 'url_path': request.path, - 'remote_addr': request.META.get('REMOTE_ADDR', None), + 'remote_addr': ip, } if type(response.data) is dict: From d65ea2a3d5d90352be3fff627d54efdacba710ad Mon Sep 17 00:00:00 2001 From: Seth Foster Date: Sun, 9 Jun 2024 20:39:18 -0400 Subject: [PATCH 32/75] Fix race condition when deleting schedules (#15259) If more than one schedule for a unified job template is removed at once, a race condition can arise. example scenario: delete schedules with ids 7 and 8 - unified job template next_schedule is currently 7 - on delete of schedule 7, update_computed_fields will try to set next_schedule to 8 - but while this logic is occurring, another transaction is deleting 8 This leads to a db IntegrityError The solution here is to call select_for_update() on the next schedule, so that 8 cannot be deleted until the transaction for deleting 7 is completed. Signed-off-by: Seth Foster --- awx/main/models/unified_jobs.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/awx/main/models/unified_jobs.py b/awx/main/models/unified_jobs.py index 4f885585a66f..39bc56f43bfb 100644 --- a/awx/main/models/unified_jobs.py +++ b/awx/main/models/unified_jobs.py @@ -17,7 +17,7 @@ # Django from django.conf import settings -from django.db import models, connection +from django.db import models, connection, transaction from django.core.exceptions import NON_FIELD_ERRORS from django.utils.translation import gettext_lazy as _ from django.utils.timezone import now @@ -273,7 +273,14 @@ def update_computed_fields(self): if new_next_schedule: if new_next_schedule.pk == self.next_schedule_id and new_next_schedule.next_run == self.next_job_run: return # no-op, common for infrequent schedules - self.next_schedule = new_next_schedule + + # If in a transaction, use select_for_update to lock the next schedule row, which + # prevents a race condition if new_next_schedule is deleted elsewhere during this transaction + if transaction.get_autocommit(): + self.next_schedule = related_schedules.first() + else: + self.next_schedule = related_schedules.select_for_update().first() + self.next_job_run = new_next_schedule.next_run self.save(update_fields=['next_schedule', 'next_job_run']) From c1dc0c7b86cdc309edeb593022ab1d5ef2ced888 Mon Sep 17 00:00:00 2001 From: Hao Liu <44379968+TheRealHaoLiu@users.noreply.github.com> Date: Mon, 10 Jun 2024 14:10:57 -0400 Subject: [PATCH 33/75] Periodically sync from share resource provider (#15264) * Periodically sync from share resource provider - add periodic task `periodic_resource_sync` run once every 15 min - if `RESOURCE_SERVER` is not configured sync will not run - only 1 node example RESOURCE_SERVER configuration ``` RESOURCE_SERVER = { "URL": "", "SECRET_KEY": "", "VALIDATE_HTTPS": , } RESOURCE_SERVICE_PATH = ``` --- awx/main/tasks/system.py | 17 +++++++++++++++++ awx/settings/defaults.py | 1 + 2 files changed, 18 insertions(+) diff --git a/awx/main/tasks/system.py b/awx/main/tasks/system.py index bca9d16c055f..aa65e706c1a7 100644 --- a/awx/main/tasks/system.py +++ b/awx/main/tasks/system.py @@ -36,6 +36,9 @@ # dateutil from dateutil.parser import parse as parse_date +# django-ansible-base +from ansible_base.resource_registry.tasks.sync import SyncExecutor + # AWX from awx import __version__ as awx_application_version from awx.main.access import access_registry @@ -964,3 +967,17 @@ def deep_copy_model_obj(model_module, model_name, obj_pk, new_obj_pk, user_pk, p permission_check_func(creater, copy_mapping.values()) if isinstance(new_obj, Inventory): update_inventory_computed_fields.delay(new_obj.id) + + +@task(queue=get_task_queuename) +def periodic_resource_sync(): + if not getattr(settings, 'RESOURCE_SERVER', None): + logger.debug("Skipping periodic resource_sync, RESOURCE_SERVER not configured") + return + + with advisory_lock('periodic_resource_sync', wait=False) as acquired: + if acquired is False: + logger.debug("Not running periodic_resource_sync, another task holds lock") + return + + SyncExecutor().run() diff --git a/awx/settings/defaults.py b/awx/settings/defaults.py index 9db1312ad74a..72d2813d4a46 100644 --- a/awx/settings/defaults.py +++ b/awx/settings/defaults.py @@ -492,6 +492,7 @@ 'cleanup_images': {'task': 'awx.main.tasks.system.cleanup_images_and_files', 'schedule': timedelta(hours=3)}, 'cleanup_host_metrics': {'task': 'awx.main.tasks.host_metrics.cleanup_host_metrics', 'schedule': timedelta(hours=3, minutes=30)}, 'host_metric_summary_monthly': {'task': 'awx.main.tasks.host_metrics.host_metric_summary_monthly', 'schedule': timedelta(hours=4)}, + 'periodic_resource_sync': {'task': 'awx.main.tasks.system.periodic_resource_sync', 'schedule': timedelta(minutes=15)}, } # Django Caching Configuration From 451f20ce0fc7c32939c568dcdb7aadbda873ea4a Mon Sep 17 00:00:00 2001 From: Artsiom Musin Date: Fri, 26 May 2023 15:30:58 +0200 Subject: [PATCH 34/75] Use patch to update users in awx cli --- awxkit/awxkit/api/pages/api.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/awxkit/awxkit/api/pages/api.py b/awxkit/awxkit/api/pages/api.py index 13f82e3a06b9..2283f10c96ed 100644 --- a/awxkit/awxkit/api/pages/api.py +++ b/awxkit/awxkit/api/pages/api.py @@ -317,7 +317,10 @@ def _import_list(self, endpoint, assets): if asset['natural_key']['type'] == 'project' and 'local_path' in post_data and _page['scm_type'] == post_data['scm_type']: del post_data['local_path'] - _page = _page.put(post_data) + if asset['natural_key']['type'] == 'user': + _page = _page.patch(**post_data) + else: + _page = _page.put(post_data) changed = True except (exc.Common, AssertionError) as e: identifier = asset.get("name", None) or asset.get("username", None) or asset.get("hostname", None) From fb860d76ce85fbdf529b7adfb96d5d6554a69a99 Mon Sep 17 00:00:00 2001 From: Hao Liu <44379968+TheRealHaoLiu@users.noreply.github.com> Date: Mon, 10 Jun 2024 15:39:24 -0400 Subject: [PATCH 35/75] Add receptor work list command to sosreport (#15207) --- tools/sosreport/controller.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/sosreport/controller.py b/tools/sosreport/controller.py index 34a5bd525494..318e2cc7efd4 100644 --- a/tools/sosreport/controller.py +++ b/tools/sosreport/controller.py @@ -25,6 +25,7 @@ "ls -ll /var/run/awx-receptor", # list contents of dirctory where receptor socket should be "ls -ll /etc/receptor", "receptorctl --socket /var/run/awx-receptor/receptor.sock status", # Get information about the status of the mesh + "receptorctl --socket /var/run/awx-receptor/receptor.sock work list", # Get list of receptor work units "umask -p", # check current umask ] From b17f0a188bcc8178bb8e12f54c340edaee19a1ab Mon Sep 17 00:00:00 2001 From: Jeff Bradberry Date: Tue, 30 Apr 2024 08:56:19 -0400 Subject: [PATCH 36/75] Initial check --- tools/scripts/ig-hotfix/.gitignore | 2 ++ tools/scripts/ig-hotfix/role_check.py | 6 ++++++ 2 files changed, 8 insertions(+) create mode 100644 tools/scripts/ig-hotfix/.gitignore create mode 100644 tools/scripts/ig-hotfix/role_check.py diff --git a/tools/scripts/ig-hotfix/.gitignore b/tools/scripts/ig-hotfix/.gitignore new file mode 100644 index 000000000000..a946df02b7eb --- /dev/null +++ b/tools/scripts/ig-hotfix/.gitignore @@ -0,0 +1,2 @@ +*~ +customer-backup.tar.* diff --git a/tools/scripts/ig-hotfix/role_check.py b/tools/scripts/ig-hotfix/role_check.py new file mode 100644 index 000000000000..57cc9973f97c --- /dev/null +++ b/tools/scripts/ig-hotfix/role_check.py @@ -0,0 +1,6 @@ +from collections import Counter + +from awx.main.models.rbac import Role + + +print(Counter(r.id == getattr(getattr(r.content_object, r.role_field, None), 'id', None) for r in Role.objects.all() if r.content_type)) From de25408a2310cd054d84d729b263ec3d7c7839f4 Mon Sep 17 00:00:00 2001 From: Jeff Bradberry Date: Tue, 30 Apr 2024 09:09:05 -0400 Subject: [PATCH 37/75] Print out details of all of the crosslinked roles --- tools/scripts/ig-hotfix/role_check.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tools/scripts/ig-hotfix/role_check.py b/tools/scripts/ig-hotfix/role_check.py index 57cc9973f97c..4daacafffcf5 100644 --- a/tools/scripts/ig-hotfix/role_check.py +++ b/tools/scripts/ig-hotfix/role_check.py @@ -3,4 +3,9 @@ from awx.main.models.rbac import Role -print(Counter(r.id == getattr(getattr(r.content_object, r.role_field, None), 'id', None) for r in Role.objects.all() if r.content_type)) +for r in Role.objects.all(): + if not r.content_type: + continue + if r.id != getattr(getattr(r.content_object, r.role_field, None), 'id', None): + rev = getattr(r.content_object, r.role_field, None) + print(f"role.id={r.id} '{r.content_type}' id={r.object_id} field={r.role_field} | obj.roleid={getattr(rev, 'id', None)} {getattr(rev, 'content_type', None)} {getattr(rev, 'object_id', None)} {getattr(rev, 'role_field', None)}") From b69ed08fe53455de64691e61c57f30484899196e Mon Sep 17 00:00:00 2001 From: Jeff Bradberry Date: Tue, 30 Apr 2024 10:00:18 -0400 Subject: [PATCH 38/75] Specifically examine the InstanceGroup roles --- tools/scripts/ig-hotfix/role_check.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/tools/scripts/ig-hotfix/role_check.py b/tools/scripts/ig-hotfix/role_check.py index 4daacafffcf5..6dd27d3324c9 100644 --- a/tools/scripts/ig-hotfix/role_check.py +++ b/tools/scripts/ig-hotfix/role_check.py @@ -1,11 +1,15 @@ from collections import Counter +from awx.main.models.ha import InstanceGroup from awx.main.models.rbac import Role -for r in Role.objects.all(): - if not r.content_type: - continue - if r.id != getattr(getattr(r.content_object, r.role_field, None), 'id', None): - rev = getattr(r.content_object, r.role_field, None) - print(f"role.id={r.id} '{r.content_type}' id={r.object_id} field={r.role_field} | obj.roleid={getattr(rev, 'id', None)} {getattr(rev, 'content_type', None)} {getattr(rev, 'object_id', None)} {getattr(rev, 'role_field', None)}") +for ig in InstanceGroup.objects.order_by('id'): + for f in ('admin_role', 'use_role', 'read_role'): + r = getattr(ig, f, None) + print(f"id={ig.id} {f} // r.id={getattr(r, 'id', None)} {getattr(r, 'content_type', None)} {getattr(r, 'object_id', None)} {getattr(r, 'role_field', None)}") + + +ct = ContentType.objects.get(app_label='main', model='instancegroup') +for r in Role.objects.filter(content_type=ct).order_by('id'): + print(f"id={r.id} instancegroup {r.object_id} {r.role_field}") From 54e85813c818dc82bb64e803079030835e33169c Mon Sep 17 00:00:00 2001 From: Jeff Bradberry Date: Tue, 30 Apr 2024 11:39:53 -0400 Subject: [PATCH 39/75] First full check script This version emits the first fix-up script as its output. --- tools/scripts/ig-hotfix/role_check.py | 76 +++++++++++++++++++++++---- 1 file changed, 67 insertions(+), 9 deletions(-) diff --git a/tools/scripts/ig-hotfix/role_check.py b/tools/scripts/ig-hotfix/role_check.py index 6dd27d3324c9..71540be0e6f1 100644 --- a/tools/scripts/ig-hotfix/role_check.py +++ b/tools/scripts/ig-hotfix/role_check.py @@ -1,15 +1,73 @@ -from collections import Counter +from collections import defaultdict +import sys +import textwrap + +from django.contrib.contenttypes.models import ContentType + +from awx.main.fields import ImplicitRoleField +from awx.main.models.rbac import Role + + +crosslinked = defaultdict(dict) +orphaned_roles = [] + + +for ct in ContentType.objects.order_by('id'): + cls = ct.model_class() + if not any(isinstance(f, ImplicitRoleField) for f in cls._meta.fields): + continue + for obj in cls.objects.all(): + for f in cls._meta.fields: + if not isinstance(f, ImplicitRoleField): + continue + r = getattr(obj, f.name, None) + if not r: + sys.stderr.write(f"{cls} id={obj.id} {f.name} does not have a Role object\n") + crosslinked[(ct.id, obj.id)][f.name] = None + continue + if r.content_object != obj: + sys.stderr.write(f"{cls.__name__} id={obj.id} {f.name} is pointing to a Role that is assigned to a different object: role.id={r.id} {r.content_type!r} {r.object_id} {r.role_field}\n") + crosslinked[(ct.id, obj.id)][f.name] = None + continue + + +sys.stderr.write('===================================\n') +for r in Role.objects.exclude(role_field__startswith='system_').order_by('id'): + if not r.content_object: + sys.stderr.write(f"Role id={r.id} is missing a valid content_object: {r.content_type!r} {r.object_id} {r.role_field}\n") + orphaned_roles.append(r.id) + continue + rev = getattr(r.content_object, r.role_field, None) + if not rev: + continue + if r.id != rev.id: + sys.stderr.write(f"Role id={r.id} {r.content_type!r} {r.object_id} {r.role_field} is pointing to an object using a different role: id={rev.id} {rev.content_type!r} {rev.object_id} {rev.role_field}\n") + crosslinked[(r.content_type_id, r.object_id)][r.role_field] = r.id + continue + + +sys.stderr.write('===================================\n') + + +print(f"""\ +from django.contrib.contenttypes.models import ContentType -from awx.main.models.ha import InstanceGroup from awx.main.models.rbac import Role +""") -for ig in InstanceGroup.objects.order_by('id'): - for f in ('admin_role', 'use_role', 'read_role'): - r = getattr(ig, f, None) - print(f"id={ig.id} {f} // r.id={getattr(r, 'id', None)} {getattr(r, 'content_type', None)} {getattr(r, 'object_id', None)} {getattr(r, 'role_field', None)}") +print("# Role objects that are assigned to objects that do not exist") +for r in orphaned_roles: + print(f"Role.objects.filter(id={r}).delete()") -ct = ContentType.objects.get(app_label='main', model='instancegroup') -for r in Role.objects.filter(content_type=ct).order_by('id'): - print(f"id={r.id} instancegroup {r.object_id} {r.role_field}") +print("\n") +print("# Resource objects that are pointing to the wrong Role. Some of these") +print("# do not have corresponding Roles anywhere, so delete the foreign key.") +print("# For those, new Roles will be constructed upon save.\n") +for (ct, obj), kv in crosslinked.items(): + print(f"ct = ContentType.objects.get(id={ct})") + print(f"obj = ct.get_object_for_this_type(id={obj})") + for f, val in kv.items(): + print(f"setattr(obj, '{f}_id', {val})") + print(f"obj.save()\n") From bea74a401dba152e66c4c5a60fd68b87f2fdb939 Mon Sep 17 00:00:00 2001 From: Jeff Bradberry Date: Tue, 30 Apr 2024 17:04:17 -0400 Subject: [PATCH 40/75] Attempt to be more efficient about grouping the content types Also, attempt to rebuild the role ancestors in the fixup script. --- tools/scripts/ig-hotfix/role_check.py | 28 +++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/tools/scripts/ig-hotfix/role_check.py b/tools/scripts/ig-hotfix/role_check.py index 71540be0e6f1..90542804c46c 100644 --- a/tools/scripts/ig-hotfix/role_check.py +++ b/tools/scripts/ig-hotfix/role_check.py @@ -1,6 +1,5 @@ from collections import defaultdict import sys -import textwrap from django.contrib.contenttypes.models import ContentType @@ -8,7 +7,7 @@ from awx.main.models.rbac import Role -crosslinked = defaultdict(dict) +crosslinked = defaultdict(lambda: defaultdict(dict)) orphaned_roles = [] @@ -23,11 +22,11 @@ r = getattr(obj, f.name, None) if not r: sys.stderr.write(f"{cls} id={obj.id} {f.name} does not have a Role object\n") - crosslinked[(ct.id, obj.id)][f.name] = None + crosslinked[ct.id][obj.id][f'{f.name}_id'] = None continue if r.content_object != obj: sys.stderr.write(f"{cls.__name__} id={obj.id} {f.name} is pointing to a Role that is assigned to a different object: role.id={r.id} {r.content_type!r} {r.object_id} {r.role_field}\n") - crosslinked[(ct.id, obj.id)][f.name] = None + crosslinked[ct.id][obj.id][f'{f.name}_id'] = None continue @@ -42,7 +41,7 @@ continue if r.id != rev.id: sys.stderr.write(f"Role id={r.id} {r.content_type!r} {r.object_id} {r.role_field} is pointing to an object using a different role: id={rev.id} {rev.content_type!r} {rev.object_id} {rev.role_field}\n") - crosslinked[(r.content_type_id, r.object_id)][r.role_field] = r.id + crosslinked[r.content_type_id][r.object_id][f'{r.role_field}_id'] = r.id continue @@ -52,7 +51,7 @@ print(f"""\ from django.contrib.contenttypes.models import ContentType -from awx.main.models.rbac import Role +from awx.main.models.rbac import Role, batch_role_ancestor_rebuilding """) @@ -65,9 +64,14 @@ print("# Resource objects that are pointing to the wrong Role. Some of these") print("# do not have corresponding Roles anywhere, so delete the foreign key.") print("# For those, new Roles will be constructed upon save.\n") -for (ct, obj), kv in crosslinked.items(): - print(f"ct = ContentType.objects.get(id={ct})") - print(f"obj = ct.get_object_for_this_type(id={obj})") - for f, val in kv.items(): - print(f"setattr(obj, '{f}_id', {val})") - print(f"obj.save()\n") +print("queue = []\n") +for ct, objs in crosslinked.items(): + print(f"cls = ContentType.objects.get(id={ct}).model_class()\n") + for obj, kv in objs.items(): + print(f"cls.objects.filter(id={obj}).update(**{kv!r})") + print(f"queue.append((cls, {obj}))") + +print(f"\nwith batch_role_ancestor_rebuilding():") +print(f" for cls, obj_id in queue:") +print(f" obj = cls.objects.get(id=obj_id)") +print(f" obj.save()") From 6f57aaa8f59fd714ddf280e9a8be34469eed126d Mon Sep 17 00:00:00 2001 From: Jeff Bradberry Date: Wed, 1 May 2024 13:58:47 -0400 Subject: [PATCH 41/75] When checking reverse links, treat duplicate Roles different from bad ones Also, null out the generic foreign key on orphaned roles before deleting. --- tools/scripts/ig-hotfix/role_check.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/tools/scripts/ig-hotfix/role_check.py b/tools/scripts/ig-hotfix/role_check.py index 90542804c46c..5018a57462ce 100644 --- a/tools/scripts/ig-hotfix/role_check.py +++ b/tools/scripts/ig-hotfix/role_check.py @@ -40,8 +40,12 @@ if not rev: continue if r.id != rev.id: - sys.stderr.write(f"Role id={r.id} {r.content_type!r} {r.object_id} {r.role_field} is pointing to an object using a different role: id={rev.id} {rev.content_type!r} {rev.object_id} {rev.role_field}\n") - crosslinked[r.content_type_id][r.object_id][f'{r.role_field}_id'] = r.id + if (r.content_type_id, r.object_id, r.role_field) == (rev.content_type_id, rev.object_id, rev.role_field): + sys.stderr.write(f"Role id={r.id} {r.content_type!r} {r.object_id} {r.role_field} is an orphaned duplicate of Role id={rev.id}, which is actually being used by the assigned resource\n") + orphaned_roles.append(r.id) + else: + sys.stderr.write(f"Role id={r.id} {r.content_type!r} {r.object_id} {r.role_field} is pointing to an object using a different role: id={rev.id} {rev.content_type!r} {rev.object_id} {rev.role_field}\n") + crosslinked[r.content_type_id][r.object_id][f'{r.role_field}_id'] = r.id continue @@ -57,6 +61,7 @@ print("# Role objects that are assigned to objects that do not exist") for r in orphaned_roles: + print(f"Role.objects.filter(id={r}).update(object_id=None)") print(f"Role.objects.filter(id={r}).delete()") From 53c5feaf6b4db22130e78ed948b372bbd1a9f0eb Mon Sep 17 00:00:00 2001 From: Jeff Bradberry Date: Wed, 1 May 2024 16:27:00 -0400 Subject: [PATCH 42/75] Set up Seth's bad role scenario --- tools/scripts/ig-hotfix/test.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 tools/scripts/ig-hotfix/test.py diff --git a/tools/scripts/ig-hotfix/test.py b/tools/scripts/ig-hotfix/test.py new file mode 100644 index 000000000000..c4791314e64d --- /dev/null +++ b/tools/scripts/ig-hotfix/test.py @@ -0,0 +1,19 @@ +from django.db import connection +from awx.main.models import InstanceGroup + +InstanceGroup.objects.filter(name__in=('green', 'yellow', 'red')).delete() + +green = InstanceGroup.objects.create(name='green') +red = InstanceGroup.objects.create(name='red') +yellow = InstanceGroup.objects.create(name='yellow') + +for ig in InstanceGroup.objects.all(): + print((ig.id, ig.name, ig.use_role_id)) + +with connection.cursor() as cursor: + cursor.execute("UPDATE main_instancegroup SET use_role_id = NULL WHERE name = 'red'") + cursor.execute(f"UPDATE main_instancegroup SET use_role_id = {green.use_role_id} WHERE name = 'yellow'") + +print("=====================================") +for ig in InstanceGroup.objects.all(): + print((ig.id, ig.name, ig.use_role_id)) From a8c07b06d86099a5a3825ce4d33630954af32e2a Mon Sep 17 00:00:00 2001 From: Jeff Bradberry Date: Wed, 1 May 2024 16:39:00 -0400 Subject: [PATCH 43/75] Set up an enhanced version of Seth's bad role scenario --- tools/scripts/ig-hotfix/test2.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 tools/scripts/ig-hotfix/test2.py diff --git a/tools/scripts/ig-hotfix/test2.py b/tools/scripts/ig-hotfix/test2.py new file mode 100644 index 000000000000..66ec04d78a1d --- /dev/null +++ b/tools/scripts/ig-hotfix/test2.py @@ -0,0 +1,20 @@ +from django.db import connection +from awx.main.models import InstanceGroup + +InstanceGroup.objects.filter(name__in=('green', 'yellow', 'red')).delete() + +green = InstanceGroup.objects.create(name='green') +red = InstanceGroup.objects.create(name='red') +yellow = InstanceGroup.objects.create(name='yellow') + +for ig in InstanceGroup.objects.all(): + print((ig.id, ig.name, ig.use_role_id)) + +with connection.cursor() as cursor: + cursor.execute(f"UPDATE main_rbac_roles SET object_id = NULL WHERE id = {red.use_role_id}") + cursor.execute("UPDATE main_instancegroup SET use_role_id = NULL WHERE name = 'red'") + cursor.execute(f"UPDATE main_instancegroup SET use_role_id = {green.use_role_id} WHERE name = 'yellow'") + +print("=====================================") +for ig in InstanceGroup.objects.all(): + print((ig.id, ig.name, ig.use_role_id)) From 5cfeeb3e87615b83392d6ce8c132171721dad9fc Mon Sep 17 00:00:00 2001 From: Jeff Bradberry Date: Wed, 1 May 2024 16:42:53 -0400 Subject: [PATCH 44/75] Treat resources with null role fks differently The underlying role should be re-linked, instead of treated as orphaned. --- tools/scripts/ig-hotfix/role_check.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/tools/scripts/ig-hotfix/role_check.py b/tools/scripts/ig-hotfix/role_check.py index 5018a57462ce..f5d902340fad 100644 --- a/tools/scripts/ig-hotfix/role_check.py +++ b/tools/scripts/ig-hotfix/role_check.py @@ -37,12 +37,13 @@ orphaned_roles.append(r.id) continue rev = getattr(r.content_object, r.role_field, None) - if not rev: - continue - if r.id != rev.id: - if (r.content_type_id, r.object_id, r.role_field) == (rev.content_type_id, rev.object_id, rev.role_field): + if rev is None or r.id != rev.id: + if rev and (r.content_type_id, r.object_id, r.role_field) == (rev.content_type_id, rev.object_id, rev.role_field): sys.stderr.write(f"Role id={r.id} {r.content_type!r} {r.object_id} {r.role_field} is an orphaned duplicate of Role id={rev.id}, which is actually being used by the assigned resource\n") orphaned_roles.append(r.id) + elif not rev: + sys.stderr.write(f"Role id={r.id} {r.content_type!r} {r.object_id} {r.role_field} is pointing to an object currently using no role\n") + crosslinked[r.content_type_id][r.object_id][f'{r.role_field}_id'] = r.id else: sys.stderr.write(f"Role id={r.id} {r.content_type!r} {r.object_id} {r.role_field} is pointing to an object using a different role: id={rev.id} {rev.content_type!r} {rev.object_id} {rev.role_field}\n") crosslinked[r.content_type_id][r.object_id][f'{r.role_field}_id'] = r.id @@ -55,7 +56,7 @@ print(f"""\ from django.contrib.contenttypes.models import ContentType -from awx.main.models.rbac import Role, batch_role_ancestor_rebuilding +from awx.main.models.rbac import Role """) @@ -76,7 +77,6 @@ print(f"cls.objects.filter(id={obj}).update(**{kv!r})") print(f"queue.append((cls, {obj}))") -print(f"\nwith batch_role_ancestor_rebuilding():") -print(f" for cls, obj_id in queue:") -print(f" obj = cls.objects.get(id=obj_id)") -print(f" obj.save()") +print(f"\n\nfor cls, obj_id in queue:") +print(f" obj = cls.objects.get(id=obj_id)") +print(f" obj.save()") From 85fc81aab1627715bb3ffa3b1814581083ea8242 Mon Sep 17 00:00:00 2001 From: Jeff Bradberry Date: Fri, 3 May 2024 15:11:33 -0400 Subject: [PATCH 45/75] Start a new script that can be used to examine a Role's ancestry --- tools/scripts/ig-hotfix/role_chain.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 tools/scripts/ig-hotfix/role_chain.py diff --git a/tools/scripts/ig-hotfix/role_chain.py b/tools/scripts/ig-hotfix/role_chain.py new file mode 100644 index 000000000000..4b8b7b691890 --- /dev/null +++ b/tools/scripts/ig-hotfix/role_chain.py @@ -0,0 +1,14 @@ +from collections import defaultdict +import os +import sys + +from django.contrib.contenttypes.models import ContentType + +from awx.main.fields import ImplicitRoleField +from awx.main.models.rbac import Role + + +r_id = int(os.environ.get('role')) +r = Role.objects.get(id=r_id) + +print(r) From 1f154742df2dc808222a3cadda3471a0beb43133 Mon Sep 17 00:00:00 2001 From: Jeff Bradberry Date: Fri, 3 May 2024 15:43:48 -0400 Subject: [PATCH 46/75] Make the role_chain.py script emit a Graphviz file of the Role relationships. --- tools/scripts/ig-hotfix/role_chain.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/tools/scripts/ig-hotfix/role_chain.py b/tools/scripts/ig-hotfix/role_chain.py index 4b8b7b691890..e64f47dfc6cd 100644 --- a/tools/scripts/ig-hotfix/role_chain.py +++ b/tools/scripts/ig-hotfix/role_chain.py @@ -1,5 +1,4 @@ from collections import defaultdict -import os import sys from django.contrib.contenttypes.models import ContentType @@ -8,7 +7,16 @@ from awx.main.models.rbac import Role -r_id = int(os.environ.get('role')) -r = Role.objects.get(id=r_id) +print("digraph G {") -print(r) +for r in Role.objects.order_by('id'): + if r.content_type is None: + print(f' {r.id} [shape=box,label="id={r.id}\lsingleton={r.singleton_name}\l"]') + else: + print(f' {r.id} [shape=box,label="id={r.id}\lct={r.content_type}\lobject_id={r.object_id}\lrole_field={r.role_field}\l"]') + +for r in Role.objects.order_by('id'): + for p in r.parents.all(): + print(f" {p.id} -> {r.id}") + +print("}") From 0e87e97820166e4442d66e6ca8825c1403f73e18 Mon Sep 17 00:00:00 2001 From: Jeff Bradberry Date: Mon, 6 May 2024 10:31:17 -0400 Subject: [PATCH 47/75] Check for a broken ContentType -> model and log and skip Apparently this has happened to a customer, per Nate Becker. --- tools/scripts/ig-hotfix/role_check.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tools/scripts/ig-hotfix/role_check.py b/tools/scripts/ig-hotfix/role_check.py index f5d902340fad..ec8511dfd996 100644 --- a/tools/scripts/ig-hotfix/role_check.py +++ b/tools/scripts/ig-hotfix/role_check.py @@ -13,6 +13,9 @@ for ct in ContentType.objects.order_by('id'): cls = ct.model_class() + if cls is None: + sys.stderr.write(f"{ct!r} does not have a corresponding model class in the codebase. Skipping.\n") + continue if not any(isinstance(f, ImplicitRoleField) for f in cls._meta.fields): continue for obj in cls.objects.all(): From 20504042c98a53016d6ec9727ba14bbf74073afb Mon Sep 17 00:00:00 2001 From: Jeff Bradberry Date: Mon, 6 May 2024 12:03:17 -0400 Subject: [PATCH 48/75] Graph out only the parent/child chains from a given Role Doing the entire graph is too much on any system with real amounts of Roles. --- tools/scripts/ig-hotfix/role_chain.py | 40 ++++++++++++++++++++++++--- 1 file changed, 36 insertions(+), 4 deletions(-) diff --git a/tools/scripts/ig-hotfix/role_chain.py b/tools/scripts/ig-hotfix/role_chain.py index e64f47dfc6cd..01171ca5344c 100644 --- a/tools/scripts/ig-hotfix/role_chain.py +++ b/tools/scripts/ig-hotfix/role_chain.py @@ -1,4 +1,5 @@ from collections import defaultdict +import os import sys from django.contrib.contenttypes.models import ContentType @@ -7,16 +8,47 @@ from awx.main.models.rbac import Role +role_id = int(os.environ.get('role')) +role = Role.objects.get(id=role_id) + +all_roles = {role,} +graph = defaultdict(set) + + +# Role parents +new_parents = {role,} +while new_parents: + old_parents = new_parents + new_parents = set() + for r in old_parents: + new_parents |= (set(r.parents.all()) - all_roles) + for p in r.parents.all(): + graph[p.id].add(r.id) + all_roles |= new_parents + +# Role children +new_children = {role,} +while new_children: + old_children = new_children + new_children = set() + for r in old_children: + new_children |= (set(r.children.all()) - all_roles) + for c in r.children.all(): + graph[r.id].add(c.id) + all_roles |= new_children + + print("digraph G {") -for r in Role.objects.order_by('id'): +for r in sorted(all_roles, key=lambda x: x.id): if r.content_type is None: print(f' {r.id} [shape=box,label="id={r.id}\lsingleton={r.singleton_name}\l"]') else: print(f' {r.id} [shape=box,label="id={r.id}\lct={r.content_type}\lobject_id={r.object_id}\lrole_field={r.role_field}\l"]') -for r in Role.objects.order_by('id'): - for p in r.parents.all(): - print(f" {p.id} -> {r.id}") +print() +for p_id, children in sorted(graph.items()): + for c_id in children: + print(f" {p_id} -> {c_id}") print("}") From d675207f997fd5b82e0b1e0e06be4eb89ebd8f7e Mon Sep 17 00:00:00 2001 From: Jeff Bradberry Date: Mon, 6 May 2024 13:47:35 -0400 Subject: [PATCH 49/75] Handle the case where a resource points to a Role which isn't in the db --- tools/scripts/ig-hotfix/role_check.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/tools/scripts/ig-hotfix/role_check.py b/tools/scripts/ig-hotfix/role_check.py index ec8511dfd996..d290d9c15c5e 100644 --- a/tools/scripts/ig-hotfix/role_check.py +++ b/tools/scripts/ig-hotfix/role_check.py @@ -22,7 +22,13 @@ for f in cls._meta.fields: if not isinstance(f, ImplicitRoleField): continue - r = getattr(obj, f.name, None) + r_id = getattr(obj, f'{f.name}_id', None) + try: + r = getattr(obj, f.name, None) + except Role.DoesNotExist: + sys.stderr.write(f"{cls} id={obj.id} {f.name} points to Role id={r_id}, which is not in the database.") + crosslinked[ct.id][obj.id][f'{f.name}_id'] = None + continue if not r: sys.stderr.write(f"{cls} id={obj.id} {f.name} does not have a Role object\n") crosslinked[ct.id][obj.id][f'{f.name}_id'] = None From a0b376a6cac4ac118a0b646d3d4b9e5c40928c8e Mon Sep 17 00:00:00 2001 From: Jeff Bradberry Date: Mon, 6 May 2024 14:48:06 -0400 Subject: [PATCH 50/75] Set up a scenario where IG.use_role_id points to something no longer there This is actually happening for one customer, though it seems like it shouldn't be if the foreign key constraint is set back up properly. In order to recreate it, I had to add the constraint back with 'NOT VALID' added on to prevent the check. --- tools/scripts/ig-hotfix/test3.py | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 tools/scripts/ig-hotfix/test3.py diff --git a/tools/scripts/ig-hotfix/test3.py b/tools/scripts/ig-hotfix/test3.py new file mode 100644 index 000000000000..2bf17d705ea3 --- /dev/null +++ b/tools/scripts/ig-hotfix/test3.py @@ -0,0 +1,28 @@ +from django.db import connection +from awx.main.models import InstanceGroup + +InstanceGroup.objects.filter(name__in=('green', 'yellow', 'red', 'blue')).delete() + +green = InstanceGroup.objects.create(name='green') +red = InstanceGroup.objects.create(name='red') +yellow = InstanceGroup.objects.create(name='yellow') +blue = InstanceGroup.objects.create(name='blue') + +for ig in InstanceGroup.objects.all(): + print((ig.id, ig.name, ig.use_role_id)) + +with connection.cursor() as cursor: + cursor.execute("ALTER TABLE main_instancegroup DROP CONSTRAINT main_instancegroup_use_role_id_48ea7ecc_fk_main_rbac_roles_id") + + cursor.execute(f"UPDATE main_rbac_roles SET object_id = NULL WHERE id = {red.use_role_id}") + cursor.execute(f"DELETE FROM main_rbac_roles_parents WHERE from_role_id = {blue.use_role_id} OR to_role_id = {blue.use_role_id}") + cursor.execute(f"DELETE FROM main_rbac_role_ancestors WHERE ancestor_id = {blue.use_role_id} OR descendent_id = {blue.use_role_id}") + cursor.execute(f"DELETE FROM main_rbac_roles WHERE id = {blue.use_role_id}") + cursor.execute("UPDATE main_instancegroup SET use_role_id = NULL WHERE name = 'red'") + cursor.execute(f"UPDATE main_instancegroup SET use_role_id = {green.use_role_id} WHERE name = 'yellow'") + + cursor.execute("ALTER TABLE main_instancegroup ADD CONSTRAINT main_instancegroup_use_role_id_48ea7ecc_fk_main_rbac_roles_id FOREIGN KEY (use_role_id) REFERENCES public.main_rbac_roles(id) DEFERRABLE INITIALLY DEFERRED NOT VALID") + +print("=====================================") +for ig in InstanceGroup.objects.all(): + print((ig.id, ig.name, ig.use_role_id)) From c8829b057e1bc19b47ae3de9f7d1c7cb00436d5c Mon Sep 17 00:00:00 2001 From: Jeff Bradberry Date: Tue, 7 May 2024 11:49:04 -0400 Subject: [PATCH 51/75] First cut at checking the role hierarchy Checking if parents and implicit_parents are consistent with ancestors. --- tools/scripts/ig-hotfix/role_check.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/tools/scripts/ig-hotfix/role_check.py b/tools/scripts/ig-hotfix/role_check.py index d290d9c15c5e..95ec14d9603e 100644 --- a/tools/scripts/ig-hotfix/role_check.py +++ b/tools/scripts/ig-hotfix/role_check.py @@ -1,4 +1,5 @@ from collections import defaultdict +import json import sys from django.contrib.contenttypes.models import ContentType @@ -41,6 +42,18 @@ sys.stderr.write('===================================\n') for r in Role.objects.exclude(role_field__startswith='system_').order_by('id'): + + # The ancestor list should be a superset of both parents and implicit_parents + parents = set(r.parents.values_list('id', flat=True)) + ancestors = set(r.ancestors.values_list('id', flat=True)) + implicit = set(json.loads(r.implicit_parents)) + + if not parents <= ancestors: + sys.stderr.write(f"Role id={r.id} has parents that are not in the ancestor list: {parents - ancestors}\n") + if not implicit <= ancestors: + sys.stderr.write(f"Role id={r.id} has implicit_parents that are not in the ancestor list: {implicit - ancestors}\n") + + # Check that the Role's generic foreign key points to a legitimate object if not r.content_object: sys.stderr.write(f"Role id={r.id} is missing a valid content_object: {r.content_type!r} {r.object_id} {r.role_field}\n") orphaned_roles.append(r.id) From 87e9dcb6d7e9223fe2b3f28b94757a7432e16759 Mon Sep 17 00:00:00 2001 From: Jeff Bradberry Date: Tue, 7 May 2024 14:27:37 -0400 Subject: [PATCH 52/75] Attempt to more thoroughly check the parents of each Role This version, however, has false positives because Roles become children of Team.member_role when a Role is granted to a Team. --- tools/scripts/ig-hotfix/role_check.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/tools/scripts/ig-hotfix/role_check.py b/tools/scripts/ig-hotfix/role_check.py index 95ec14d9603e..91cdaf8b96dd 100644 --- a/tools/scripts/ig-hotfix/role_check.py +++ b/tools/scripts/ig-hotfix/role_check.py @@ -58,6 +58,21 @@ sys.stderr.write(f"Role id={r.id} is missing a valid content_object: {r.content_type!r} {r.object_id} {r.role_field}\n") orphaned_roles.append(r.id) continue + + # Check the resource's role field parents for consistency with Role.parents.all(). + # f._resolve_parent_roles() walks the f.parent_role list, splitting on dots and recursively + # getting those resources as well, until we are down to just the Role ids at the end. + f = r.content_object._meta.get_field(r.role_field) + parent_roles = f._resolve_parent_roles(r.content_object) + minus = parent_roles - parents + if minus: + minus = [f"{x.content_type} {x.object_id} {x.role_field}" for x in Role.objects.filter(id__in=minus)] + sys.stderr.write(f"Role id={r.id} is missing parents: {minus}\n") + plus = parents - parent_roles + if plus: + plus = [f"{x.content_type} {x.object_id} {x.role_field}" for x in Role.objects.filter(id__in=plus)] + sys.stderr.write(f"Role id={r.id} has excess parents: {plus}\n") + rev = getattr(r.content_object, r.role_field, None) if rev is None or r.id != rev.id: if rev and (r.content_type_id, r.object_id, r.role_field) == (rev.content_type_id, rev.object_id, rev.role_field): From 054cbe69d774e6f9029b523d4503773532557edf Mon Sep 17 00:00:00 2001 From: Jeff Bradberry Date: Tue, 7 May 2024 14:36:53 -0400 Subject: [PATCH 53/75] Exclude the team grant false positives The results in my test now look correct. --- tools/scripts/ig-hotfix/role_check.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/tools/scripts/ig-hotfix/role_check.py b/tools/scripts/ig-hotfix/role_check.py index 91cdaf8b96dd..32d38d2651c9 100644 --- a/tools/scripts/ig-hotfix/role_check.py +++ b/tools/scripts/ig-hotfix/role_check.py @@ -8,6 +8,8 @@ from awx.main.models.rbac import Role +team_ct = ContentType.objects.get(app_label='main', model='team') + crosslinked = defaultdict(lambda: defaultdict(dict)) orphaned_roles = [] @@ -70,8 +72,9 @@ sys.stderr.write(f"Role id={r.id} is missing parents: {minus}\n") plus = parents - parent_roles if plus: - plus = [f"{x.content_type} {x.object_id} {x.role_field}" for x in Role.objects.filter(id__in=plus)] - sys.stderr.write(f"Role id={r.id} has excess parents: {plus}\n") + plus = [f"{x.content_type} {x.object_id} {x.role_field}" for x in Role.objects.filter(id__in=plus).exclude(content_type=team_ct, role_field='member_role')] + if plus: + sys.stderr.write(f"Role id={r.id} has excess parents: {plus}\n") rev = getattr(r.content_object, r.role_field, None) if rev is None or r.id != rev.id: From f613b76baa784fa0f22e5f1e9000659f935f3122 Mon Sep 17 00:00:00 2001 From: Jeff Bradberry Date: Tue, 7 May 2024 16:18:55 -0400 Subject: [PATCH 54/75] Modify the role parent check logic to stay in the roles as much as possible since the foreign keys to the roles from the resources can make us go wrong almost immediately. --- tools/scripts/ig-hotfix/role_check.py | 46 ++++++++++++++++++++------- 1 file changed, 35 insertions(+), 11 deletions(-) diff --git a/tools/scripts/ig-hotfix/role_check.py b/tools/scripts/ig-hotfix/role_check.py index 32d38d2651c9..776bd74cd59c 100644 --- a/tools/scripts/ig-hotfix/role_check.py +++ b/tools/scripts/ig-hotfix/role_check.py @@ -3,6 +3,7 @@ import sys from django.contrib.contenttypes.models import ContentType +from django.db.models.fields.related_descriptors import ManyToManyDescriptor from awx.main.fields import ImplicitRoleField from awx.main.models.rbac import Role @@ -14,6 +15,20 @@ orphaned_roles = [] +def resolve(obj, path): + fname, _, path = path.partition('.') + new_obj = getattr(obj, fname, None) + if new_obj is None: + return set() + if not path: + return {new_obj,} + + if isinstance(new_obj, ManyToManyDescriptor): + return {x for o in new_obj.all() for x in resolve(o, path)} + + return resolve(new_obj, path) + + for ct in ContentType.objects.order_by('id'): cls = ct.model_class() if cls is None: @@ -62,19 +77,28 @@ continue # Check the resource's role field parents for consistency with Role.parents.all(). - # f._resolve_parent_roles() walks the f.parent_role list, splitting on dots and recursively - # getting those resources as well, until we are down to just the Role ids at the end. f = r.content_object._meta.get_field(r.role_field) - parent_roles = f._resolve_parent_roles(r.content_object) - minus = parent_roles - parents - if minus: - minus = [f"{x.content_type} {x.object_id} {x.role_field}" for x in Role.objects.filter(id__in=minus)] - sys.stderr.write(f"Role id={r.id} is missing parents: {minus}\n") - plus = parents - parent_roles + f_parent = set(f.parent_role) if isinstance(f.parent_role, list) else {f.parent_role,} + dotted = {x for p in f_parent if '.' in p for x in resolve(r.content_object, p)} + plus = set() + for p in r.parents.all(): + if p.singleton_name: + if f'singleton:{p.singleton_name}' not in f_parent: + plus.add(p) + elif (p.content_type, p.role_field) == (team_ct, 'member_role'): + # Team has been granted this role; probably legitimate. + continue + elif (p.content_type, p.object_id) == (r.content_type, r.object_id): + if p.role_field not in f_parent: + plus.add(p) + elif p in dotted: + continue + else: + plus.add(p) + if plus: - plus = [f"{x.content_type} {x.object_id} {x.role_field}" for x in Role.objects.filter(id__in=plus).exclude(content_type=team_ct, role_field='member_role')] - if plus: - sys.stderr.write(f"Role id={r.id} has excess parents: {plus}\n") + plus = [f"{x.content_type!r} {x.object_id} {x.role_field}" for x in plus] + sys.stderr.write(f"Role id={r.id} has cross-linked parents: {plus}\n") rev = getattr(r.content_object, r.role_field, None) if rev is None or r.id != rev.id: From fe77fda7b23e3f0d8980fda06c64a62a3fd8aade Mon Sep 17 00:00:00 2001 From: Jeff Bradberry Date: Wed, 8 May 2024 10:08:02 -0400 Subject: [PATCH 55/75] Exclude more files in the .gitignore --- tools/scripts/ig-hotfix/.gitignore | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tools/scripts/ig-hotfix/.gitignore b/tools/scripts/ig-hotfix/.gitignore index a946df02b7eb..c75dd77f731a 100644 --- a/tools/scripts/ig-hotfix/.gitignore +++ b/tools/scripts/ig-hotfix/.gitignore @@ -1,2 +1,7 @@ *~ customer-backup.tar.* +*.db +*.log +*.dot +*.png +*.tar.* From d67af794515346ca41f7ecc51e49380db8b0b9f6 Mon Sep 17 00:00:00 2001 From: Jeff Bradberry Date: Wed, 8 May 2024 10:21:03 -0400 Subject: [PATCH 56/75] Attempt to correct any crosslinked parents I think that rebuild_role_ancestor_list() will then correctly update all of the affected Role.ancestors. --- tools/scripts/ig-hotfix/role_check.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tools/scripts/ig-hotfix/role_check.py b/tools/scripts/ig-hotfix/role_check.py index 776bd74cd59c..076f2e1b9085 100644 --- a/tools/scripts/ig-hotfix/role_check.py +++ b/tools/scripts/ig-hotfix/role_check.py @@ -12,6 +12,7 @@ team_ct = ContentType.objects.get(app_label='main', model='team') crosslinked = defaultdict(lambda: defaultdict(dict)) +crosslinked_parents = defaultdict(list) orphaned_roles = [] @@ -99,6 +100,7 @@ def resolve(obj, path): if plus: plus = [f"{x.content_type!r} {x.object_id} {x.role_field}" for x in plus] sys.stderr.write(f"Role id={r.id} has cross-linked parents: {plus}\n") + crosslinked_parents[r.id].extend(x.id for x in plus) rev = getattr(r.content_object, r.role_field, None) if rev is None or r.id != rev.id: @@ -141,6 +143,10 @@ def resolve(obj, path): print(f"cls.objects.filter(id={obj}).update(**{kv!r})") print(f"queue.append((cls, {obj}))") +for child, parents in crosslinked_parents.items(): + print(f"\n\nr = Role.objects.get(id={child})") + print(f"r.parents.remove(*Role.objects.filter(id__in={parents!r}))") + print(f"\n\nfor cls, obj_id in queue:") print(f" obj = cls.objects.get(id=obj_id)") print(f" obj.save()") From 6a317cca1b222b287bab6f9a1471964f471e5c60 Mon Sep 17 00:00:00 2001 From: Jeff Bradberry Date: Wed, 8 May 2024 10:39:26 -0400 Subject: [PATCH 57/75] Remove the role_chain.py module it wound up being unworkable, and I think ultimately we only need to check the immediate parentage of each role. --- tools/scripts/ig-hotfix/role_chain.py | 54 --------------------------- 1 file changed, 54 deletions(-) delete mode 100644 tools/scripts/ig-hotfix/role_chain.py diff --git a/tools/scripts/ig-hotfix/role_chain.py b/tools/scripts/ig-hotfix/role_chain.py deleted file mode 100644 index 01171ca5344c..000000000000 --- a/tools/scripts/ig-hotfix/role_chain.py +++ /dev/null @@ -1,54 +0,0 @@ -from collections import defaultdict -import os -import sys - -from django.contrib.contenttypes.models import ContentType - -from awx.main.fields import ImplicitRoleField -from awx.main.models.rbac import Role - - -role_id = int(os.environ.get('role')) -role = Role.objects.get(id=role_id) - -all_roles = {role,} -graph = defaultdict(set) - - -# Role parents -new_parents = {role,} -while new_parents: - old_parents = new_parents - new_parents = set() - for r in old_parents: - new_parents |= (set(r.parents.all()) - all_roles) - for p in r.parents.all(): - graph[p.id].add(r.id) - all_roles |= new_parents - -# Role children -new_children = {role,} -while new_children: - old_children = new_children - new_children = set() - for r in old_children: - new_children |= (set(r.children.all()) - all_roles) - for c in r.children.all(): - graph[r.id].add(c.id) - all_roles |= new_children - - -print("digraph G {") - -for r in sorted(all_roles, key=lambda x: x.id): - if r.content_type is None: - print(f' {r.id} [shape=box,label="id={r.id}\lsingleton={r.singleton_name}\l"]') - else: - print(f' {r.id} [shape=box,label="id={r.id}\lct={r.content_type}\lobject_id={r.object_id}\lrole_field={r.role_field}\l"]') - -print() -for p_id, children in sorted(graph.items()): - for c_id in children: - print(f" {p_id} -> {c_id}") - -print("}") From 4a8f6e45f875fdf1460dc61bf8ca1b3da5c0bbbe Mon Sep 17 00:00:00 2001 From: Jeff Bradberry Date: Wed, 8 May 2024 10:40:53 -0400 Subject: [PATCH 58/75] Move the "test" files into their own directory --- tools/scripts/ig-hotfix/{ => scenarios}/test.py | 0 tools/scripts/ig-hotfix/{ => scenarios}/test2.py | 0 tools/scripts/ig-hotfix/{ => scenarios}/test3.py | 0 3 files changed, 0 insertions(+), 0 deletions(-) rename tools/scripts/ig-hotfix/{ => scenarios}/test.py (100%) rename tools/scripts/ig-hotfix/{ => scenarios}/test2.py (100%) rename tools/scripts/ig-hotfix/{ => scenarios}/test3.py (100%) diff --git a/tools/scripts/ig-hotfix/test.py b/tools/scripts/ig-hotfix/scenarios/test.py similarity index 100% rename from tools/scripts/ig-hotfix/test.py rename to tools/scripts/ig-hotfix/scenarios/test.py diff --git a/tools/scripts/ig-hotfix/test2.py b/tools/scripts/ig-hotfix/scenarios/test2.py similarity index 100% rename from tools/scripts/ig-hotfix/test2.py rename to tools/scripts/ig-hotfix/scenarios/test2.py diff --git a/tools/scripts/ig-hotfix/test3.py b/tools/scripts/ig-hotfix/scenarios/test3.py similarity index 100% rename from tools/scripts/ig-hotfix/test3.py rename to tools/scripts/ig-hotfix/scenarios/test3.py From ee3e3e15160be74b250ad3075e3400846c20e4db Mon Sep 17 00:00:00 2001 From: Jeff Bradberry Date: Wed, 8 May 2024 12:49:38 -0400 Subject: [PATCH 59/75] First cut at detecting which foreign keys enter and exit the topology tables --- tools/scripts/ig-hotfix/foreignkeys.sql | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 tools/scripts/ig-hotfix/foreignkeys.sql diff --git a/tools/scripts/ig-hotfix/foreignkeys.sql b/tools/scripts/ig-hotfix/foreignkeys.sql new file mode 100644 index 000000000000..158a56254d6c --- /dev/null +++ b/tools/scripts/ig-hotfix/foreignkeys.sql @@ -0,0 +1,9 @@ +SELECT DISTINCT + tc.table_name, + ccu.table_name AS foreign_table_name +FROM information_schema.table_constraints AS tc +JOIN information_schema.constraint_column_usage AS ccu + ON ccu.constraint_name = tc.constraint_name +WHERE tc.constraint_type = 'FOREIGN KEY' + AND (tc.table_name IN ('main_instance', 'main_instancegroup', 'main_instancegroup_instances') + OR ccu.table_name IN ('main_instance', 'main_instancegroup', 'main_instancegroup_instances')); From 9e22865d2e0e216299bd6979d07eb5d364d7f905 Mon Sep 17 00:00:00 2001 From: Jeff Bradberry Date: Wed, 8 May 2024 13:00:46 -0400 Subject: [PATCH 60/75] Filter out the relations within the known topology tables --- tools/scripts/ig-hotfix/foreignkeys.sql | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/tools/scripts/ig-hotfix/foreignkeys.sql b/tools/scripts/ig-hotfix/foreignkeys.sql index 158a56254d6c..b5c5389e6143 100644 --- a/tools/scripts/ig-hotfix/foreignkeys.sql +++ b/tools/scripts/ig-hotfix/foreignkeys.sql @@ -5,5 +5,9 @@ FROM information_schema.table_constraints AS tc JOIN information_schema.constraint_column_usage AS ccu ON ccu.constraint_name = tc.constraint_name WHERE tc.constraint_type = 'FOREIGN KEY' - AND (tc.table_name IN ('main_instance', 'main_instancegroup', 'main_instancegroup_instances') - OR ccu.table_name IN ('main_instance', 'main_instancegroup', 'main_instancegroup_instances')); + AND + (tc.table_name IN ('main_instance', 'main_instancegroup', 'main_instancegroup_instances') + AND ccu.table_name NOT IN ('main_instance', 'main_instancegroup', 'main_instancegroup_instances')) + OR + (ccu.table_name IN ('main_instance', 'main_instancegroup', 'main_instancegroup_instances') + AND tc.table_name NOT IN ('main_instance', 'main_instancegroup', 'main_instancegroup_instances')); From b837d549ff028ec2282a399e152f2137f2714ac4 Mon Sep 17 00:00:00 2001 From: Jeff Bradberry Date: Wed, 8 May 2024 14:09:34 -0400 Subject: [PATCH 61/75] Split the foreign key sql script into an 'into' and 'from' portion Also, make use of up-front defined arrays of the tables involved, for ease of editing in the future. --- tools/scripts/ig-hotfix/foreignkeys.sql | 48 ++++++++++++++++++------- 1 file changed, 35 insertions(+), 13 deletions(-) diff --git a/tools/scripts/ig-hotfix/foreignkeys.sql b/tools/scripts/ig-hotfix/foreignkeys.sql index b5c5389e6143..e0816c4bd86a 100644 --- a/tools/scripts/ig-hotfix/foreignkeys.sql +++ b/tools/scripts/ig-hotfix/foreignkeys.sql @@ -1,13 +1,35 @@ -SELECT DISTINCT - tc.table_name, - ccu.table_name AS foreign_table_name -FROM information_schema.table_constraints AS tc -JOIN information_schema.constraint_column_usage AS ccu - ON ccu.constraint_name = tc.constraint_name -WHERE tc.constraint_type = 'FOREIGN KEY' - AND - (tc.table_name IN ('main_instance', 'main_instancegroup', 'main_instancegroup_instances') - AND ccu.table_name NOT IN ('main_instance', 'main_instancegroup', 'main_instancegroup_instances')) - OR - (ccu.table_name IN ('main_instance', 'main_instancegroup', 'main_instancegroup_instances') - AND tc.table_name NOT IN ('main_instance', 'main_instancegroup', 'main_instancegroup_instances')); +DO $$ +DECLARE + topology text[] := ARRAY['main_instance', 'main_instancegroup', 'main_instancegroup_instances']; + excluded text[] := ARRAY['main_instance', 'main_instancegroup', 'main_instancegroup_instances', 'main_organizationinstancegroupmembership', 'main_unifiedjobtemplateinstancegroupmembership', 'main_inventoryinstancegroupmembership']; +BEGIN + CREATE TABLE tmp_fk_from AS ( + SELECT DISTINCT + tc.table_name, + ccu.table_name AS foreign_table_name + FROM information_schema.table_constraints AS tc + JOIN information_schema.constraint_column_usage AS ccu + ON ccu.constraint_name = tc.constraint_name + WHERE tc.constraint_type = 'FOREIGN KEY' + AND tc.table_name = ANY (excluded) + AND NOT ccu.table_name = ANY (topology) + ); + + CREATE TABLE tmp_fk_into AS ( + SELECT DISTINCT + tc.table_name, + ccu.table_name AS foreign_table_name + FROM information_schema.table_constraints AS tc + JOIN information_schema.constraint_column_usage AS ccu + ON ccu.constraint_name = tc.constraint_name + WHERE tc.constraint_type = 'FOREIGN KEY' + AND ccu.table_name = ANY (excluded) + AND NOT tc.table_name = ANY (topology) + ); +END $$; + +SELECT * FROM tmp_fk_from; +SELECT * FROM tmp_fk_into; + +DROP TABLE tmp_fk_from; +DROP TABLE tmp_fk_into; From ad9d5904d8d31c3aeaeac0a8b9492adddea65a1b Mon Sep 17 00:00:00 2001 From: Jeff Bradberry Date: Wed, 8 May 2024 15:28:27 -0400 Subject: [PATCH 62/75] Adjusted foreignkeys.sql for correctness Some relationships known to be handled by the special mapping sql file were being caught as false positives. --- tools/scripts/ig-hotfix/foreignkeys.sql | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/tools/scripts/ig-hotfix/foreignkeys.sql b/tools/scripts/ig-hotfix/foreignkeys.sql index e0816c4bd86a..ac9ba2a5de17 100644 --- a/tools/scripts/ig-hotfix/foreignkeys.sql +++ b/tools/scripts/ig-hotfix/foreignkeys.sql @@ -1,7 +1,10 @@ DO $$ DECLARE + -- add table names here when they get excluded from main / included in topology dump topology text[] := ARRAY['main_instance', 'main_instancegroup', 'main_instancegroup_instances']; - excluded text[] := ARRAY['main_instance', 'main_instancegroup', 'main_instancegroup_instances', 'main_organizationinstancegroupmembership', 'main_unifiedjobtemplateinstancegroupmembership', 'main_inventoryinstancegroupmembership']; + + -- add table names here when they are handled by the special-case mapping + mapping text[] := ARRAY['main_organizationinstancegroupmembership', 'main_unifiedjobtemplateinstancegroupmembership', 'main_inventoryinstancegroupmembership']; BEGIN CREATE TABLE tmp_fk_from AS ( SELECT DISTINCT @@ -11,8 +14,8 @@ BEGIN JOIN information_schema.constraint_column_usage AS ccu ON ccu.constraint_name = tc.constraint_name WHERE tc.constraint_type = 'FOREIGN KEY' - AND tc.table_name = ANY (excluded) - AND NOT ccu.table_name = ANY (topology) + AND tc.table_name = ANY (topology) + AND NOT ccu.table_name = ANY (topology || mapping) ); CREATE TABLE tmp_fk_into AS ( @@ -23,8 +26,8 @@ BEGIN JOIN information_schema.constraint_column_usage AS ccu ON ccu.constraint_name = tc.constraint_name WHERE tc.constraint_type = 'FOREIGN KEY' - AND ccu.table_name = ANY (excluded) - AND NOT tc.table_name = ANY (topology) + AND ccu.table_name = ANY (topology) + AND NOT tc.table_name = ANY (topology || mapping) ); END $$; From 31db6a1447994d943cdfd77091503003bb04ddc1 Mon Sep 17 00:00:00 2001 From: Jeff Bradberry Date: Mon, 13 May 2024 16:00:26 -0400 Subject: [PATCH 63/75] Fix another instance where a bad resource->Role fk could throw a traceback --- tools/scripts/ig-hotfix/role_check.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/tools/scripts/ig-hotfix/role_check.py b/tools/scripts/ig-hotfix/role_check.py index 076f2e1b9085..6ca653fc6c23 100644 --- a/tools/scripts/ig-hotfix/role_check.py +++ b/tools/scripts/ig-hotfix/role_check.py @@ -45,7 +45,7 @@ def resolve(obj, path): try: r = getattr(obj, f.name, None) except Role.DoesNotExist: - sys.stderr.write(f"{cls} id={obj.id} {f.name} points to Role id={r_id}, which is not in the database.") + sys.stderr.write(f"{cls} id={obj.id} {f.name} points to Role id={r_id}, which is not in the database.\n") crosslinked[ct.id][obj.id][f'{f.name}_id'] = None continue if not r: @@ -102,7 +102,12 @@ def resolve(obj, path): sys.stderr.write(f"Role id={r.id} has cross-linked parents: {plus}\n") crosslinked_parents[r.id].extend(x.id for x in plus) - rev = getattr(r.content_object, r.role_field, None) + try: + rev = getattr(r.content_object, r.role_field, None) + except Role.DoesNotExist: + sys.stderr.write(f"Role id={r.id} {r.content_type!r} {r.object_id} {r.role_field} points at an object with a broken role.\n") + crosslinked[r.content_type_id][r.object_id][f'{r.role_field}_id'] = r.id + continue if rev is None or r.id != rev.id: if rev and (r.content_type_id, r.object_id, r.role_field) == (rev.content_type_id, rev.object_id, rev.role_field): sys.stderr.write(f"Role id={r.id} {r.content_type!r} {r.object_id} {r.role_field} is an orphaned duplicate of Role id={rev.id}, which is actually being used by the assigned resource\n") From 4cb061e7dba93a1bffd1950f1001d4bc773b404b Mon Sep 17 00:00:00 2001 From: Jeff Bradberry Date: Wed, 15 May 2024 15:02:18 -0400 Subject: [PATCH 64/75] Add a readme file with instructions --- tools/scripts/ig-hotfix/README.md | 36 +++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 tools/scripts/ig-hotfix/README.md diff --git a/tools/scripts/ig-hotfix/README.md b/tools/scripts/ig-hotfix/README.md new file mode 100644 index 000000000000..614d2b096b85 --- /dev/null +++ b/tools/scripts/ig-hotfix/README.md @@ -0,0 +1,36 @@ +# Hotfix for Instance Groups and Roles after backup/restore corruption # + +## role_check.py ## + +`awx-manage shell < role_check.py 2> role_check.log > fix.py` + +This checks the roles and resources on the system, and constructs a +fix.py file that will change the linkages of the roles that it finds +are incorrect. The command line above also redirects logging output to +a file. The fix.py file (and the log file) can then be examined (and +potentially modified) before performing the actual fix. + +`awx-manage shell < fix.py > fix.log 2>&1` + +This performs the fix, while redirecting all output to another log +file. Ideally, this file should wind up being empty after execution +completes. + +`awx-manage shell < role_check.py 2> role_check2.log > fix2.py` + +Re-run the check script in order to see that there are no remaining +problems. Ideally the log file will only consist of the equal-sign +lines. + + +## foreignkeys.sql ## + +This script uses Postgres internals to determine all of the foreign +keys that cross the boundaries established by our (old) backup/restore +logic. Users have no need to run this. + + +## scenarios/test*.py ## + +These files were used to set up corruption similar to that caused by +faulty backup/restore, for testing purposes. Do not use. From 8b4efbc973ef560d1723f5672ffffe88e0ed9b20 Mon Sep 17 00:00:00 2001 From: Jeff Bradberry Date: Tue, 21 May 2024 11:08:47 -0400 Subject: [PATCH 65/75] Do not throw away the container of cross-linked parents Since we use it twice, the second time to get the id field of each. --- tools/scripts/ig-hotfix/role_check.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/scripts/ig-hotfix/role_check.py b/tools/scripts/ig-hotfix/role_check.py index 6ca653fc6c23..3f11c5d5a8e6 100644 --- a/tools/scripts/ig-hotfix/role_check.py +++ b/tools/scripts/ig-hotfix/role_check.py @@ -98,8 +98,8 @@ def resolve(obj, path): plus.add(p) if plus: - plus = [f"{x.content_type!r} {x.object_id} {x.role_field}" for x in plus] - sys.stderr.write(f"Role id={r.id} has cross-linked parents: {plus}\n") + plus_repr = [f"{x.content_type!r} {x.object_id} {x.role_field}" for x in plus] + sys.stderr.write(f"Role id={r.id} has cross-linked parents: {plus_repr}\n") crosslinked_parents[r.id].extend(x.id for x in plus) try: From d16b69a102dc0babb2e7047635100343cf8f63f5 Mon Sep 17 00:00:00 2001 From: Jeff Bradberry Date: Thu, 23 May 2024 11:20:01 -0400 Subject: [PATCH 66/75] Add output of the update and deletion counts to fix.py --- tools/scripts/ig-hotfix/role_check.py | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/tools/scripts/ig-hotfix/role_check.py b/tools/scripts/ig-hotfix/role_check.py index 3f11c5d5a8e6..4b84926a92b4 100644 --- a/tools/scripts/ig-hotfix/role_check.py +++ b/tools/scripts/ig-hotfix/role_check.py @@ -125,16 +125,24 @@ def resolve(obj, path): print(f"""\ +from collections import Counter + from django.contrib.contenttypes.models import ContentType from awx.main.models.rbac import Role + +delete_counts = Counter() +update_counts = Counter() + """) print("# Role objects that are assigned to objects that do not exist") for r in orphaned_roles: - print(f"Role.objects.filter(id={r}).update(object_id=None)") - print(f"Role.objects.filter(id={r}).delete()") + print(f"c = Role.objects.filter(id={r}).update(object_id=None)") + print("update_counts.update({'main.Role': c})") + print(f"_, c = Role.objects.filter(id={r}).delete()") + print("delete_counts.update(c)") print("\n") @@ -145,13 +153,17 @@ def resolve(obj, path): for ct, objs in crosslinked.items(): print(f"cls = ContentType.objects.get(id={ct}).model_class()\n") for obj, kv in objs.items(): - print(f"cls.objects.filter(id={obj}).update(**{kv!r})") + print(f"c = cls.objects.filter(id={obj}).update(**{kv!r})") + print("update_counts.update({repr(cls): c})") print(f"queue.append((cls, {obj}))") for child, parents in crosslinked_parents.items(): print(f"\n\nr = Role.objects.get(id={child})") print(f"r.parents.remove(*Role.objects.filter(id__in={parents!r}))") +print('print("Objects deleted:", delete_counts)') +print('print("Objects updated:", update_counts)') + print(f"\n\nfor cls, obj_id in queue:") print(f" obj = cls.objects.get(id=obj_id)") print(f" obj.save()") From d45e258a787d3c42635ac634627c2a40723baa0d Mon Sep 17 00:00:00 2001 From: Jeff Bradberry Date: Wed, 29 May 2024 13:06:12 -0400 Subject: [PATCH 67/75] Wait until the end of the fix script to clean up orphaned roles --- tools/scripts/ig-hotfix/role_check.py | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/tools/scripts/ig-hotfix/role_check.py b/tools/scripts/ig-hotfix/role_check.py index 4b84926a92b4..c4a53e1f22ad 100644 --- a/tools/scripts/ig-hotfix/role_check.py +++ b/tools/scripts/ig-hotfix/role_check.py @@ -13,7 +13,7 @@ crosslinked = defaultdict(lambda: defaultdict(dict)) crosslinked_parents = defaultdict(list) -orphaned_roles = [] +orphaned_roles = set() def resolve(obj, path): @@ -74,7 +74,7 @@ def resolve(obj, path): # Check that the Role's generic foreign key points to a legitimate object if not r.content_object: sys.stderr.write(f"Role id={r.id} is missing a valid content_object: {r.content_type!r} {r.object_id} {r.role_field}\n") - orphaned_roles.append(r.id) + orphaned_roles.add(r.id) continue # Check the resource's role field parents for consistency with Role.parents.all(). @@ -111,7 +111,7 @@ def resolve(obj, path): if rev is None or r.id != rev.id: if rev and (r.content_type_id, r.object_id, r.role_field) == (rev.content_type_id, rev.object_id, rev.role_field): sys.stderr.write(f"Role id={r.id} {r.content_type!r} {r.object_id} {r.role_field} is an orphaned duplicate of Role id={rev.id}, which is actually being used by the assigned resource\n") - orphaned_roles.append(r.id) + orphaned_roles.add(r.id) elif not rev: sys.stderr.write(f"Role id={r.id} {r.content_type!r} {r.object_id} {r.role_field} is pointing to an object currently using no role\n") crosslinked[r.content_type_id][r.object_id][f'{r.role_field}_id'] = r.id @@ -137,15 +137,7 @@ def resolve(obj, path): """) -print("# Role objects that are assigned to objects that do not exist") -for r in orphaned_roles: - print(f"c = Role.objects.filter(id={r}).update(object_id=None)") - print("update_counts.update({'main.Role': c})") - print(f"_, c = Role.objects.filter(id={r}).delete()") - print("delete_counts.update(c)") - -print("\n") print("# Resource objects that are pointing to the wrong Role. Some of these") print("# do not have corresponding Roles anywhere, so delete the foreign key.") print("# For those, new Roles will be constructed upon save.\n") @@ -157,12 +149,19 @@ def resolve(obj, path): print("update_counts.update({repr(cls): c})") print(f"queue.append((cls, {obj}))") +print("\n# Role objects that are assigned to objects that do not exist") +for r in orphaned_roles: + print(f"c = Role.objects.filter(id={r}).update(object_id=None)") + print("update_counts.update({'main.Role': c})") + print(f"_, c = Role.objects.filter(id={r}).delete()") + print("delete_counts.update(c)") + for child, parents in crosslinked_parents.items(): print(f"\n\nr = Role.objects.get(id={child})") print(f"r.parents.remove(*Role.objects.filter(id__in={parents!r}))") -print('print("Objects deleted:", delete_counts)') -print('print("Objects updated:", update_counts)') +print('print("Objects deleted:", dict(delete_counts.most_common()))') +print('print("Objects updated:", dict(update_counts.most_common()))') print(f"\n\nfor cls, obj_id in queue:") print(f" obj = cls.objects.get(id=obj_id)") From dbcd32a1d98281cfc3a3f45717f22eff3f9b9a57 Mon Sep 17 00:00:00 2001 From: Jeff Bradberry Date: Wed, 29 May 2024 13:07:07 -0400 Subject: [PATCH 68/75] Mark and rebuild the implicit_parents field for all affected roles --- tools/scripts/ig-hotfix/role_check.py | 33 ++++++++++++++++++++------- 1 file changed, 25 insertions(+), 8 deletions(-) diff --git a/tools/scripts/ig-hotfix/role_check.py b/tools/scripts/ig-hotfix/role_check.py index c4a53e1f22ad..3cc1b7933b84 100644 --- a/tools/scripts/ig-hotfix/role_check.py +++ b/tools/scripts/ig-hotfix/role_check.py @@ -61,15 +61,23 @@ def resolve(obj, path): sys.stderr.write('===================================\n') for r in Role.objects.exclude(role_field__startswith='system_').order_by('id'): - # The ancestor list should be a superset of both parents and implicit_parents + # The ancestor list should be a superset of both parents and implicit_parents. + # Also, parents should be a superset of implicit_parents. parents = set(r.parents.values_list('id', flat=True)) ancestors = set(r.ancestors.values_list('id', flat=True)) implicit = set(json.loads(r.implicit_parents)) + if not implicit: + sys.stderr.write(f"Role id={r.id} has no implicit_parents\n") if not parents <= ancestors: sys.stderr.write(f"Role id={r.id} has parents that are not in the ancestor list: {parents - ancestors}\n") + crosslinked[r.content_type_id][r.object_id] + if not implicit <= parents: + sys.stderr.write(f"Role id={r.id} has implicit_parents that are not in the parents list: {implicit - parents}\n") + crosslinked[r.content_type_id][r.object_id] if not implicit <= ancestors: sys.stderr.write(f"Role id={r.id} has implicit_parents that are not in the ancestor list: {implicit - ancestors}\n") + crosslinked[r.content_type_id][r.object_id] # Check that the Role's generic foreign key points to a legitimate object if not r.content_object: @@ -129,6 +137,7 @@ def resolve(obj, path): from django.contrib.contenttypes.models import ContentType +from awx.main.fields import ImplicitRoleField from awx.main.models.rbac import Role @@ -141,13 +150,13 @@ def resolve(obj, path): print("# Resource objects that are pointing to the wrong Role. Some of these") print("# do not have corresponding Roles anywhere, so delete the foreign key.") print("# For those, new Roles will be constructed upon save.\n") -print("queue = []\n") +print("queue = set()\n") for ct, objs in crosslinked.items(): print(f"cls = ContentType.objects.get(id={ct}).model_class()\n") for obj, kv in objs.items(): print(f"c = cls.objects.filter(id={obj}).update(**{kv!r})") - print("update_counts.update({repr(cls): c})") - print(f"queue.append((cls, {obj}))") + print("update_counts.update({cls._meta.label: c})") + print(f"queue.add((cls, {obj}))") print("\n# Role objects that are assigned to objects that do not exist") for r in orphaned_roles: @@ -156,13 +165,21 @@ def resolve(obj, path): print(f"_, c = Role.objects.filter(id={r}).delete()") print("delete_counts.update(c)") +print('\n\n') for child, parents in crosslinked_parents.items(): - print(f"\n\nr = Role.objects.get(id={child})") + print(f"r = Role.objects.get(id={child})") print(f"r.parents.remove(*Role.objects.filter(id__in={parents!r}))") + print(f"queue.add((r.content_object.__class__, r.object_id))") +print('\n\n') print('print("Objects deleted:", dict(delete_counts.most_common()))') print('print("Objects updated:", dict(update_counts.most_common()))') -print(f"\n\nfor cls, obj_id in queue:") -print(f" obj = cls.objects.get(id=obj_id)") -print(f" obj.save()") +print("\n\nfor cls, obj_id in queue:") +print(" role_fields = [f for f in cls._meta.fields if isinstance(f, ImplicitRoleField)]") +print(" obj = cls.objects.get(id=obj_id)") +print(" for f in role_fields:") +print(" r = getattr(obj, f.name, None)") +print(" r.implicit_parents = '[]'") +print(" r.save()") +print(" obj.save()") From 2c3a7fafc5f4462214168d673c69196af06e5d6d Mon Sep 17 00:00:00 2001 From: Jeff Bradberry Date: Wed, 29 May 2024 14:42:56 -0400 Subject: [PATCH 69/75] Add a new test scenario to trigger the implicit parent not being in the parents and ancestors lists. --- tools/scripts/ig-hotfix/scenarios/test4.py | 26 ++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 tools/scripts/ig-hotfix/scenarios/test4.py diff --git a/tools/scripts/ig-hotfix/scenarios/test4.py b/tools/scripts/ig-hotfix/scenarios/test4.py new file mode 100644 index 000000000000..9198ac94f393 --- /dev/null +++ b/tools/scripts/ig-hotfix/scenarios/test4.py @@ -0,0 +1,26 @@ +from django.db import connection +from awx.main.models import InstanceGroup + +InstanceGroup.objects.filter(name__in=('green', 'yellow', 'red')).delete() + +green = InstanceGroup.objects.create(name='green') +red = InstanceGroup.objects.create(name='red') +yellow = InstanceGroup.objects.create(name='yellow') + +for ig in InstanceGroup.objects.all(): + print((ig.id, ig.name, ig.use_role_id)) + +with connection.cursor() as cursor: + cursor.execute("UPDATE main_instancegroup SET use_role_id = NULL WHERE name = 'red'") + cursor.execute(f"UPDATE main_instancegroup SET use_role_id = {green.use_role_id} WHERE name = 'yellow'") + +green.refresh_from_db() +red.refresh_from_db() +yellow.refresh_from_db() +green.save() +red.save() +yellow.save() + +print("=====================================") +for ig in InstanceGroup.objects.all(): + print((ig.id, ig.name, ig.use_role_id)) From 345c1c11e986941ac5ec53592b0727302370ed5a Mon Sep 17 00:00:00 2001 From: Jeff Bradberry Date: Tue, 4 Jun 2024 09:34:50 -0400 Subject: [PATCH 70/75] Guard against the role field not being populated when doing the final reset of Role.implicit_parents. --- tools/scripts/ig-hotfix/role_check.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tools/scripts/ig-hotfix/role_check.py b/tools/scripts/ig-hotfix/role_check.py index 3cc1b7933b84..ef2e67b9c0c4 100644 --- a/tools/scripts/ig-hotfix/role_check.py +++ b/tools/scripts/ig-hotfix/role_check.py @@ -180,6 +180,7 @@ def resolve(obj, path): print(" obj = cls.objects.get(id=obj_id)") print(" for f in role_fields:") print(" r = getattr(obj, f.name, None)") -print(" r.implicit_parents = '[]'") -print(" r.save()") +print(" if r is not None:") +print(" r.implicit_parents = '[]'") +print(" r.save()") print(" obj.save()") From aadcc217ebab7064ad9e6d9542d9505c8f32b40e Mon Sep 17 00:00:00 2001 From: Jeff Bradberry Date: Fri, 7 Jun 2024 14:01:08 -0400 Subject: [PATCH 71/75] This should deal correctly with the ancestor list mismatches --- tools/scripts/ig-hotfix/role_check.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tools/scripts/ig-hotfix/role_check.py b/tools/scripts/ig-hotfix/role_check.py index ef2e67b9c0c4..7da16b1e1cc3 100644 --- a/tools/scripts/ig-hotfix/role_check.py +++ b/tools/scripts/ig-hotfix/role_check.py @@ -71,13 +71,13 @@ def resolve(obj, path): sys.stderr.write(f"Role id={r.id} has no implicit_parents\n") if not parents <= ancestors: sys.stderr.write(f"Role id={r.id} has parents that are not in the ancestor list: {parents - ancestors}\n") - crosslinked[r.content_type_id][r.object_id] + crosslinked[r.content_type_id][r.object_id][f'{r.role_field}_id'] = r.id if not implicit <= parents: sys.stderr.write(f"Role id={r.id} has implicit_parents that are not in the parents list: {implicit - parents}\n") - crosslinked[r.content_type_id][r.object_id] + crosslinked[r.content_type_id][r.object_id][f'{r.role_field}_id'] = r.id if not implicit <= ancestors: sys.stderr.write(f"Role id={r.id} has implicit_parents that are not in the ancestor list: {implicit - ancestors}\n") - crosslinked[r.content_type_id][r.object_id] + crosslinked[r.content_type_id][r.object_id][f'{r.role_field}_id'] = r.id # Check that the Role's generic foreign key points to a legitimate object if not r.content_object: @@ -181,6 +181,7 @@ def resolve(obj, path): print(" for f in role_fields:") print(" r = getattr(obj, f.name, None)") print(" if r is not None:") +print(" print(f'updating implicit parents on Role {r.id}')") print(" r.implicit_parents = '[]'") print(" r.save()") print(" obj.save()") From c312d9bce3f7193ea3fa9afeee3324e5ff1ba59d Mon Sep 17 00:00:00 2001 From: Seth Foster Date: Tue, 11 Jun 2024 12:50:18 -0400 Subject: [PATCH 72/75] Rename setting to allow local resource management (#15269) rename AWX_DIRECT_SHARED_RESOURCE_MANAGEMENT_ENABLED to ALLOW_LOCAL_RESOURCE_MANAGEMENT - clearer meaning - drop prefix so the same setting is used across the platform Signed-off-by: Seth Foster --- awx/api/views/__init__.py | 14 +++++++------- .../functional/api/test_immutablesharedfields.py | 2 +- awx/settings/defaults.py | 2 +- awx/sso/conf.py | 2 +- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/awx/api/views/__init__.py b/awx/api/views/__init__.py index 4ed8f8b2f645..92db0f87f20f 100644 --- a/awx/api/views/__init__.py +++ b/awx/api/views/__init__.py @@ -714,7 +714,7 @@ def get(self, request): def immutablesharedfields(cls): ''' - Class decorator to prevent modifying shared resources when AWX_DIRECT_SHARED_RESOURCE_MANAGEMENT_ENABLED setting is set to False. + Class decorator to prevent modifying shared resources when ALLOW_LOCAL_RESOURCE_MANAGEMENT setting is set to False. Works by overriding these view methods: - create @@ -731,7 +731,7 @@ def immutablesharedfields(cls): @functools.wraps(cls.create) def create_wrapper(*args, **kwargs): - if settings.AWX_DIRECT_SHARED_RESOURCE_MANAGEMENT_ENABLED: + if settings.ALLOW_LOCAL_RESOURCE_MANAGEMENT: return cls.original_create(*args, **kwargs) raise PermissionDenied({'detail': _('Creation of this resource is not allowed. Create this resource via the platform ingress.')}) @@ -742,7 +742,7 @@ def create_wrapper(*args, **kwargs): @functools.wraps(cls.delete) def delete_wrapper(*args, **kwargs): - if settings.AWX_DIRECT_SHARED_RESOURCE_MANAGEMENT_ENABLED: + if settings.ALLOW_LOCAL_RESOURCE_MANAGEMENT: return cls.original_delete(*args, **kwargs) raise PermissionDenied({'detail': _('Deletion of this resource is not allowed. Delete this resource via the platform ingress.')}) @@ -753,7 +753,7 @@ def delete_wrapper(*args, **kwargs): @functools.wraps(cls.perform_update) def update_wrapper(*args, **kwargs): - if not settings.AWX_DIRECT_SHARED_RESOURCE_MANAGEMENT_ENABLED: + if not settings.ALLOW_LOCAL_RESOURCE_MANAGEMENT: view, serializer = args instance = view.get_object() if instance: @@ -1340,8 +1340,8 @@ def post(self, request, *args, **kwargs): role = get_object_or_400(models.Role, pk=sub_id) content_types = ContentType.objects.get_for_models(models.Organization, models.Team, models.Credential) # dict of {model: content_type} - # Prevent user to be associated with team/org when AWX_DIRECT_SHARED_RESOURCE_MANAGEMENT_ENABLED is False - if not settings.AWX_DIRECT_SHARED_RESOURCE_MANAGEMENT_ENABLED: + # Prevent user to be associated with team/org when ALLOW_LOCAL_RESOURCE_MANAGEMENT is False + if not settings.ALLOW_LOCAL_RESOURCE_MANAGEMENT: for model in [models.Organization, models.Team]: ct = content_types[model] if role.content_type == ct and role.role_field in ['member_role', 'admin_role']: @@ -4374,7 +4374,7 @@ def post(self, request, *args, **kwargs): role = self.get_parent_object() content_types = ContentType.objects.get_for_models(models.Organization, models.Team, models.Credential) # dict of {model: content_type} - if not settings.AWX_DIRECT_SHARED_RESOURCE_MANAGEMENT_ENABLED: + if not settings.ALLOW_LOCAL_RESOURCE_MANAGEMENT: for model in [models.Organization, models.Team]: ct = content_types[model] if role.content_type == ct and role.role_field in ['member_role', 'admin_role']: diff --git a/awx/main/tests/functional/api/test_immutablesharedfields.py b/awx/main/tests/functional/api/test_immutablesharedfields.py index 9b72abcb1613..b5ae68f2e59c 100644 --- a/awx/main/tests/functional/api/test_immutablesharedfields.py +++ b/awx/main/tests/functional/api/test_immutablesharedfields.py @@ -8,7 +8,7 @@ class TestImmutableSharedFields: @pytest.fixture(autouse=True) def configure_settings(self, settings): - settings.AWX_DIRECT_SHARED_RESOURCE_MANAGEMENT_ENABLED = False + settings.ALLOW_LOCAL_RESOURCE_MANAGEMENT = False def test_create_raises_permission_denied(self, admin_user, post): orgA = Organization.objects.create(name='orgA') diff --git a/awx/settings/defaults.py b/awx/settings/defaults.py index 72d2813d4a46..d0fd7b115cad 100644 --- a/awx/settings/defaults.py +++ b/awx/settings/defaults.py @@ -659,7 +659,7 @@ # If False, do not allow creation of resources that are shared with the platform ingress # e.g. organizations, teams, and users -AWX_DIRECT_SHARED_RESOURCE_MANAGEMENT_ENABLED = True +ALLOW_LOCAL_RESOURCE_MANAGEMENT = True # Enable Pendo on the UI, possible values are 'off', 'anonymous', and 'detailed' # Note: This setting may be overridden by database settings. diff --git a/awx/sso/conf.py b/awx/sso/conf.py index 9190f47c0db4..03640fccd8ae 100644 --- a/awx/sso/conf.py +++ b/awx/sso/conf.py @@ -92,7 +92,7 @@ def __call__(self): ] ) -if settings.AWX_DIRECT_SHARED_RESOURCE_MANAGEMENT_ENABLED: +if settings.ALLOW_LOCAL_RESOURCE_MANAGEMENT: ############################################################################### # AUTHENTICATION BACKENDS DYNAMIC SETTING ############################################################################### From 030704a9e135aaad3d53e4e245eb0d0b9a86c557 Mon Sep 17 00:00:00 2001 From: Jeff Bradberry Date: Tue, 11 Jun 2024 11:13:48 -0400 Subject: [PATCH 73/75] Change all uses of ImplicitRoleField to do on_delete=SET_NULL This will mitigate the problem where if any Role gets deleted for some weird reason it could previously cascade delete important objects. --- awx/main/fields.py | 2 +- .../0021_v330_declare_new_rbac_roles.py | 30 +++++++++---------- .../0042_v330_org_member_role_deparent.py | 4 +-- .../migrations/0086_v360_workflow_approval.py | 8 ++--- ...09_v370_job_template_organization_field.py | 6 ++-- .../0125_more_ee_modeling_changes.py | 2 +- .../0128_organiaztion_read_roles_ee_admin.py | 2 +- .../0177_instance_group_role_addition.py | 6 ++-- 8 files changed, 30 insertions(+), 30 deletions(-) diff --git a/awx/main/fields.py b/awx/main/fields.py index c6d01f279500..49895d70fc3f 100644 --- a/awx/main/fields.py +++ b/awx/main/fields.py @@ -252,7 +252,7 @@ def __init__(self, parent_role=None, *args, **kwargs): kwargs.setdefault('related_name', '+') kwargs.setdefault('null', 'True') kwargs.setdefault('editable', False) - kwargs.setdefault('on_delete', models.CASCADE) + kwargs.setdefault('on_delete', models.SET_NULL) super(ImplicitRoleField, self).__init__(*args, **kwargs) def deconstruct(self): diff --git a/awx/main/migrations/0021_v330_declare_new_rbac_roles.py b/awx/main/migrations/0021_v330_declare_new_rbac_roles.py index 5f8e7788fea3..d5302fae42dc 100644 --- a/awx/main/migrations/0021_v330_declare_new_rbac_roles.py +++ b/awx/main/migrations/0021_v330_declare_new_rbac_roles.py @@ -17,49 +17,49 @@ class Migration(migrations.Migration): model_name='organization', name='execute_role', field=awx.main.fields.ImplicitRoleField( - null='True', on_delete=django.db.models.deletion.CASCADE, parent_role='admin_role', related_name='+', to='main.Role' + null='True', on_delete=django.db.models.deletion.SET_NULL, parent_role='admin_role', related_name='+', to='main.Role' ), ), migrations.AddField( model_name='organization', name='job_template_admin_role', field=awx.main.fields.ImplicitRoleField( - editable=False, null='True', on_delete=django.db.models.deletion.CASCADE, parent_role='admin_role', related_name='+', to='main.Role' + editable=False, null='True', on_delete=django.db.models.deletion.SET_NULL, parent_role='admin_role', related_name='+', to='main.Role' ), ), migrations.AddField( model_name='organization', name='credential_admin_role', field=awx.main.fields.ImplicitRoleField( - null='True', on_delete=django.db.models.deletion.CASCADE, parent_role='admin_role', related_name='+', to='main.Role' + null='True', on_delete=django.db.models.deletion.SET_NULL, parent_role='admin_role', related_name='+', to='main.Role' ), ), migrations.AddField( model_name='organization', name='inventory_admin_role', field=awx.main.fields.ImplicitRoleField( - null='True', on_delete=django.db.models.deletion.CASCADE, parent_role='admin_role', related_name='+', to='main.Role' + null='True', on_delete=django.db.models.deletion.SET_NULL, parent_role='admin_role', related_name='+', to='main.Role' ), ), migrations.AddField( model_name='organization', name='project_admin_role', field=awx.main.fields.ImplicitRoleField( - null='True', on_delete=django.db.models.deletion.CASCADE, parent_role='admin_role', related_name='+', to='main.Role' + null='True', on_delete=django.db.models.deletion.SET_NULL, parent_role='admin_role', related_name='+', to='main.Role' ), ), migrations.AddField( model_name='organization', name='workflow_admin_role', field=awx.main.fields.ImplicitRoleField( - null='True', on_delete=django.db.models.deletion.CASCADE, parent_role='admin_role', related_name='+', to='main.Role' + null='True', on_delete=django.db.models.deletion.SET_NULL, parent_role='admin_role', related_name='+', to='main.Role' ), ), migrations.AddField( model_name='organization', name='notification_admin_role', field=awx.main.fields.ImplicitRoleField( - null='True', on_delete=django.db.models.deletion.CASCADE, parent_role='admin_role', related_name='+', to='main.Role' + null='True', on_delete=django.db.models.deletion.SET_NULL, parent_role='admin_role', related_name='+', to='main.Role' ), ), migrations.AlterField( @@ -67,7 +67,7 @@ class Migration(migrations.Migration): name='admin_role', field=awx.main.fields.ImplicitRoleField( null='True', - on_delete=django.db.models.deletion.CASCADE, + on_delete=django.db.models.deletion.SET_NULL, parent_role=['singleton:system_administrator', 'organization.credential_admin_role'], related_name='+', to='main.Role', @@ -77,7 +77,7 @@ class Migration(migrations.Migration): model_name='inventory', name='admin_role', field=awx.main.fields.ImplicitRoleField( - null='True', on_delete=django.db.models.deletion.CASCADE, parent_role='organization.inventory_admin_role', related_name='+', to='main.Role' + null='True', on_delete=django.db.models.deletion.SET_NULL, parent_role='organization.inventory_admin_role', related_name='+', to='main.Role' ), ), migrations.AlterField( @@ -85,7 +85,7 @@ class Migration(migrations.Migration): name='admin_role', field=awx.main.fields.ImplicitRoleField( null='True', - on_delete=django.db.models.deletion.CASCADE, + on_delete=django.db.models.deletion.SET_NULL, parent_role=['organization.project_admin_role', 'singleton:system_administrator'], related_name='+', to='main.Role', @@ -96,7 +96,7 @@ class Migration(migrations.Migration): name='admin_role', field=awx.main.fields.ImplicitRoleField( null='True', - on_delete=django.db.models.deletion.CASCADE, + on_delete=django.db.models.deletion.SET_NULL, parent_role=['singleton:system_administrator', 'organization.workflow_admin_role'], related_name='+', to='main.Role', @@ -107,7 +107,7 @@ class Migration(migrations.Migration): name='execute_role', field=awx.main.fields.ImplicitRoleField( null='True', - on_delete=django.db.models.deletion.CASCADE, + on_delete=django.db.models.deletion.SET_NULL, parent_role=['admin_role', 'organization.execute_role'], related_name='+', to='main.Role', @@ -119,7 +119,7 @@ class Migration(migrations.Migration): field=awx.main.fields.ImplicitRoleField( editable=False, null='True', - on_delete=django.db.models.deletion.CASCADE, + on_delete=django.db.models.deletion.SET_NULL, parent_role=['project.organization.job_template_admin_role', 'inventory.organization.job_template_admin_role'], related_name='+', to='main.Role', @@ -130,7 +130,7 @@ class Migration(migrations.Migration): name='execute_role', field=awx.main.fields.ImplicitRoleField( null='True', - on_delete=django.db.models.deletion.CASCADE, + on_delete=django.db.models.deletion.SET_NULL, parent_role=['admin_role', 'project.organization.execute_role', 'inventory.organization.execute_role'], related_name='+', to='main.Role', @@ -142,7 +142,7 @@ class Migration(migrations.Migration): field=awx.main.fields.ImplicitRoleField( editable=False, null='True', - on_delete=django.db.models.deletion.CASCADE, + on_delete=django.db.models.deletion.SET_NULL, parent_role=[ 'admin_role', 'execute_role', diff --git a/awx/main/migrations/0042_v330_org_member_role_deparent.py b/awx/main/migrations/0042_v330_org_member_role_deparent.py index f08bdca5e2f2..ce137cbc5f82 100644 --- a/awx/main/migrations/0042_v330_org_member_role_deparent.py +++ b/awx/main/migrations/0042_v330_org_member_role_deparent.py @@ -18,7 +18,7 @@ class Migration(migrations.Migration): model_name='organization', name='member_role', field=awx.main.fields.ImplicitRoleField( - editable=False, null='True', on_delete=django.db.models.deletion.CASCADE, parent_role=['admin_role'], related_name='+', to='main.Role' + editable=False, null='True', on_delete=django.db.models.deletion.SET_NULL, parent_role=['admin_role'], related_name='+', to='main.Role' ), ), migrations.AlterField( @@ -27,7 +27,7 @@ class Migration(migrations.Migration): field=awx.main.fields.ImplicitRoleField( editable=False, null='True', - on_delete=django.db.models.deletion.CASCADE, + on_delete=django.db.models.deletion.SET_NULL, parent_role=[ 'member_role', 'auditor_role', diff --git a/awx/main/migrations/0086_v360_workflow_approval.py b/awx/main/migrations/0086_v360_workflow_approval.py index 999cba8ec160..7612b0799427 100644 --- a/awx/main/migrations/0086_v360_workflow_approval.py +++ b/awx/main/migrations/0086_v360_workflow_approval.py @@ -36,7 +36,7 @@ class Migration(migrations.Migration): model_name='organization', name='approval_role', field=awx.main.fields.ImplicitRoleField( - editable=False, null='True', on_delete=django.db.models.deletion.CASCADE, parent_role='admin_role', related_name='+', to='main.Role' + editable=False, null='True', on_delete=django.db.models.deletion.SET_NULL, parent_role='admin_role', related_name='+', to='main.Role' ), preserve_default='True', ), @@ -46,7 +46,7 @@ class Migration(migrations.Migration): field=awx.main.fields.ImplicitRoleField( editable=False, null='True', - on_delete=django.db.models.deletion.CASCADE, + on_delete=django.db.models.deletion.SET_NULL, parent_role=['organization.approval_role', 'admin_role'], related_name='+', to='main.Role', @@ -116,7 +116,7 @@ class Migration(migrations.Migration): field=awx.main.fields.ImplicitRoleField( editable=False, null='True', - on_delete=django.db.models.deletion.CASCADE, + on_delete=django.db.models.deletion.SET_NULL, parent_role=[ 'member_role', 'auditor_role', @@ -139,7 +139,7 @@ class Migration(migrations.Migration): field=awx.main.fields.ImplicitRoleField( editable=False, null='True', - on_delete=django.db.models.deletion.CASCADE, + on_delete=django.db.models.deletion.SET_NULL, parent_role=['singleton:system_auditor', 'organization.auditor_role', 'execute_role', 'admin_role', 'approval_role'], related_name='+', to='main.Role', diff --git a/awx/main/migrations/0109_v370_job_template_organization_field.py b/awx/main/migrations/0109_v370_job_template_organization_field.py index 38c1867480eb..41564ffba2f7 100644 --- a/awx/main/migrations/0109_v370_job_template_organization_field.py +++ b/awx/main/migrations/0109_v370_job_template_organization_field.py @@ -80,7 +80,7 @@ class Migration(migrations.Migration): field=awx.main.fields.ImplicitRoleField( editable=False, null='True', - on_delete=django.db.models.deletion.CASCADE, + on_delete=django.db.models.deletion.SET_NULL, parent_role=['organization.job_template_admin_role'], related_name='+', to='main.Role', @@ -92,7 +92,7 @@ class Migration(migrations.Migration): field=awx.main.fields.ImplicitRoleField( editable=False, null='True', - on_delete=django.db.models.deletion.CASCADE, + on_delete=django.db.models.deletion.SET_NULL, parent_role=['admin_role', 'organization.execute_role'], related_name='+', to='main.Role', @@ -104,7 +104,7 @@ class Migration(migrations.Migration): field=awx.main.fields.ImplicitRoleField( editable=False, null='True', - on_delete=django.db.models.deletion.CASCADE, + on_delete=django.db.models.deletion.SET_NULL, parent_role=['organization.auditor_role', 'inventory.organization.auditor_role', 'execute_role', 'admin_role'], related_name='+', to='main.Role', diff --git a/awx/main/migrations/0125_more_ee_modeling_changes.py b/awx/main/migrations/0125_more_ee_modeling_changes.py index e1e0a86e14f0..a866dd89f64c 100644 --- a/awx/main/migrations/0125_more_ee_modeling_changes.py +++ b/awx/main/migrations/0125_more_ee_modeling_changes.py @@ -26,7 +26,7 @@ class Migration(migrations.Migration): model_name='organization', name='execution_environment_admin_role', field=awx.main.fields.ImplicitRoleField( - editable=False, null='True', on_delete=django.db.models.deletion.CASCADE, parent_role='admin_role', related_name='+', to='main.Role' + editable=False, null='True', on_delete=django.db.models.deletion.SET_NULL, parent_role='admin_role', related_name='+', to='main.Role' ), preserve_default='True', ), diff --git a/awx/main/migrations/0128_organiaztion_read_roles_ee_admin.py b/awx/main/migrations/0128_organiaztion_read_roles_ee_admin.py index 482f4509a5de..262031d94b97 100644 --- a/awx/main/migrations/0128_organiaztion_read_roles_ee_admin.py +++ b/awx/main/migrations/0128_organiaztion_read_roles_ee_admin.py @@ -17,7 +17,7 @@ class Migration(migrations.Migration): field=awx.main.fields.ImplicitRoleField( editable=False, null='True', - on_delete=django.db.models.deletion.CASCADE, + on_delete=django.db.models.deletion.SET_NULL, parent_role=[ 'member_role', 'auditor_role', diff --git a/awx/main/migrations/0177_instance_group_role_addition.py b/awx/main/migrations/0177_instance_group_role_addition.py index c25a43845b74..27c53e3d93a2 100644 --- a/awx/main/migrations/0177_instance_group_role_addition.py +++ b/awx/main/migrations/0177_instance_group_role_addition.py @@ -17,7 +17,7 @@ class Migration(migrations.Migration): field=awx.main.fields.ImplicitRoleField( editable=False, null='True', - on_delete=django.db.models.deletion.CASCADE, + on_delete=django.db.models.deletion.SET_NULL, parent_role=['singleton:system_administrator'], related_name='+', to='main.role', @@ -30,7 +30,7 @@ class Migration(migrations.Migration): field=awx.main.fields.ImplicitRoleField( editable=False, null='True', - on_delete=django.db.models.deletion.CASCADE, + on_delete=django.db.models.deletion.SET_NULL, parent_role=['singleton:system_auditor', 'use_role', 'admin_role'], related_name='+', to='main.role', @@ -41,7 +41,7 @@ class Migration(migrations.Migration): model_name='instancegroup', name='use_role', field=awx.main.fields.ImplicitRoleField( - editable=False, null='True', on_delete=django.db.models.deletion.CASCADE, parent_role=['admin_role'], related_name='+', to='main.role' + editable=False, null='True', on_delete=django.db.models.deletion.SET_NULL, parent_role=['admin_role'], related_name='+', to='main.role' ), preserve_default='True', ), From bfd811f408be26d49c3e8e5063545cb4f93a8f95 Mon Sep 17 00:00:00 2001 From: Jake Jackson Date: Wed, 12 Jun 2024 15:20:40 -0400 Subject: [PATCH 74/75] Upgrade aiohttp for cve 2024-23829 (#15257) --- requirements/requirements.in | 2 +- requirements/requirements.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements/requirements.in b/requirements/requirements.in index 0911f59c40b0..3dc2bb6a46a4 100644 --- a/requirements/requirements.in +++ b/requirements/requirements.in @@ -1,4 +1,4 @@ -aiohttp>=3.8.6 # CVE-2023-47627 +aiohttp>=3.9.4 # CVE-2024-30251 ansiconv==1.0.0 # UPGRADE BLOCKER: from 2013, consider replacing instead of upgrading asciichartpy asn1 diff --git a/requirements/requirements.txt b/requirements/requirements.txt index 447dd5ec3610..59241c34fd36 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -1,6 +1,6 @@ adal==1.2.7 # via msrestazure -aiohttp==3.9.3 +aiohttp==3.9.5 # via # -r /awx_devel/requirements/requirements.in # aiohttp-retry From a7113549ebf005813f35a368d02d4559ea6487bd Mon Sep 17 00:00:00 2001 From: Viktor Varga <59172780+vvarga007@users.noreply.github.com> Date: Wed, 12 Jun 2024 15:22:21 -0400 Subject: [PATCH 75/75] Add 'Terraform State' inventory source support for collection (#15258) --- awx_collection/plugins/modules/inventory_source.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/awx_collection/plugins/modules/inventory_source.py b/awx_collection/plugins/modules/inventory_source.py index 5f6c1781b60f..216ebce3d8aa 100644 --- a/awx_collection/plugins/modules/inventory_source.py +++ b/awx_collection/plugins/modules/inventory_source.py @@ -42,7 +42,7 @@ source: description: - The source to use for this group. - choices: [ "scm", "ec2", "gce", "azure_rm", "vmware", "satellite6", "openstack", "rhv", "controller", "insights" ] + choices: [ "scm", "ec2", "gce", "azure_rm", "vmware", "satellite6", "openstack", "rhv", "controller", "insights", "terraform" ] type: str source_path: description: @@ -170,7 +170,7 @@ def main(): # # How do we handle manual and file? The controller does not seem to be able to activate them # - source=dict(choices=["scm", "ec2", "gce", "azure_rm", "vmware", "satellite6", "openstack", "rhv", "controller", "insights"]), + source=dict(choices=["scm", "ec2", "gce", "azure_rm", "vmware", "satellite6", "openstack", "rhv", "controller", "insights", "terraform"]), source_path=dict(), source_vars=dict(type='dict'), enabled_var=dict(),