From 526a05e75b22f265928db824377723fe20c023b2 Mon Sep 17 00:00:00 2001 From: Michael Kozono <mkozono@gmail.com> Date: Mon, 26 Jun 2017 13:38:15 -0700 Subject: [PATCH 01/18] Revert "Merge branch 'dm-revert-authorized-files-toggle-master' into 'master'" This reverts commit fce9c01b28e2b3f7a79a6020fba4f408a07ccc3c, reversing changes made to b10034088c9f77ac4f1edc9955ca6694dceb1a1e. To reiterate: this reverts a revert. --- .../admin/application_settings_controller.rb | 3 +- .../application_settings/_form.html.haml | 16 +++ doc/administration/operations.md | 1 + .../img/write_to_authorized_keys_setting.png | Bin 0 -> 94218 bytes doc/administration/operations/speed_up_ssh.md | 12 +++ lib/gitlab/shell.rb | 12 +++ spec/lib/gitlab/shell_spec.rb | 100 ++++++++++++++++-- 7 files changed, 137 insertions(+), 7 deletions(-) create mode 100644 doc/administration/operations/img/write_to_authorized_keys_setting.png diff --git a/app/controllers/admin/application_settings_controller.rb b/app/controllers/admin/application_settings_controller.rb index c5a2c3b8d8bf9620..480d4bbcf0652633 100644 --- a/app/controllers/admin/application_settings_controller.rb +++ b/app/controllers/admin/application_settings_controller.rb @@ -180,7 +180,8 @@ def application_setting_params_ee :check_namespace_plan, :mirror_max_delay, :mirror_max_capacity, - :mirror_capacity_threshold + :mirror_capacity_threshold, + :authorized_keys_enabled ] end end diff --git a/app/views/admin/application_settings/_form.html.haml b/app/views/admin/application_settings/_form.html.haml index 2d6cf92d0cef2b93..befb1c677474e71b 100644 --- a/app/views/admin/application_settings/_form.html.haml +++ b/app/views/admin/application_settings/_form.html.haml @@ -671,6 +671,22 @@ installations. Set to 0 to completely disable polling. = link_to icon('question-circle'), help_page_path('administration/polling') + %fieldset + %legend Performance optimization + .form-group + .col-sm-offset-2.col-sm-10 + .checkbox + = f.label :authorized_keys_enabled do + = f.check_box :authorized_keys_enabled + Write to "authorized_keys" file + .help-block + By default, we write to the "authorized_keys" file to support Git + over SSH without additional configuration. GitLab can be optimized + to authenticate SSH keys via the database file. Only uncheck this + if you have configured your OpenSSH server to use the + AuthorizedKeysCommand. Click on the help icon for more details. + = link_to icon('question-circle'), help_page_path('administration/operations/speed_up_ssh', anchor: 'the-solution') + - if Gitlab::Geo.license_allows? %fieldset %legend GitLab Geo diff --git a/doc/administration/operations.md b/doc/administration/operations.md index b43fdddd7e621a87..472b50739f5c4426 100644 --- a/doc/administration/operations.md +++ b/doc/administration/operations.md @@ -6,3 +6,4 @@ - [Cleaning up Redis sessions](operations/cleaning_up_redis_sessions.md) - [Understanding Unicorn and unicorn-worker-killer](operations/unicorn.md) - [Moving repositories to a new location](operations/moving_repositories.md) +- [Speed up SSH operations](operations/speed_up_ssh.md) diff --git a/doc/administration/operations/img/write_to_authorized_keys_setting.png b/doc/administration/operations/img/write_to_authorized_keys_setting.png new file mode 100644 index 0000000000000000000000000000000000000000..232765f1917960eb3858ed8dd3ab939ebc757e96 GIT binary patch literal 94218 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�pu zYxxh!f*d*Hrg4C6;Gw~@KLT{Q)|&abk-(r0iY$Y2tlqM5p1v%v*Id<=Xf`|YJ8&7Z z8D6|5A)ZaJSb>U`@|dEg$^f2rwfTHhcBE+GcGz#=j-waq8Bzhbf&%^uL*w&m5XnoO z=0lw)DQrD5awAECez#qZwa4q#TZy_8?UZE*tgk!o0;h0tl*clowMy!38)%VE^@;t^ zFi2d4a2<wZvS68Ahq&x1Ht>;Qpz~(zFl#ow!?%54#AP@EaRLdyUC$~qLLo&Bnsr$V z!vd~(g66L}b(&w`h$~<wWD5Q80%M`kpdG_}Lg<0FN4`yF-kH!wt2D5fJ}iM``J{`i zRiRzK+WR!n9ijue!|e}mC2<cqyukw;7P7@%PgjA2jO>DE$U@?q?Qs9|OI1k5=Nvlm zAR)YlR~eyhM!K8$drX=mcE$wRmvI7BMB2=GZQQq2xQ2$KPH@C8SWkjaFjUd`jvgim zlT;=mYmW_igZk|#ANWxBMHMvC`C9jv;)ru|cGn~+v9IlS(by=!=uVyUUQ{_9rDmv3 zk>h;fMJ%HJMin2~kgN>$%}A1X{PBrI)w#Jn^R6~tI{nnRKt?p&sMEbS<%~n7<wRDf z{#`fwK+4ViLCTm9lsXae)C2H}SrY3z%pSkuRPn%@SJl!rx0}Z}?Vn3|aP*Bm<OCnN zXI}v{NdvQC)gvGuQjh;*{p3_AOnmvbtr-yq60}6NyvoN#$CcmHHZ^x=uyrU}x<=fP zD$E#@zIlxn`M9lS#9+Qb3!1}P>zqbrImPSLRl5HfO<^jtD;N0H)m7Uw0S&M(>j+G8 z?BVKbu6-Jmvw$g6t)yibjN};f)CKM#h};X%m$L%8wTz3P%X7sv)a)}aenasp-KV=) zmlLXO__ICbjt;R8!IkN#i_ZGKkwSl;#ka<2o?>_LZeL{&q0@ONqF|2SQ~6BYFlJ@x z8?;zO;s$l&rY`G=$2(85qzGMOf?NWpm#g2AHA9H^8_Qk<IybF5Usns4G1`J4;M#f- zQNnXHc#qsZX1WM%N?!A=Lh)lxROsr`&(qawVwj^CksO~M2u8J}sKzBNy*n<IqE8u9 zzkdOvN-KVGv+6Oaj7C;3#Zto-f3h<2{ic-Rj-O|~WuyeL^FdbD5i3?|BDB8!(V{0% zWl1+|5&x?CMMeV)lvQ?}OM_-fo$qYkb<Xk*I$wkl<#^HCALwOxJ{eMI<r?l9&VqJ% z_<3wpnoHq(kX7J`ywHm#S<e&4b;pJ;(j3w)6&~F~Q}dFolGb4gQV*{0J;Lc%2y2Km zVuDSWK=$Y<j8MPzX%l(Z8URykogQhPJL2o4>zM;D^-iV%eV5o7ln2u#6HkqlTjkc{ zw-7u?GPj0=tu}lSR~Jpv87Qs&(e~{2<avv`l~~G0Vfm{7mI0Pb>#wj!#!7sW1rc*` z8Pl;4EI2V(UBp8+OuV3xCLc{NPJ8iq)s2dUo9)ni_MITFcjeTtffe&LhMJWYHoFiQ z|HLDgGR91~I4i|uXQTwbVp0FM@m`DLMA>nFmXAtg71ZfBh{smu-MzBOKcsN(>$G3H zjI3ijbtxH)`D)MK?-C;!UUERVOuS^>IVLXO{DjcQOT9>WtSCe-!>G67Yi{qs7w@nU z`)>rLMpFcxQ8i#*aWVOzYoh05qS`05i7Y7fGYSlY%Ggvi^FbR;(pl-fB})6}EbF20 z-S-Iv@wm1XHht#-*wHcfybWAKGQqqltmQy~Aik?`|KPkWmVW8lb=?r$-PIp4dGI1X z;9SwjD^V!;cPqr_*}X*BpK2>KZ5Yt18pYa%0n=rHM{ip|uNFH|si&F3AOn4yos3@} z@K99Fxwl!sEpi1slUlW!B&mBAp+cnW>!Yzg{BUq8s27mR3ED?zd!^nc?J91Ma3||b z{PY>$=XKj_mb4KBV8Ir~s-t@~PkfnK2PB~NCcJsZE8+q-nK9rX)!Dj>*P2Umki^!9 z*l{MmCtMhoyUcgIpu=kVk<*9Nj1P$dahRE_f|N817?qw;tm6^GkTO#>7T)KZ^sywV zfl02dEL%JHRw2CB)E2YV?teVHOC_DJ%m+dq_ZRqyKHO87W+E#quuXjt;N#~Ubj3f9 z6J+mG<Gy7?KRu>1ErS_#vWH*rq`%`->JXs&6!SUY;9R8Rl|g;qbKKKZug*M`(`A!q zdj;Wuxr!lS#>gUC_w4mEx=5HuMQo<$4O3;Z{KWNy8yPF}n2%kyRKx@)x~#CTH#}n? zBG!1>*H@b6Z*iFse)ep?MsWQgVqLyY0sSlZGmnPf5m(#mB|A7TV>n5&U`N#4&;Ii< z(H2{e&@>NupC)@CxRe|eT4Ol9a!cdF_`Ha^YJqHUNxQ=!RhsdIK`SNCCyCQG3ONMB zVdVLbF}v+ed<wbfGzwAq>rIk?fk=us;Md8~Xd^_nx43(`vpaP>XwrtfNvEPO*ysBW zXSNxvT~H)2p0^pCw~=K?rPuUnjnM(4S)*-yP^w>smvMRGOs~u}J-j;Q^@pgwun@1@ z4icexYIcREZMu90&>H4M3%+7azZdAm?^@V@gTJ+jIDRGe9XJR|2OsnY?RgB)oBwbg z{CQ@+B4Pg6&}kKCRd|#uz2l+W%@aCGIKwB(dc1mV;Zw_0@ym)L$rHWl`?Oz=LKPn- z;~s}!taBmn5r(p0pXNQX-+Y2qAhLQP3@~4d`sU*#i^K^yGNY8!{BZHzNlvVo-8|L} z_r(q5tuvPPsI`BhL3sLEvR6>5YGc_~PA8UGOIY>gx9#^`eBFmCspnSe#cF~d&U3vJ zHp45)G+Vt9k_-N%Q_8)#w3V9;p)bdOB%Jbhjank<(+@=)D&~W?aySp<$oTHeQ!L$S zZlDo?MOBg#$>XF(e{_7+XSyz&2hHy#p8hq|{IZvjbrsNsTY|UlDyG}sMX9g8D|pH3 z(s86j=2R)-H7y4^hg}w5yghxt)X~@;VXCLmbqdaCr4QWT`g+3+()X%|Oa23J2snON z-N>oCfxhTe_Hq!Dls#%+bO{%Naojz7j@-H5D~_*6)?)!P(=MYjqWO>x4%k<iY50(} zD||8}s+@X*WWn;CcBDRCn>#)W=j$^A-A&Z8p&M+T4oM<{)MOJQNitQ8Rmy&}+YjA7 z_VIbK6PwKy_P(~OlIkzne=6Dq8b^-tC}DENd%dT;l-=U*cpi}~_WuIo{uhN@E5gdn zA8)#W)cV+YSg8j0rHE%uaInz!!^u|$O!MMMny2k!rHF$E$}3DOuTMl)d$q^4o4WW! zBaLC2@);RR!v4h^8zaASlzHV|Tv~iSZY`jrCUM5WG6^DS?0!qzHVws>#u*S|BlP_s zx|P#Toe?}%kg&yu=#B}VGI;qui=Y-e_j<s@>W*EI*!q&dF*5$;#N@YVtuuI6gVw4~ zGQ@1K=qRIfU+0W$JH(F{^;D#+j$B8eZvlBSRF7+)X!*;9#KJTASljvcZhc|#fUBN$ z*}EF<lG>rd;~eb4K08X)W2Y|H*5dM%F4<(%RHi?(U(xb7xGE|Aq@?~DfBOv4DF6vS z3F_rb-w*$$bUg!FB5|;x28-ZaA8ZP+>D6qx{aXcDW1^@N3cpZ+y~wZ>UF3!v?P%Wp zZnVWdSu`%waVL3o!!JgziH&Q|W(?L8*@?V)LT);%J^$IyjzV@VaP3Y~`pap2R#j+{ z3QYKy<NDhk<#IhzE;HsREv(N}>Uz+DbZ@N3oyqo6RMPw*fP1fn0Z8?eZsdmKX}N49 zjgzJx*leh`HtmY|cXLZ2Xa1v<=wdLVb{ik{mGJB9jMt^p=y`uz;BDy;^q9B4ipv=H zN+8>`d(y_L$J=KP8Mz+xfSH)Jy8Pt3{Q*STjOSxHei_%^(`>M!c3+W&Mo)P23B$N5 zizL<t`CXTmocV<!tQE(npP}sLeaHe|h1;7fPVHEzxsMLY1t>1o{4pp0)H4(qQ-kWt z6Hj_Y@+R`O+Z4M!-9aX<0L1`5Yt+Z8pZAngKHh8hDIxonSYn;GEg2Ex!<*Q%$SR#y zf1@2IJcpt;$lrV;b0@XOzdV!6?#8Y51Wn{tsB)8Mn3hXjw_Y-cuIQDuH@^P{TY69F zhltYnVrlzm#UX6m6Dcb|@O1VkV+?Nl$V3TMzlBD4RjN9<^!{5)lHH}kbDH*G23g(q z5bn3;1}5df6%EfJ8-73b_+%Xk1U!8LG-$r_vl3#(cVxU;-9LnXr**P-ebvY)St=~^ zE&*@&;1*e&u$KDGji5;O7|tmUFJ<?fo)$)~;(etw;h*@~BpKZi+{$Tl=&!Z>VCiDI z*rNW%DQcqlwv3W{PQQN^hgp{f0gB_3!(#zy3_(+-P0nQ!rmOkvB=Tes{#2-}4<UzG z56f}E%qz@RbNjDV!<A(^_|cGcM(^coVtoxacrSq-kIvHDnE-?39EW!u2D6qr&)riW zP?%%lfz#akG{I7k&HOU;ef!A_6R|mJ=tLY21XSMMed?Rfu!<Rsc{QHjpXm*r9ym^O zd^h?*cYk2iel|x@%WnBiK2SK~lRxz{7p97vT{FcPMi4197Tk2ab6~j{eKYS?E4#my zM~2*-T6(WBT4zOPqY<K-e!Z0gC1A6iE~u)PcuNAEG{*3JI=vz=(os8zvyo-EIlu1u z3ajF~DD#!l8Vv8c=DVoy?3$EFBco$2vaE?$ljIHtVJB0}`^6~j#hfP5mGcU(9HW59 zm%}|vmarF-7^EON@ixn{AeImsa!rz8)V#9fMYo22DB`R%?oahV=|;IUcSclskPFgy zb;1+NWIiA-!eh`{JRx1XiAXk`Dbm%Le7mTsG$Y-yes{#Djs&Wv|0H5n(z=;N;&711 z$5bRgQXIcofnB-U+955|g)SxVs8hFpXgB%Uw+AjrzV-3gQcK4o75(oo^BA~IvWM@v z>L)h<nJ!x8Yml|H#8pK^DH(0U6D5YiF!KiPz;Bh%Lu+))bs@omk*==($$8(I^-5{R z;dOF0uAhzsDayG82kvA|!(OeOF2$5i0YibXMXsSfLE9@ex-?_Di9GUw9UjSd={*h_ zbI=bxVyVexwssXd&kS(~{RDuRb|8bT{#yB_hI>;Y!aB+A$olpj4wHCF%zEV@OM}xB zAR<?qJ1MS^Z_2~;(0mvjL;f)jmnql)|Ah)l`&YaGck00ea+KW8Oe07btxrrF<Pf;Y z!{qLy(TLQ6)@!Yub|u7hxKR=Pk{Jt*gIIH=o2Dr(ZF(NirpG&(Mx^XQQ;{nSLFMi* zt_+Kfd-?mha`lP=Z&!=NyYj8S_Zo-vyRY^kQ=8su7j4Kd9e0EIX+qvJg^<7V(*hFY z-BQu#W+q#U9y^X1Vj|Tn8Q+k<16~>^eU*Z7JcFo`;s4+W%_=j=Zu*+An>17YnchcL zpWCb)KfVo*27>PN)7oEEC1>p}SEXir0-K-rf8MLaoGSO&?lEH^^9`|l9z=O-RO}r* z3VY$YD0C;glHmJ1^~D9v-#9o+qLHs`JyAL-pxZpcVtC=j{m95EI=w)Jv?t4Mo6B-@ z<rIBl&OS?@Wd=mK%hShLCrwmNI-dI2e;ni4;1__n{C@tz|4U__Ql=Rrv#398io2^w zPhc04jAZ1xVKF_3n;2e+GRTmdM0f-Jt_jYa0aLq8puf3Yb+Mof`PuS>aO)(3gv#_G zI#2S0a74%>Ql}G<8x6{*989E!kfNK!K@1|wi;opIM4!=4J__^OYULCu|I#0|VAi(b zJ4nt-te~k$asQRF=Z13LRoA-*b-(*emr2BgcvkN{7)Mgi<{@aEC$j1QW99KwX4Nms zn@A-(3Kn6B_yY<-M?H%Q{;qxvvXRT}xyzEAq?hup{>qDG*%V$l<Ws`FqV*)?H*x!T z4vd`L9Tsmp#74u^z_1DHP&><_VvIKOd56U00c})LtnfA`8SK6K%O+uM70xheiU-`f zGNjkBG0Gz%zFnm0_KSQhE??-3D0j0<5&gV*FK%isyomBif%xD2cBICwCZ~iBz>Ovq zd8?7yO)>nKRbPCGUz9BB@c=!g!TK?9cl6I@J2lRu|6?vsH<&=-5>1#h9L-Dk?r5NB zF$*2M=Bbn#$D^DE?aAZv)7yJL0Pjmqveo>)_KEh!#qA4!FR8Ny;Q#`Hbk#bUKd*2N zvJJL?(f()f1gz=6yM{01;xYz5?SgiGdLBHS9rhXtI~)3FwoL0;{yPCPPjfn6I(%^W zvdevaX*h%9IcbYe|1-wjpHZa48+`T-ZnNQo0R|}xOjqwY*_ts;Ph=?O7TA^O)|RD| zGEc|7FUFBJVX^oESHUkNgrDsL^&FTOvm^Sp(QE&IM`GY+m`W6t-bE2r`sO0f+`X55 z&ayEE1tXE@phq{18RU+>!*(U(TAKN@EIm<iT>%CX56NCWmhT5liH~4b*A{~|zn=&8 z2lZ(M8!S!^{g9h!%Ipg#A@};gM=G9d?D)G=LxM2e1=)Hwvj!pTQ`w_wKJ%@Sun*eQ zcB?Q+n2qfoccyT(krJ6Aha%E*cjKpEj8Kk}ppOOHxl0p?p6cWktkiV3@DH4p@3}p2 z48-I3x?k6USLm{5MW*97C4;<~zKTN%dD?x!0@OsEN#b2d(5~6Ms&k5bQScQK<37e| zqzo)!u4)5p^&O$^_Xq&uh@PVZt637X?}~WMeRLQ)KEW08<Wt(6Klj{;JbvY&MwMi| z78H<;T{pet&Qv6qAJsp0PD?am<U<z(NsDdV88#G(ka%M(^A!eDea6onH3PK8rbT&$ zI=*H~!(!Hwuc|K657<ag6hB9P@%%*Rl_Z;PF}*C>TeM`6ac*qwEs;<(`52x1{gqI+ zz*4U+jvg#M6SmqUOt{k}pVW_C_q)BSDeQQ^tp8D1sdpzLeLo=L5ZSOEyG{cTPX?Y$ zt27MQPU<SqO~CAJ2H+nu<$uy2Trp%l5t^_<06yT=HFXM%6o&G+s=qV^GcpCN@Qn7; zj#60t6niHu%PP5rS~>5Ik)X-N$*fINYOmO$WNCAgaFw!AtjepGnY+G3G|UU;?X$&e z_i!Nrb2g4C(^VIrk5qzaN3$ES{4i@1oeBOLW2-7Fv+&P)=_1-lf+^|o_zJzpL!+gb zO(5`B>W-n~65{%E2L<8{9@}DX2~w3kf&-)@Xs@WAGfp?!KK!(FDzw3>clbcQ#PZHK z;`08*eRl!$lI_)zEz6O`P=_|jkCKGQ*uy#V?ZLtJYLq6%gUEMK;PI+au_@0Hksf^H z$X7q!Q2+~r6g7UX5>?74N>X0zaeO?~H%y#gG?hA%{$iG7{^v13`P)|B4njhl=tg79 z2z|wN6Ui5TD6#fhPLW^M-Ur)rOv#-u{+3I!GDbhtWq!zEVK{F*)K7o+X>2N94r#xu z3*c164_0ia%r+@VHk^FER|(VZztPZJ|Kxz=*|Yam|1J0QrAS0{#8?J)ByUQY3{7{d zsw#1YT+YQ~+_}Pt)mK0KsbM_raEq=>Q2~S6v<%D0ySyL&N@rJ2X*YsK7`l=U3x!Se zS+~)jaII1`vnX1k%Ec(P`RBBz%Zijl>qKN}-e$dy_`U2CBb`?Az(t_N!t+&77H*!I z1mWj3|5V)l`E0!UJr$IU1zyfo#oK~Bo-gF~c7>6h+K_S{;AjXMh+)a5;NvcqiM_oN zq{STgA^&mz6po%f6J3VAcgsz>mhC44N(*Lvty@l6i0kl0%~ER_Dkxpbn4ugt>$)}% z*#iFy>c=wZwI7EEfioHpqP~RVi9XN~kloWiO>X)u{><~}h4I4;2Z*@plC+@JQ%O?t zJQgwtzf@S)2%_bSIWl831W~0l5JM}%sNeJ@67qnBZh6?x6^>A&lG)(eTI5{uF}jA| zS$^mG8V<qtsRUM;H1@1=e`szas&<itmoR<Yq2<nGaJhOb0?OR@u@EiX^=?gl#VAJ% zqx!uqi#DBM`DJ4;-<aA)!QsOQ)y2m3Fko$07fKkQ@24yd;6Vstf=rQa)AaInACmj0 zfkwA4o_=!r-g^?m>D$#->}^cxn;t&<+=~kBn(LOks!X-aE9(KFF7fFV+<Z{FgrPul zmu2=ON8URiFh2{{X(;r7Z#=86s$P4eQJ@&K&RUHC$V!?h^AwowH<}ziR2*irA<5Ko zNq?kvx6lzmlMs8T^{8pG7r2A{65Gyo(8FZWf`fE|^apugA{9KW-MwOotbSIIC2}pY zk~nJ7a#@%k)NOi;l1X<LJKjW(HzjshmY6E;e37$ROC`3iBKA$-q>KEy_C}__!)yS{ zQP}^{)UeOb>ZL5k+;TKFQ--JC=!&A|Hk2$Y-9tZ3k%0<bzyU)HmOpN0%wWKrl!~{T zIkv^Pq1DJ}^7-=#x(^dO)Spwn^9o6b3-pL8{goGEuf!5}V4hxtFsi(beT?`XHec<Q zT{KK=cJukAuiQXigEpPsM2rm{u~~Buud=lXl3XbuH-i6#_gx)n8<}43uU>FoABTo5 zMhrUa>nIbD*;XkTt^b%}Dd0kMd~CSA{yt6Le89DGCe|gq+3>Tcb-v7t67ja`5%NP% z^1ni&g>i@DD$+3U&&{+e07IE*6&)p9AA+!SWZy66%l7LqnMX2q9}Q*QA`vpEoH0}t zO!*=3NQk2HTXUQ2O>Ua*RT)^=yx6u-T#Rzc5+zYNwmS2?jcK&9y{0l^&^unPpq~RH zHB>NbkV0BejJ%MZi!m^lcwCEnaZ^IeZ&S>ob_k-gIh-6c8_T(j-!Gu-h0!;j=AHia zpgGxEEA7ZkjwUw7o~ptzy^o$>e*@A!r*$})FQY-t#$^og%Vls2MB|&6;qr2Q(<QWE zoV)s>I25E;@$;$grr4y3#1o~R6ebqI1N{@?Q|(`$UNw)@uZmabD_-R11<uGh5mnHM z_8~mqqEs6Cp(Cz_f}hfrsnTjK=w_BE!g_t+8s+WrXL`TKj}|FcxTf?o5foVJAf-pt zt+%iLnc%^kJI#7!3g!)o#@X=qoEJH*UEXmv>}-F$`CMY8snZ=$R03_{qG9#ik-vp5 zWO$!$xrE_uTz^B~Kq#X1I0z!wd&2*8109ks!gWy+tB^j<{Bz``q)3h|N>`b{I8`+I z=Dy~9X#cmM8+7~cJ4er#E(VHr4u*K+`iA^P(YxPd_Sps};swze$At~AppT_57Kigw zzqCJ9SI8g5%Q-0WkuUdY0%M#FHi-u+HHtNKB{XFh2!C3touS~;`v^x*`&i}dxRE7_ zd&bVd!aJQb4ma=O!O1<*2gKpL9yLLhM3X-XFBm$WU5sJJhS(YhqLt-BJVpmfC(PYG zJ<9vFv5^=<$9NCTqr|MvvK(BguW>>Y<dW)}sbxNhHIv}8)|+56Fg5zTn(gtq61Puz z(oWh#AlO2TjgU>gieQH)b7AWh@=%S6o0~V#-%lfDUgIio?Deo6yvnhH&OsWLzcLIz zj3exoC(NZ%9&T;kQi)Ve6=)RQMeEv+xXSE4B>u#MuQMW`)N*U>3n|Mfd#`qau5(G> z?;;TkG||*Y1g@n-b)XARB!(!7n)=qOhFNL*;=}kR;Xz^tERWqzYj%|25Ib$ymt$;5 z`IPCY1+-uxZ(ogbbzcJ|N1e|Yw>l`0ZhrawsYtf0zMFP(*LptgzDaygvYff9%s>nD z)wJdLP@`A-(<Q3?r??$ogcn4B+UOD=CK4EfZuA_HOZceA&WA45dzhzHuAVJ|Pgs8% z<S@G4w?VK@j8j72Y&z%?Z`#l<fu^St7Kv+{EB$Qq`v#fOho^6A88QT94d$!xAcO@B zM%lZ8vz8fDUrb0GZ+5BCbuU$q@pXIVXidW?or-m%GOOVbqk~D-B2?Go2YpX)R&GAa za9Ix#K3|rScz!&ew)#`-&-*x9N`9{DkWZ93Rm(2RS=Oa1mX~@k*12A`Y5piGLbj^` z{9L`0VVK2g5o>diEi0WpeBy9p*lP80*}EM8HD<G0?K!G6&=lvrc@RI%ZN`Evo-miN zfQ>AEf^1iG7R<(@vQF2I?y%Nl8=Dxt5<H)vQT5LIKxk^?n-bJ!DM1yBmL9z}8-Ue~ z$*)0x?s_8&BG2BWZd~Le`E6nP55!sNIiItaz(RKoW(J~rHY;}-YP(t)%-8==>mad1 zJh!?srn#@RT95T+P#EHR@|rfeR}m>uv!#y{n^65w7Ovx6%9?knF`3X;nc)nA885-~ zY>Tz%0F`Rcfn$BsjApto_guj8YE(Sa9_TS?n(0|0W*^GUy-WC_`06lKn{soe*eDnc zOGq7XUh(3a{1ul0r3n_bq~0S)vjb+gBgQRDH@U@18a)+d1L&vvrUG9?h&okgjb;C4 z>B5LyJ(4@Ul|o(bcn2Tq^u0LxXL7puTH5YM_={N;Tf(?(7P3ugw%@2$U*R{F?E-tu z`WXbDh!p03%cNoh17T1aYh`Oc-KMUMQdbeZ^V@l_)zcVamVsfDVq>QO&{eP(=<QSl zSnEy~v3rg;P|b}Gv9|9~q*PCekFEVqGEd8)Ur%MnupAUhaN2T?37YC7-4xABzN_0x z8qk6@L{V$wH-Fa<t$d`^sggb$<1#S|vJO#ca{OXYW_i8*P{~bvJgo~6rk98)qU5R$ z|Dn;!nBnn>L8?2#Rre4;-?^$bQ4LNAgnaCEB=ks+&t6)JEry{>yXscqu(+McfbdxI zyMpZ^1WTagdPa7XHxt*CGCV)$4s`j?%OIr}^8C&@5$9`-DpA5Yw;5H1`^p=LOxc<F zJicN<3A*Idg4h>nuky<siv_m|Q_|OS!gPLW?9sMUDV07dT`UTtwV)Xg+!l?vR$sOK zW_#{9UEIk&nv`3l@m|xglmz7Jv|k9MDW7Sgvi2>GW!8lO6pr>98+x8!iEP<YTA7DE z?T4m(hoeBMnZXdh%sNvOI{o7}UOY;l^KW{N*ECM(Odsq7rLQMql2(K~LnSvu5erfM zaz@MDRjhQ{{kk^fHC^K(-o2*zh=TZ>^O=<s90>-;Ot}vF9O<;;c%_{nAcAeMe3~cx zqz8GX9kD{8l)mTBQnj6Z%6+tWt>-y2;3H?C9L2HyWz+-2R(7scSX@Xix%^?L<Cw9Z z()IvQD3(g8%ObXEoieoRn6*(T$Kd||N7{QvHPwFYzM@EZXd+540!mPNZvn&zC`bU2 zPCx{tN$)~HMCnzU^d?2Bbm=I)gx;&v&?1n87TQ_y`TM?Sk2Cf-<K6qSn6h%Ox$b+; z>-t>*u0sfp8!M`W=0hyIo?V9`ttWBu!IcF?%X-^9aHnj)Rjk>sIo(~knI|o*Xk+#0 z>}OaFxwMb5jP<6v1xxwXFZgXPIU{?cN08B?Z0Cg8>Fv2EZG+)d8_KzvaN~#)v+lOm z_s*xxpuj44h%Y>EYIB`A0wT4=oV&Yhd=M;Ge`|X#A;x6UjlB3i#KM`i&DCFh_B{?E zW2rgU(Agx%jP`a7`vb(312nYN`Kuzv@sCiq4uiwUyG;FkqS>mW)!j#G%5pco7jlo{ zM)+U-Ih|rWOAh*-O{~Y<e;4YO6bl_2S$7|h7@Tg2C}l)cVE<_mCJvR7thlZ0^?o_` z?FG1oXdGf2$r@LZk1%H#5Bj)V;&x||1m+b!yxyz+pds6*v<`9lM!rhBN-MGCLu1Za zX|qBj^9$YkY0nZ@wjH);oRgdzW@^JS-n*mIZ#S@mHNqgW%d1t*Zwpw{PhMa$au<30 zu+z#_EX%%(zQYyuo=USy+h2Tg>Yof{PbRBDPR-TmI5Lv?9`$`*Gw$2{`EU*Ll{r2N z{{65i>MTA{I72bjx^&}S_f1bi{r5D~?gYPGE<(d5N?xSfZx!?`q*9Ux_C}=E9H!v* ziBV%QEr072zYMG1hJivj=>q{eQ%j~C;QTlsP4z`Keo<IJ$=jo2*rWRJg@LV)L6}I@ zAW?~tL<--t6a6m#f=V=x9X&K;-?&UVBSQy}e|^u-pc=LeVRGB}bkM7M&$xJ><V2ai zSD@Q$y*2KuPZ2r>Y^k54CEc*z;7_`?>W*rAFA8%TcdAq+`ZPH7(3@&Jq`@i-J<;}> z-oh!(ExpXkanQ2o<R6B^_mXPzUH*d@x*NA*nf*3)lh2BCb&c=xRZuT*3|(UAsyQ-* za0Fgo5%)8m73CXSUQAv~>ITVK3@VTmCsjh1zDeEq#)(|FQl9+z@2;rkIbp^7l=%90 zA4#_X9QZ=xrkX>jvMp3IJg9!C6m+}5xgcyLrgXb7meT$ECQ{g?z8LJ%dqn{>C#5#I z3uY)@%fP%`K1e@m%$LISi=+UGhs%Mo$uph)YFQPUttV4|Do$)vX;sW6{Uks{nMdF` zMB+t~(|4g@^NJ422E1(r1%2h)^*&hov68efLlH&8R$eT(+rtQ0C%tDDlO-Q*X4|j6 zNU;s%-N{B7gW0OS&sSkH?zxA%-ep*iagi3S02N<d+`KZe^_bp>NA`zwBVm-i1)uWi zr;!q@J74P<E>LdL=k)hM`$Y5i%0^4mWxOC<PwyD$`e-9KFlo!0pQvLr>1rS+2IXxb zDe8rBkG>Z74{qIo^+M8Kxq^yDhO`&6+(IkdKw$bx4Twpv!gj*qxNl`a&sE}#CN2G$ z$;7H+1HXd!qzoR`6PAps9}F$Daghe8XGYuoGQGM%&seCi?a3q7kLoe<i7tSRr(qfh z^Gm^x{tZ(P6ILSk4W0VoLg?Kv8qam?J{Zm5jF3Fa-OP!=swlHX4LsP@Z*1MnGH+iK zQSGKpbvv1ZQ7V{ps;_)C>R&1AjV`7GHqMi@Z^bT~g(PH7zCHPnP963mP6R*Z?aonG zBDeNNZ~jx*;BRaFBg`4cEOq<n---gO>XbpVU9&}xIz2+l*B5mB6kE}iJbdt4VKlnv z)fe!r*l&uST3X`Nji@(rr})W7kM#j{1_gM-2u^_fht@{YZ6ysD!k7;Sxbm{D(j=O- zG3`oq#3>#C4KuOP<*YNoiMcCp8*s1KqLks)X^9G5g*|c2xc*toYgE0azJUu^fSKCl z<{sj4Qs^UCQ$irODeYfT)C}7#2&k!>P}tV(gLow!hcPSTW_)xENnMo}HyKAm5(BnI z>-9yuc+Pd|<|dmrc(BTHiR2MW-ySzB$Q)H3CgLkg>^60ZT^1;ak&Rj%s4T4Tvl#27 zAd|oZZCfU|Tv6og$J_~Rr;-DQ2zjjWsT^2e=HN!d)sz`qg@egI`yU1aq`s>-ddXHw za&*sYWQK@ldF`wk=1L>s<f0)h6_3w~w~DOj*qvLf@BGMjvo8P`vDW?mbe?<x=3BJB z9IC5dAB3+5{Zi-<%iqMwjt%5t5i@@AO5@e4*ztA$c5UCHYGvKjQV)f&5d+&kgVZ@C zc*=-dWPs5$d5z#&;>eVEM6)LVUwQ^z&1q6~`DIn>lgsyd*s=$-HI&PFu>U+lIS#s( z2pAQ5pPv+*9M0RaF+T#GT<4lYx|a+q&yg7DcElq`r^q>4d|*|PIR}FllfLS<BE@SW z2yjaR@!%4jc!S_T*AE$LdA5Q~lw#_^H#%+sBAAm!<#qS-=kz&j(Ph16<CdRee?38D zQQLHB7#IU|_*#>lQOh~ApdY>;=b2CI)VKOcN+uj+oJ)1QlMMbkk8N*s|6`v%RvhBf z6D64xjl9QM!!K8qrS9?dYk=B73)b-Va<UN6URxe0WL~+|YAr}@bJFEqN<ZT${_>IJ zUGDW6R<RXIFZbarn&F$qFzcWp8PUje?i=~fKjvsZ!!9LqMoMOjeXp?H0jO#y#-JxK zBl;qW(+yqa=k6cz0yk8<vih$VK`+RKcV2|4i*G)XwXjav`qJ_^s0Q}+{B%Jpf$^q_ zMO^wv$O9||Dcm(Wh><L=OZ8;?OJ#dyuIT=oCsHa7x);^WS%W!q?7E)bfQ5_krrVYz zy&x1Y2q<g{FH&^mkj-ZKluWtpD}3c$v)sxg%gPO%l#|L4b*;V}lGb!jP(Jx0maD#< zH`wsIx;eR-41!MKve!Ln)B>wGHCSm%t5~ZJRt@YMyG2^bZ2XfiFw*`=<4Xt+*#<tk z*wD-b+^3870qDSuLus9%oY(|fMcguBx)dBdc(8?o{jB5hWwZODEnR>7{yV1Io7U2) z`&CvQ?bKeIWw_(-@Ecf*vt?P`_%N?CmIsjTe4Uokv#0qAX=jfTJEIv_PNT((dw{J6 z8aHF}WR_<>*E>EQ=34pAKTGOr7%?90oSfK<%|&s-d{8;IjKw+Nrcamjda)p{4${YC zA&7@~P3okN4vg;0lbp}{-d00WeWG9)(aUDfH?JQFkb*+zqHrS^*2XE4d?sSZVVAet z>dwj%x7LX<ChQ14S&k%%%NJdHi{1AeDng}Vf+edfI+ro*qS$fE1vyog7&d%t#_5S< zi8spl<l%PapsKa4lM72d?T!o3dq1j+?I<tx7}DY~u`XE?(n;~yw;viFU&qvgs|)|K z0H##B<$HG-M*}FEl%&($q~vUqzw6d9yOZ|{d@QZ!p{SOdjZa4h?mgs-oCr9Pm%eYN zb${%bdsI+W7PnP^?e7zS^c7aCq3TBHk1$*Bs@=oRESugdY>2IDuK0C2xDJu*Nv0?| z;nrarUwGG#mS&FRdWWP3vn9ScF@sR-?8q{3x?6c_#z@cPmB_kPk)LoV#mpjZRf%7~ zH_8Ij;}&&RXETrMe5lq1$41HJv#k-2?4RgB&qgY|<V4A<E3d;>4P&)6Uzc1yh=3oD zp)<+XY7M+yJ2v0axzRte<%_J*Ts$spRGUuepq=Ov5Zk%x*R<u+Jh8wi{~HMu3(l>f z@j!T^=$G_f0cr8avdacMJQ$7k-G%{R0YB@O>GNlVs{{eHaLp^c1RT}&RMc+q{OyLM z!KK{WW!Zzx$`<9hEZx1WWF!>=iASZinewDgFeuDw&xRf0_?Ht)L$xxB2REw+S3R&) z$Ar&Z@O1;^Mwt?eF7VZ9vZd(-pjqQH15S;6*C#Gd(Cq+)vT4Lt-jD!)FuEFHe)I3a zXza#kogWs@Puf9p<>#x`Ps>3qk_ur*ysvh?wNb_n7EZcf8Q)l`V}#(iTDQOjp%SrH z?xy8rAsvplP5?-#FCyD1gviSBhuHfE$Yj69{EMZRHwPt^`~dc~y}H9{Y=h)^`NNRx zXG2dv)+kS!e-EQf3r?`wQDG8|a{8$I3Q0E*K|d?`BSKvA*Sl-U#Nb~9wp>i91#O_9 zlhrG3za;EvO^e0D&unzpYViUIJDKqL566Vb(!7ve0D3k9O%9G#zcV5}PI}wPWvGT{ z|LvkT%5N18Y<<dPbIFNAN?VaM+zwV7IRQRc*M*yGHu_tRBbJXRUFWvV4NpIHYQ1f+ z`g%aI7V^3Ey_g@20Q75SSx>jV*~6=X%49#pl=<_qxj;r_o|;sXBs4%1DFoD?UH&&g z=;~JnwrpF54|p&xx;SQCb!!dA`WZi~v|u_S8LQpi*rORbz&3XjNORXMCR|dgRYC?; zr6pr*eYwG-nx<C2*Si+)%fs5Z2w+fWt(<yY!}sq^3pu0(u!_CwLXHFThTU4SXkP9u z`;{lLW%M}7uOHP*D(Mda$B3EQEw>wnm{(ueLe1>RH^OMnIrG`bkfnu(!%7CZIZ?{- z84auIbZS+y`6cIFerL)UydWz~T?Bc3qBY95!&-9hIpqU7%yE(Mm#Ek0?WyU4B_|%6 z9z(hLQxLLx(Xfsz6Po-YztPu>x@)E{hZBi+gQ{fr>wgaVU0~c7TJ;`xO9MunTy6D} z-5k~O0lR5!<Ho*YM5x<pKA4Rxt-L%$iZn80@9_sAENpGMS&m^X(rJ8<BtiU)f)AoT z^g^o4PTd%0mt%2X0P#L(%ipY@B2&$}_h9Jl>$<Xo5%&QvtH$U0X8Uy`H!yekQcnMD zU+zOBBG61}pRe<*&*Kq$MRb*)6&E?V*{n_vd&2n*ubrpVyH+z;6xql2ZMQwzpOB7r z_+Y?lR_zs%k-hVMVkQ-d`L{5&Nm+#yrLP;e@h`6z^{zELSHmk^w1%J;)pCYy{8o%V zuT@J<S=!`(RJw2~*{9wWyVS?QO;`c~g^n$(^s8$aAYG`R!duBsX$i_=86)TTM5J{H z8&2c&!R)lk{z_7B<BX@#77bt=GwwwTS{1;wk#{H$qXH<U3zoLYy5Zt(OL2Qu`LZPl z4rCc>BtM0R67J%ksf)_G7tUdflFmbbCSiSJ;-3?Cs}{%12`ui*Z}>dxS_I@`of|Eo z>L#<3>;5k@&va~~N+n^t-5Hrx6EhXG%CiMwCLy)@cBZ|9xk3gf)f0g&Z$f~f7aV<W z#4$}d;~soW;gn%e42$2!h*GU3^7GnQd^Hx^W}Lqvoh2o>Bv)%2A-AkFRByBRODaWF zY9k;T6N#<n*+h<q*j8C5Js`F$SGF}ao10ue8C_Lt(xxmwbOWNYYxKDRf5;4fFG%b} zKS$FmMiNMUu(b7yJU-oR@pA#J%N>I~ZsEQ+l7O?4Tv=&`w&k^dFe|0+N-ptH6W+lz zH7+}))aX>M^wWAq{4h;yrsu_f4bwOTOWyar=MB4+3N4_aM;NIOE#K`loZr$Z`SDp2 zmLWC)#u|fk;E|cq3Yr~;+obcAJ8=PA+5=*jnYs~nacz<JnE!FsxNZo#tztQOwSe)a z2urB0FEI<;t-ERfmr~#Ug*KlwRak22lOuLmSwf<IIAZ3swsDOQm4C}6^|NPH4F5^~ zRR3sTj0vi6pw~z4m`kGWX~S&P;c-gKNy-E_IbRhYD$^;GEQpMpfm(QeOM}j7xzJ8m zI1t+eVoURxfEos)UpG-^K~8uxo+tN3s83>yP9c%HxRQgCT<y22a1OL~vRHavKCB)( z9nwhaH4cl%GHqxEx%B>CmMNj4@$zI?(Wit>xv!O-aOYJYvip|s!i{ks+~Yc?K2%EP z2M$rW9eo?gL@`>gNLNAeKtT*p@{800eTe0Ii?8$fB?xJZdK}!_=z1EVwL=2)oU|H8 zh7zACF6=#(+@PVmfaI~WiE$$;$Mi98+n5#vPkboz?e<9*9NRxDb}j`+DUcCA{F!}P zwM>!`*ZJ;0J!L1I{)6Ty@#P0BFtEy;*Q)HMsz2f%NueC|8x`avXP&yJamCWct3ZFb z>(MhiW!aOWa;#I7w^1c9U*7jbEW<LWMWN?hEGhm&K_muI`fFM1!yrzeC8Mf0C?B5$ zIc2g4N>@Bov1Nlvcdpg!u)CoRoRV*EcatXt6#u*tsd)*MHnjQZb8Az9NrwcfF!Y<r z?{uOQr9<fY$)?itnJf9VOTn{lS8q$ZrfQU(-%wt_@1N`pXrnW~@~nOw8EUx2G{R%z zx|WZUB4afhq{!6%uR=8Trw|$F<9u}FbQx_jihdUjHo_Cm+}PvP7na+T0?_ej{{;EK z5(3yJpkFIWH^_GVx7|FZ43r-$SOc0q2O$>4xHvxJV{Axz5RPuZ1ujz`g{Db=>F{pq z{CUsfMwJ{K)wikA{}e#wdnw3xLBm+Afh)mCHMXol1v~ydlA}%6(^zl<Xt!<;mbx<s z{Ck|jcbDyT0U1Ei1-b<|Ul~ToBbt2C4E;e{w5CNlyt!YgCYz5G8YZML`t4s~<4Ylf zz7_Yuv!X8qdQNN`o^hVK<>*9+r}Ge<=`eA(xO6*PK1)$AZAd7w%d{(YvN@V@Smt?_ z`@Mf$^2U3$^F5O(n<|P-A#Brg)=KbbJ#eTJi_%B>52bGx<~u3R*Dt#^g<(}}OZaS^ z;!GM=D+ad3@eP|gQ6-?8EM+-CJoVV*AL+n}8IJrMigjR%q`oZ}HXi`C0sm|u=@o#X z6@^AUlYNtg?)M_4KR}o7bJf9Yu`&FQ^0ZN2$+glnN(N1@8Cr~`BbP<X>~ywij?54k z28<*6?zd^V3G$p&pK&+slEfN1Yv&ZC&vI?e+K?QR_3f3<L0dS{{o@%<P_K=KfhCGK zCSxX5ugly;v3PM%xcL#&K&lU;9CT|ZS%Q$InI8m@0*nUSg|z?rY>C)2RX9m;x90He z-8Z0w_7b(<ZbD~3E{{{&`}x_0L-9|d5cez9)N5>=5^l^rl)lv3bkm-Bp%Mq=`HvRd zSnm7%IZf_7IvEeC1GY%=UN>ED2FC<xxmO5L8%VNj;&TR5l!PV^ub7bQbI(bQkr%dW zF{+5#u;AQpA)WKj&zGqDzMu1Q(SH+3*UGDi5%mm}oU9vgGuTn3`CLBZ_=IKo%iS5) zw@yZZbjO4mRhG}t!2z)}l;*kWaY*s;r+#^gv(N};YW2lL+RKpqhSmly+tlY>P?@VB zWn;iK>#!Si#`GvCoqM9A?`2r5!!vT*&OAogFlA#|+SN>K0`Bo@2^_Lw!GQe&lKm?C zRcvJQ0FYY0vd!%L7Ia4DJ1mJ=;7L0I?zd#XlHF`*-owG+Ok`A9tOHg7tG?c|LxgZG zbRpFm+`V~99&8K$n2BUmKdDpV@k#^YXHX!qy8OXqSNeexm4F%^Co%HPBAi#HQRI5= zQJp~h&ySr;>1cT%;Ymy~_mTITtKFL!m{jC5XxA86ly&g7a)bodcwfT{gLj{l@RCFB zMn2`$<0<3e+gq%1(_tK6@{hIbNpZh&=~y~>JoGVE7V9ERwxU9bC4&a%30TcLHwyXk zMAiu?UHqr3yv$s+S~Q}wy`2=9{98Dx5&1%!mS9PQc_@A{lR>&oihbqQF?CrU#JL`6 za<OJ|Gk3hf)k)2QWOv2o-JETvujJ+rT|$<@7r)V^?l*Z4G^UFvZ<=*OI<o(Cg|<fM z#=d_1j$OecCq$AMo2}KM^c1p+o4s~wd7%fz=icPSIq!#<Kwmg&S%KV0iPUgKZmB_P zzlHy<ZM=d)#AGZb4y^f12uG|XuabR%$W{7AAZt-yq@{N)18`eXK+iq^i?gQ0h5Kmc z@a*@B>GF-ZUCR!${yK%c`;(Au{n&=pEa3R}3r<+l(#aGs7*AwZ#GO*q+o_114yUUp z0F|$0GC|^~D$-4uK)5#)_#s14hK4-_*}|~EGBzE&sC5#*7^Aaaidgwm=WNdi;nY=X zRfu@;RZJBih>AjsEppdxhoY`ufHEOtsdjGtp+X;yA0)cPD)MWgM7`RKhM<h7uw-S5 zhTt4<rVsOOoqLBSDAm?zg^>bB>8x}OM;lg+g*{qVM6`5n!_J$_A{F}R&$2DtdPWUq z!?RXwWQ7wnbxT8SOYFohM2}8BVA(14q=|EQ*Yy9+{S7R?C3VKA&pvar?@&bkX7IM7 z8ySN}Otz5+>oReA<`a<r@e4J#zepBQ_L<+H73b@CZ48t`@3=ngz9e7=M)6{I^sJ7m zLGXxCAgaY|_Rk)S3OguFbvF%d7|!SA64%*dHvw2$NdGN>Rf7pQ(Gy$0nQ)+`gxm-k zlX<j0naq2oZ^lOs{Qy=?5uGx+Rk`}rtzdZ4;^+?5T1;~3*Q(dX_o7I|n(qPJnabkf z`)OjnL6jYjyMz|*!>fJ2x*^~Y3ntqu{dAj5QUzuXFO&NK7rM@jjhS6S0A}j7OVhFJ z1lTV*R{E<1({nihV^ekaei;u}W53-37x$T#+~#urqY*JUmpQR(0cESv)w<05S2rye zd~aa6x%TczQ>@Ya_Paclz}t^Gw^n;okwCB1T}EKJa`WDTwo%|Ksr#)Gs(*B^dH0_M z|JA*20=idw?o=tza@L)d542%k)lVs{4<2WC7xj^Xmd8o~uSG)&p2vZ*<$@{$2=?T8 z{h==NyU;0)S2~TA?4M}N1i`ky=)KU9($4Si_P+Vh9P(;GGf0a-Qt{mYkcNAJkiW#p zcjbu1{<9Fod6ilc5qJHCo8Hhow!P<Itmmb#@|k1mD(eiOA$4he7qvo|wN4&8y1H0@ z<K`|pKXq(hV)-QrH-l4`RU3B>&{y9HPNZl^qFs*P%TsGx;#0?Q?tZtKjSpu}Y8fkM zM|XQVzq%56wHH#w=fgVCj_Voe&fk{)K)J;dNw(VYh`J#x<{EY83V`5A+tW{c79x65 zSdsNXe*gBQ0rRrkfx|p1#GkO<>D~BVc@(@j?E@y9R4{gZ<gwmpzjGO!-e{A@fD#q3 z^In-WY4;Y@LRX~bZ_+9jrMM}IzDls^U`63;SKVhWtgB2}ubI2g`mLF|AB(-keBCjN zl*+TSM3c4Yt(E|NE<Y2Kj-E^PORV}VjeDzT-x0TfNsn&Cvpp&J(m%T2f8KrI<S6)V zkYr0Ty}YxB#&Q4%wR8WJK3^NYnCeWOMYbC}>ZT2@06*uH^D@t`^4czTjY+ik4fd!W z95shn^nBIm8lLf_S?l-KW_B?NZ#-RoeA&I()h=b#quZ;|^kr-^r1FVZ2Ax_o0U+co znT+mzdSwrcrbr!o$Z{O>uA_|xDrp))!@wk@7stuRj%nz<v5?sL4xDb0Cea5&_q^qn zEM(4Pefw&J9<5Qn5lao3Nq<XDTK`bZ2e;eCdiaeGtKMsF)HQtfQNnEd=15WC26v1H zN}6aQ*&`vTvCpS)zcMn(>*IHr3+>u;ct*mg2h~LV4gKZw?rUkE1{0O!M`EL@)Pkd~ zkvgYcd~O4jQS)dxSl3e5&U@i#-*Gj|N1lh(Y=9zD71-@%ro){R!BuH4tc_6`JhtS( zX788v@jdZu4LNkmzQ3Z`RVQ?w_!`wh;<|+0a&Jp`BLBMLO_<f1{`$2v^@nY|z>se6 z{tDH_FREm~UTZ%G{pk$^m;8Y{ta`9IxE6QsKKWuXw!Z&oZYSoO{9@}96RK!ty{@E^ zU#*_skksPwNpe{NtO4Wz<F)wRP810Uo=rb}kSwe$n&;?S!*@PR(@PYP@4<sL)tPA? z_>*d+C?U!vDawx_M<z0T3gROUW{*nJy7^hxzYh;JI+VB+7I;8W)mDYCo0@y{XOHwD ztJWoUPvpxYXU9P~rAD_W=W42$>ZvfP_?QM%CwrQzja>C9ICA)KZ{rYPo+;kkDU?j> z>Xh|-h;o73N#61(5fLs~?XI}))bchH<h*PIc#7rUMj*oE$Q%OgBrxSQWcsfJu2Jnp zJd`yCA|S;98j<pha(Bdt($C<GGHHReH2a~@@Ad4r8C$RLzE~Kv#dqT>>TH<In@2r! zy-^a4UHb8L9M0@lp>o(CzjA$S<~dMr%&;aMLDKQAOU+()HY>n9o6^0pb%^?f7<mQj zl4=V+s7b~G;~UdLvWYz{-;aZ;xa<Ms65Y;Cn}$;hTe}naK7>A29~E0UC7Y>Yp$_m6 ze0v%T#kQAci*@;Inu|W0&)eelQm^MlX1{)0!5c|!%`<1dwsK7*I8Rq@v?p2G)JTup zS&v%p49}$JdY2IITBad006rIrtB5;5uG>h(?y@mwz^m!eq<wW$aK>z^Wp1X=^X<!A zSFl!H;aWNM2eA)&-jJXn2<VB7gZv$!)ThMG+~VYy+~jKi1W%KE<737QvYO{$Gts?9 z0QB{hWRg_=_VpEksbSxj4<98c=$j?$1jt|Rm)6k49~HJMoxS-Mf`pWMy?V$P?&dSW zX!!$AB8&al)-AxZFs2>xt-WpN13`NIzSK<RfHcpiTFJ~Wh1XIic#!Ik;#{EuByAGb zLKCoUgGn1nf|wqnSV;<f>H#vf)Q7HnKZt8-6gI+GEMt`OAfVn87Hh#)$F~NATuvU} z+gN5|BORF|aE1OHGIt-(H1m8G30WhoC7GsIV1~V5H+F9itum`JT^)Dp&=~k?@B@Pl z+aZqw?W}fjJozm@G`HrM>1u_qyKz0BkY!i$@JD+@y2td=q;EgnZdqc7w)|vIB&8=R zEHIob)f`FqsUeO2Ad0(&)gXp$QAl&=tLDXR{L=p|!25lll=9+Le$KMN7EO$Y?&a&H z*jUK8=5OHeYwHqbIG2qYeaQ++{gffi`uM^ORv93QdOM^*EQU`TZa8)ak`<xkYjH^t zYI|RPli#DP06-57dm*owAB&nxbu>t(g?i7>zR4n(W+r<SYJHz-<mQ;wrxgp|yyhM5 zj~-IzJgICwL4aa4WK_~C4_}#eO%VB?U%tzVSSoq`a2=lj7^_<+FvNxJ*)~3^FIfsy zuil=@4CEH>Q7YL`-IL63Z3f_Q8F9BI7`i;=u!Wv+t1;;tko0LQNr$v}=vtJg@hKQ) z&+~2lRxCikEpp?=Pks#<7*6_gQXaFybUTEOI-X3@fwoLk<hceE)SD?cfvxMW;qp~k z5WO!zDoKv+M8?{CG|W~*cksy}Dfp62Q<o^8RG9A=Lm)Zwdru77#CF_D%yr@%djhiv zR(hO16g0fMK_#7TVX4IuBuZs2K)JPdg;I|9Sc#iK3mG5uLp4t5F)-#l&8*$-y$ZtJ zfmTvyv(T66&ne1#A!Qcplu9{}Qcap^w{f^W4VHTw1u$fX0R}Ok)u^Sc9MLE1mTEbW zjURWeIjgp~2?;EWb{&h-o6X~u)<kuo*J1<B_@`e|zoj3HKjL78(_dI-?c)<f52!j1 zNW!b%zRd^Z|ABjg`&B#o`+)R6+WNt`*^|Xlcm6d6DZ&!JNC|-q<b{&gOCNxKRXktt z(Kn}}Tr_eGY=dD!mww#gjpTm|zz7@1)rW24%epx+KlEyd&hK)qef!*VzdbypzeSkk zKl;`dK;PO7UdQ{I-z&X8)V9M2y}75^ynfpE{PfZi{?1(r-@0a+`$c59$FqwagKovy z(fYs%^q<TD)(Jm5$H!3*XE}-82&An*(BEM(rGc=<N#BeUPb=nJ&XM-}HQ0~L!UF4Z zs%O^tK4Z?A(DBE{z;3OMyS0c$X?v55aGZN}$%yiqzMKOo5DSM-vk84uRy7Y`LPod7 z@Zr%Zz@XSK=}pzWN59FyOe291$4Ti}fwuu$KH$+Sd_gSkdfV^PShO}>rrL*C@L|fe z^&xo(pJG(@KE3?6uvc_TA7E)YgTf9kn7N6i%RPQBPB$0TFExs@m6((^Oz6&La%qV+ zFIYCO!lrD#5ih;S#uR^|>B~mF!5o@&C~s5hK#=}l`kX7jY`a$LPc{J0)jxGvH)G&1 z9$`bq^vduxG)&DH^>gap5b==5CXJ0Mf+;<x#Aed#V7eID?!&|&*#4UU1_4}NiTo!c z9kr|FtQc6lmX{CPh(LAey?GG~1w5rq*s;};_c=rNar^5l%{w*e($Iu}F*8PQ*9eo9 zx>wAD>uJbXDp%pCSH>Z72vd<_kM?AGCjfX@TSM34l7cC|RYeK3C{?;d;*07Uce+#3 za*!xumDs~t&q(8rJX&l%v+(Y6F!_RR!_0Vcna>&z%3mexT72hjYKEVH0KPl0P$OjV zw>$8b`l0C<%T^O9f5<y@kchDy;`!*Ovh>W4=rook(TiwrS~)%DkhI2JQSMZFR!w77 zV0=;qq(1ppq!h#v4{B&kC&hWF6*?RDW>gKG01=I8v#+kmhX?S!y;<>T&>ZUVCSKTd zm<h@9t=o0u(F<Q?<7)B2f#j_(TD|#?bvKiIjmhP>QG|W5LHt3@UbEjGoB0wcJ#&3k zPSN%u{o#%M@$xUL50$_jDY)(XrZMV9)hHW_E&in^iJ07HGg05+S!CO#RgjH4P3b<9 z@h0wjsL-O_TO#m*>=`^p@Gz!xb9t@#B0}?-dddhI%Sue_IQ~%vF5lVY>s}SdM&!!1 zBk5~-aYv5qTDKL<Xz)ZZ1o?sFQGynjN6KT*h%VvQ=yTR~P{Husu7C_o<g7`a!D)tE zH@&|CEMR14`~n8o_IiRTowxK-0Yk-2jW)>uY#rRNtBpCT993NgxAO&`sL51ve;6fX zUy%%v+*8`;X0Y^PgKn8WJqa(v-N$%sGk;nCSbVdYI+2G+4rgQ1tyI-~T-m70tO}0- z<F~B^xxi?|;5-oPHp2gWp|U%u@*AU_Dwcy-^65Y6<Cz&_<4C8$SDZMyz~27d2M|Zs zcM1HkDp#OKp*?)fNqo1EEKDJsv#O5B(}LBDzVUH7#Jqi|Mwij{Xv4wh=YdPcsS#gq zGP3)oT>MkN$)QP;V3`-n!#<evMm^*idh#FK-XCu%it$J|qI%IX)kz6csM}O{_%;l= zVX|S$*owikD<Q^^tBr%3Na;M=*;r+^K+Q<>d9kO)p`OdNjpwX<e=D12AyC(dG7_Pe z!4-8Q2|$Xus*FcTgjuNamy(W%qqmCcW;E&i#oOM$r?2#Os<|%SWD7U7eMc$y`_t|l ztZA_+e+JUK&|%$UjQxlpo%)3U<EO6dF6vy7+LgwdPaA_$izXbymnP~h+o7;eW{|G( z5o_hR+f~4q<QOD@P3;?>!b74$a?m-nNPGYp#si3$eZ0u=jxX}Tl=Oy{z5vG$Fn+hG zGSqnX$T&1;y~IT)ULm(8Nx2KP9jl<FJG-?R+pasqc^=I(RY?$i7D$M-5g$YW;bXSh zRMu_m-)++-Zw>8pZc$Ra?bW+a3XR=By@~4;)^>AYzI1vOL2;7X_-$!jOI~#!Ewa&X z64mQ;95G5|GOwXL;q^w&Y!4O!h0_XCB>lj>v#%fZoTDL@4HfS(9d@tr!j+*niZQJP zWxYwBd1m^$DQ-<+jd2snJ7pc4TcZZ<aX<$#?d~$dwoH%8P_HE2#xPHADo?VBj$!1s z<DL(I=`?oDEX)lE1S*!-4(m{EW2UQ3gH7tE$QzrtMtK85Dp@h}(OPl3K#VXB5W1Z5 zoG5mKes{SveG(tEn^f)ckMugtoVd3bFx-goL4Pp$cDb>>Xq0flACWR++|E~Bw@!V8 zpP(=}VYJt5zJkShM-Qo|z9D*wmyD#;<%7X1$#zgYP$T*La8|&$tnU5o6g9nA6xqqI zz~*D$79;%Nq;95d>@ODo^FIV}K;1j;Fig$9nUwir`9`}nkCQWJ?66(UG*|YEvaQz1 z>T3nb3QO0+SX28y(fLd5Z<<nh>ow491LxN_wvMGJpDzRS1fS>DKYEgj;8Ot`z6Xd{ zQsS&r4KBX0v-S$c3RCc^$Zu&z*>DHNhLGG-mX15d^D%$4miiyID*OVVgx<D|9p47- z-=y+vy~Z{CgehNq0$O8Ecb`(h#%i?ZsF)^$l-fCm$jFmKIb-KnUjE!W??q(jx;2Eo z1fvtNCdNDs>!E!9NREJ4cv5bnHXx#=jr8;UCwj@Kr}>`IpcAKxe!R+!^zzk>?u`3) z`OiOG+?3hyRC*V+(Y1FI#655`FY6kFF7~Nx?%m#Y{b<r1ATN_XDzCZ1?DI!1S!ylN z0t83+af^T4;4*J_@9AUnCzMzRsP(VMVV|Rsl4ch~gZQC=VU+-=af!a~{_UNO(tQ>m z1rst$L?%U&-_$)2Y{&!xHGSsa<<hEoYbU;I%rQ&2()E?yYJUE;cyo?@$@ovWEy|-O ze)z4?yrvOjN?f2p!zU^*ONPR$;ZXJTc1x4^pzQuqTQ%0!BFQ%M{+_VG@3I>TR5rjy zc3Zo$^U7lr-Nul9`cpi>!LHt_k}-0;f0S%5xT8FI=d~$0r#t<O%0YVVK=gIKv7kZO zn<|mcz{((20@CM!0m+9(8})FDL@-f_;*qu$m2A-jU4ti8J;1o6s%fM$nUF(>5e<ZD zHt%k4Q~SlBS<@&WG7Wo`G}0?~KxGwKL9vu7{xrcu@QUAEmNUZ$$*=TvNhdIh%I+Jh z6dC&c=k(JdJ8RfUX4i7hKa9hp5xuMO*`rw=uKVl7XhBa>Twrf-mR-iS(JA7qML1I( z1*e={h_U18>|%4Uf{7nH+cNoheD>Uon@+KdZ@oqUr+^u`a2Ah7l>Ry|q^qg40wdFD z+}Zt!pU!Dmo)y5md2G3E<ZXP&El>6N<qZWRzwCqa0+hS8mM5UE*VBmBPW7Rqp^cr% zOwnu^){4p?!AR$kCzNow`m9%uYjI{>UdA2uq(ou1$3feS#7I<SYGl7<otrNiyuaZ@ zlnwA+C-k4y<xyqJ^xN7#wB$vs#Mtezp=F_WdNEDxE<c*R&Z5Ds7CO^;=U>Gmhjeoy zbH#%^EUV)mpTMN454QiQ5+NIq<Xmr^i_<z{C{p#WSGWd^_F~S7kXj;P&|z!~a7U*R zHw+!v`h@$exs(kh+9?`<T*YxL=dsP9*Mz0Xp@YBrETO|DW6&>~mZ+l#eX3_!7LM{% zEsu@iXxHFp>Dpc6UUyx@cVwqoiPCJsx?Nc8v|LG;FVK0Ig6(LYdcL;;Qwdkk$aY)J z?lmQg7b8-{TIA-c2a-$O)!9Y|2OlZ3$~<AJCp?ddh}+<2@Ln$uC*6}|x}OAjDbr&{ zVzC?^FHtaNRV<6ds;xqw+6#)mT;bdAC>GBUIxf{o^rSot_rRL%s36n5?G3x$6g@2E zP+u<Mi!m+>tEmJgL|vbWm1I@V{_*?BS8IF5I+BjHG?#aoPu`i7%Qt(Xn=w(5!w;MM zqz2&KW_WDO20qsaTJ2QuDL4K203aa&6i~QwEWkABCSO;q1f&Gh`Y2Wfhm2CvmiAQM zSttipY4`MTg?7RZ>ZnFqsm1X!MEuSS{Jf&b;W5j1_lTn6mOIF{=Hkr>NLL7Y9TgCx zj*=b(%cBV$juWL|tOA^r&KMxX)^4)8qFD|1OKrI<sOcdJTrE{s>h*9^D6#lx7{u}L zi1+-?NSC%mF~xBC(;#@H0CV|Q4&pbt8_y*X(#8Dv9pQY9$lBi>A#LcD*hRtjh_^Gw z5f8)rd4IOsuki<H@KME?$Gw2Q<oKo+tYw%^I5tA%MVa>Or)GMR)vDG<9Quyk&Ocr6 zLKR*<!*OSjO6$EW4pMp#twHMFwH7h_)pB6fcKwsQ$3zti(Ou5~_rmL*!58mMKY!oz z(mgYft42aLUwYiD`gZ)zMKX+PF6y?RUSI$v+<vDy+x3;kFOf{b-XC{FUc;)2o4r#v zHX6oQ7>}Rzi4sV;4J*=BA3UTS;84HMR7YSEPwSnRl`p^_xwv<;ii1;#o43ASE;$cQ zby?X;u=|by&fZzc?lrvrMUm+JHA?g98|RgW&Dd0M*Kc{uSdtT?a&&7r&8Hw~zqXWa z7RNh7%6Tkds`Px@m0pgl&-1h3@L_bic<v?5ugNSN6PB}K={}Bi6So?~|7(+rpf?D_ zS%!D|&p3?5Xt8j_$#1%>3L%ag)6tGrvs6o*=99WDxbX=o896?p$jRoz>XbD1nXjQM z(Ty&t(f(B!(W!Nx37T!AL*dPa4RXnGKcKqUA6&=LtO%yusPD`+0(A;OTe9IBZL48M zNWuZHnfr;A48Bp*N4UMuiG$X<Gq=3Ni|oM;8TVu<nf5rRC|KBTF&#S@8$$Y5vI4x? z-Xd12Ss}lXb{KYxvE_Fnk_a1~uWK?`uPW;OC4H<DvtJP5s<2PzR0ng4Z6d4S-9d}c z*n@<T$*1oZgm7@68^RJ`(ixo}HsNGT|4_!yY)0x^P<9l%L=u}R&ATWmf0zDpPx3k2 z?|Z3d!^%UT=vvHF!;>iADERf$&gxRP63v5=4S}Hudui7<cjm$3Y`unMt>52=6Uxt+ zGv==-&Q9l{TFm;dseT_U+KxjT#ajY*)&^PlG|>GML!6m$E3rF-EZ%cj(sd<x<FzL< zslW5}Ui~_N3gQnec@;dLqeQGH!quLvd4HjCklSq7))g)qIV=$4*U$b-Q*cFO9MU;@ zFy;i@OmN8&Wn>z%jY_f(t=y7r_simq;#7F6he)8;slV_39uQ<|vHe8$7RWPN{-<c8 zjHqANtR!B0<}}q{{<p8C8g_+d8qk!!0!Rg8NfFM@1_sdyp9Q(neFm=OJf8gBd$08U zQxQC~{mlW%OTuLrj8C&ma%O%ZG}d0zX`H^J)IzSWMxVkX?B~$QK;%O^0*7SgN(f;F zy+B&Mh+2B2?SF>@`CH!z9-VUR{5~9EKbB5nG@`t)AL&t*@r!F$_HZiaTr}5h*E-tW z2>5K`*RBHBP~sY8X~4g84uE1US>H)OqJhJUcIx&c92(~yr_{UQJ&)U0E6IfZVC4xo zznzhMn~hSxa8$XOL1a1%d{R0-KfY(|Bn9Ov^0lvA|IOlg>2^cULKzBu`U)hG;k_#2 zL7jl;5#2z$VK_3Shl{04SfnHb{Jy{bTT*nc6U;kOApVdW2Q6I;BqNij&`{Zfu|rIx zW`21~Da{Ga2LDC%+zC5B6Z3nssAc^7xq<Rj++ik<N2@W(IjBD@+m%-HbF;PWwZwn& z%FK1qzwaT=e)TUm-(3xHO!!fK#pqg(%9-b`_u_0SkfF&}q7L8EN&yn1?e1-e$Nc;M zzR?npOZ!M$b|ZbB^yGzSWq2AIj(l&y-p%YzD&Hu`i9}nc5(JlRzi*`{^YrQ5-D{(A zkdT@#sOLwlKl-m=BLAyb@3A!8D1n>LHw1=f0G#1(Vs`{P!H%}AJ#M0ZF0KE8@)emC zd`QG$Sf~MRSeb1;y|HZ2r{#bH$SEqRMZsMDt|>qR9hnNi=*5ybqrer@fVyt79TT;t zd`HgAR(WpCfk(><f32p%Nmdot@m%EdXZ>pcwi2NSo3sw0mb^T}%nsh%|C)^t<gG)> z?n;SO%L5T0hH3PWZ3+j^w)J4n$Zrtz7`L?lf2LproPq*ZQ>Xj@$gP_A?+LUF5ZRjy zU}=rjHb=LsMScH-W=uUJ2^XT8d*=C_|34QGOk%D)&}XzRj3PoC&MnjSLB8CdcEsN! zOKLB#{=GcB#8-Krn>hZsj<Pn!%ig@hPn|VhKluC4^#0n9e&Hh#T=qNYYBPdO&ARci z8Y-(PI)AUof8MT&<YkKtGSwv+&&D>b?EMpD0c7sl*gMCjhyu~3%BSAeArhv4m&CvC z@RCRA9YX(&<oB^${_)bTU4Rs~AlQ5x11Z^ZUoB=|LH@6Q{y&c7(i6l#K?Pd}7XGio zyaca`0T)gm(mD87*ZTJi^gm3(`t}n41H62f*7RRL?*Ara=>O*pY=Fa9<-g1M|Lg_+ zbNzqd4|PAT$aw#+Kj;7WHOBxek|hO$yu^QM)&KYf{{6nK@2LSx(|ZE<!~e@4z5t&v z#wh&n&GA3{k^Wzw-Tyi#@H*G8?d<$t{_q+Qpd^Z!pUwSWu0t{<;C0A|rl$Y$(*F;? zTV^1@n6m9YuKUkA^e?&MUj|3nBD#QT^!gN4>+svNOGX^;6-X?dpo6(;s*c8&y5_D| zBsT4g3`@ji;*;qSK-$fvSYrKHZgP>)+5<=|pIRe+27u_GGnpHy+_Edz%zVFuk2+Y# z)3lRk=hnp$4j1bVrR)}!d$?p3zT+;u$xIw*A_ye^L1Qnz51SR8E&|u1<ICoHbF~ZS zD!OynfCCu<5A9wTPON)|VQSfD(t?m&rUS@*KB`@SQ?xBEo(eylM${qGXLo=Kg-G}Q zEhRP&9TLUVGzl5O32~j+@v@~?Hhbs0ek~^e<#k~TyAezIca?QMY<1Zb(wGyD(*Ego zdnd(^<7^uw-PQ3r7&Y{Rgx)_-KlcQZSYj7*$TkZKJc!e7wa)39=VxUNMEjKMz<D#u z4xx5`9+F{rX8H1x$=_G5VI=(A$m)B%RN}%O?~CVKodI9?;`#XHe`gtO0yW<XA3vCz z?Z7Q0nJoJ^Ca8u|9xsYcK>=UM3Y{B#^ijYkOA3hSF*MMr<D(_z3OxGb!UHPw!o<)M zeMwtZzq9?4Sgib6o4hD+G<R10i4RIzzbD}J)CFM6m!CJ;G(urcS1-r%<;IPGx{nx; z*`4z_-!^e@{AUixwZS)k%9<Q_YBT-1zu^}H0#HE4u0uWQwJ^cT;8zOw?3&*!0(%=# z2Vnop7qxC>5WoFjX-0hMkjgRam%1J``;oJgjLVasbUQC5G=AfslRFiC{BxzGY^PIQ z$#}Lu9^5@GXUTcpY!d%W;aZqDhRoV0)GRUz8FokyisY5Of=>p#78jLWB2~9SvSQQd znl=<w>q6ehexq+7rws)1kf<awfIy&R0Gbqpa4U65(-D{j(7VY%<if<C7?5$(h+;Xw zvdq{i;yOn1vSPRSv<A0z1H?cp0^}p8*GTW;ub(*62n-OI*&WOS!A4>g3(LLhe6(gE zEu&BwikY}4UW><_5;KjNx94Z@Yr&rIMGNen;dSS&A|MsQJm8x1t6-xoC9}rbq7T~- zrX)@clu(L(XpmpU>o%6R_Xqg)$eBpWyb!iJm0HalH!-zEFqF;saS`s38{5n!I<dv4 zp)L{dht$?<%RtL)=ARvvQBMV*T*Iv-S*A~KMNQe0;?E_9^D%BA-&QV?drcO{-`HXM z^yuy!em-PF%pQiA)G9U`UQbOhoh#XQ>~x`Sk!aJCBjShG6bSV?vI0x7m<azIjBpo@ z|GO@glz-vT_4qKLr_I|ak`BUTSaqtoj!cX)=-YI(6uZxL`-1KN3J2OgeNWf;($$il zt0C@MLYUx%Lqd(-t%i59&@LdGD{=A`wUQ{bp~I8_seIL^jIycd!>%%rGP9m50Jjb> z&ElB@QpFr70Taf@J<hd4g`*?i-J|3n@G3iO^*HU$jj+diOB|~)x2mIO4>kng!rE)~ z(r>6r1VLWQ!OS3*O8O&RzXM$@sQ^ko)fG5!7yX-x#CpEZcIi|J0PUaoy3hm~!arLi zG|$ceC#cgBiZbou-9|Iqg9$HLs|hD>+PXSc(NY73y)C?QZ5{B8qVBRVL^hg?t2w2` z^E8@YV>EjJ)+KwR(Yj=NH1AQSXbqJ=$FAcC1^EMEqdWf{MC4<|ByROVYh42=JcGuE z#;4Gk^n0#yvej{!T<Y-?;;<cS+3sRrO-ETNw_qkO^`h#2a14ziDpoI3kzmWPGI!o= zBlO&x@Ag%2mvHw`DQPc@(|okb;%j<ws#smZox4-2;;JuwEUt;AP&WJo9F^zm_=aia zN~DatBBtS<@=IAH)tl#jfDB~vj=Bwj_h|qC3th(#rH3aN&LwAOXss`^)Z3HWCcr@G zRgVrqzQvHG4cKU##!POJ-0Y;$vo#;K4R}w9f#-e=D}GaQP*rXFXP*x|QC?D9eMYY5 z8ai5|XH^fFi0ioqIQ$%Kmhkjy+I>sELrVn0-o279)G&~AhxRdI?_tFC>CYk%cf-4; zmKXRKAvr+T2-Rhgd6Q5>&y?7$zP~ih=it@19w^ahx~<p8!P_YZEd%2r!*}W9lRkG5 z8jV`!z~jn?b&rqn6N}UP8(CEvgZZ*3Yt$wQWRyH2p9m)7OsR-U+!r4alwiqSn+p3a z05LD@bxRe@0Jv>X_{=mDKpFl+8|Lu>P8JPP$G-ylZnN&+&kqnftn@ULw$M7g-GNV- zvOnHA#p4XuTdz^2b{A7WHK;4@v^tG1!s;k}>!mN0Lw#<J{~`9h{(;Hs_>=&$*?%}H z^iF`9S$zVyjK#j!Y^dUc@WRLj14Q)E!G#V~?6;Mlx*jmQh7atb9k5=_My>Z>Qo9!a zn$|d%a}^!YYq|b$jRFEJj9gza?or<l3%#VrKF-p_EVV(0)u~-dXTH;Y#uKS-6Z)~) zbQXO{xAuxs5WzfdGfz%mcX}o)KDZQN<B9T+viX{Jn-O3@o{HirYrLzx7L2J&$IBFv zC6Kn*bnKTyb9B}|i$mQFy|R<GDx6u(CXvl>lxmINIA2%QWx=yW1Ee0rwL-h~sRf*s z_<a{G9JRcpnApV7iS`nD&U&Zp6Q9k3a4N80_W%DvcQ!~IvlnVx&QXW1*WBI8EQ3SX zEhf0~rM<p(=&L7N@~K8#zh^d3e4-@XVXaB#YtZ?iR`I3yq4r5#5ykOc&Q43Yz8p|K zzfAL<Aeov^jmXtTAHcjJhR4<$D61`}0=ABlN+e!h6g-js09|Y;YwJoZD7N8lD#zX~ zK|}c7gpNIm>m%>p>)~@nOxb=y?}1nNn&ht`N<d9?ewErVMJ%7ui9?)h>7??dfd?%q z-LS{x^UAj^oi?ZAMJE#wr|u^<MzI8qMG>2gE>kxNSZa|mDTeEf2eJx@KFc&2Q+xWu zB$5mAfD_mb$n{~WlIEg3P9?kq)W`OSkWbrd+_zjykhj6<XR9D}=(4#F9LN$JmUi5` zZ_p_(CqLME_c-PuGj#U@h%<9g_(Jhll^V0$4_Zs%UMV1RkqcybQvkXIl7An0`;1lE zkMV(D_o%5EwMN=k+u|v`2f7WeIBNJl-m@~Rdk{uzAYvDm1y>t&XqO#024V(}AJZya z%Lw#6=`&gAO;Q}G(s!DFa=K2B%0<g#%F&)`K|gic616zyfM>onr7U*nxDKtmHd{dM zUac&<r5U?_f|ocf0Hs)B*K5$5L#2qu$$;I(FlZd|Qg)r{?Xuk+*Fo=v$~59vA`1EI zF55oSkL}VV^|GRh>?CkPxR;yT5@Fvi3kLLKsWi(FZ?zfp@wEk1qRdJ>QG~*IdJel5 z<MiYN<oxAN#yq|Lx9ceH(XWh)&5UHc0@E(dhXd)OCS#7=^tmUB5T>VDFKOc#<&k%h z9sz<b#I>FJB6oqLmC<yc8}VDx)-)@6a&<1$8JOn*r4dZS!fmCd^o0zGL2DIG<+SrA zf!$-;f>#Ge9vK_~qbycD<V9FjJVhGu+U`_s-bzcyenAM{yIhmJA#Lwa5*BVIc4)YO zPFTmg-tWq!iU5I=(aZu)bMgRM9R%{YiNwc4;)f)PacVBwpoBiyZ)ZdZD1GWp1FFVw zEb6)!a{+(z@$w-|?)D~d=vBl3Ch`Kfu8dPiqV#LDVT)sy+4bE({?5QiCC2UA%x{ef zvwk8nPaMq2tmJ3j03tW5LDC$lu{UN?3*Z(Bky6aBieYQ*3#1SP*>aC%3qJt{c<}{o zc<7)6zGj8JRcP0*w*4(~`n$ma14Mo{A8V7RuF=PujVevv&npUSn^4-#MpAV{Tz@(a zwJQaxB?|DaJ)HAsTwy_(H1-M0;P&6n%)H1;T!jqPCzbtoHJB(m7k?i#S!7?N5oxDc zG^CO@Oj68--#N$^!#@2zDQHr^44T1DtR}2JYI+0Xn9}u2V6I+fYAI}*2y;)`*}DY% z3&Ur?Crbwl@Lgz9HfY@s`<{BGt;ZDK2k<U@nc^Ahhs^Nv^?uObuy9bi4#)wXq<reA z#G9^A{JK!^H1NyMAahB1-K2oXmszdo;Py?>pYRtNLpv^Xl$PET)uu~%Z_BGkFe%Vk zP6+MSCpFfZ)b~PPFnUFJEk-|kMx$ijCzxwRbRazZsxQI3ShKOFLRkqT{Nh+IyCXA% zms(R_YxuBv@M!husG$sLg!H@UgUb)5?H-)W9Z6lflpc7Wm!5l+hIPn}RE+Bwis2DU zGN8*(qYAa{NBB@Lmz4;Knu}NvGXvFRW9;Ke-8$-4{K3ZvNDo5&<k&Z~Omj1kM{B}O z>1cNfLgDVSKY$f^WaY8)17%=aGLFAZNw;&hMZIR?>2^4Nd=T<d@!T$Luy0tQpiy2S z?>hY%I$tdKtUyq+1|dEhp7utG@^ZW7q6ncp_w%TfXO*!w?xPb?=l3qQpKD~>cunUI zD0t;8_^A8l2`m<g&km&UqyH{aM_6>0>H+idTCiAWxa7a=vlNuxD#%F&awxn}MeGgO zC9d6Hq-L~*&KH?_j+?oOHG|#q=$NW3QARfk6{0`2)q}bPoRvaXAESD9k8(_VXGBA! zn?LMimBOiezo_oVA{!vpv}4|vLDyEPgZJ39a1m!(?$Z=8Z{P1tpOZ?p`dvg@_CD5a zG79|2%rW}mDPzH6fp`5D#oT&G9X=7rj-i!m`GyL`s>lJ@H$vn~P<?!l!?G$fNe0d> zIEx?OS~B4J5XAPqWSkh%OXkEO_bihpqgh;dS@LO`3Tt^KF`6-Utvw};5qK`MRiS@4 zY^@r|aRADR*Rg8i`qm!(!E|02goAHoymq=~`Y)L;r4;h$+n3Sas{%75p67?PEhRM| z2m?nSmTp4NETVjFNo|o3ioa=64kio*lq;JAECO&YMh&uYw){bRAXZ2}7%dkB0SDFu zjqcqmZqV7P?f6X>e?q*{T#;7in6WMH%UK!swYmNta{J2x&k`a>4OD7swOz0n?Y1Qm z*IO{l-O~NM3dH2lxL<G?Hmkoz-(szGUu2l}6WeeQmjIws4ytbOQ#4wUR+SHMoZgR? zyLLd-5T;3NU$>TE*W`kiP=;-3Iy~167s1$1s@5?-FZ;C)oU&|3AA1#!({<n2zn9^{ zl6Fd{xftXVI~i^%xzMog@cM&^7hUZfaql5o*1BKDvm)SzuXzCPi1<)hmLJ4;WTA6E z_d{QhO7;K4)Vqf>-T(39_kDL)w}_-dVMz@+lf!bV6y;oshLOWaqPAsgv8kLAIW){+ zsF1pyH{=vKu5Av5oDX4#<h(i0;rHJC{d}+Q?|-h#AKs7G^Z9r_4wVpot1%q$y8a@u z{V<sOcX0QYsuy^Vau_^SP@jkb!bZ#Esmgso?Nhct#<2;>zG&n*(pHOnmgVbF3QSq` zl{Si9_UVIwJRB(WjKTjU2H>DM=%BtC_gF9tOvH>i?B4&p(iZ@gQS@!e)N-4B{tw@9 zblP^#Al~SgHoQU3a5;Ki(0DcUBp86(jH>vjrb&=1FQ4@N-z<RNgl_Ae$mD>(SsHE0 z<z>Ln`e*cM7jB)X?1^n$@=D)Sj2aCx_1{j~cD+|k^WX$~C!8088naWqhF}k?O_>R1 zL+1~q#nv3bz`Vk=)e3s*t`~!(3T7+%-4t5ixPstd1n7Rk=r!uQ3G^*Qruj}(uUfgV z)X=S}J0JAl6~{d4p`P&lu%BKHT*=d)DArmuF8aIkorj&?xcP{b1ABYr)En1bVt{$d z5?Sm@!yLd-gZ4IWf#;)^%fNmG`O;Ad`(aacu4{)8v!g**vc6M){kuRjGqIE}lG;?z z02-exup=g_JFL+PWnb3(@ZghW+AqwV_p&HKeQyrdk{gXR-#S2;IWuHF<jAH_GE1#o zJx!E<8{&I3o`XoK5??{=FUnmOKP3Y+wiivz#%#|oxTn}~6Skz!XA`H)07C};>(1J! zP8Szw%s_hYh2%8c)M;4}P!gPwQl4P%>iBcNj3!x*Rb`b(2sxp>E5X{6#nka2^GtXs z7`ci63zf2Bc3H%*SD{y!iTQf74)i3`!q_CHFlp(u2@vrFBfYRv*V&rSXl6`IjpuH& zX6t86`d7Y-GN0_xj~UILCQJxwALr`iky5BaQ~qF=vuJr)OLDdMZ{49PKkFhjjqS7W z#5$KuPC9*^*l=y_TlEL^Pi5dQhFsrnEad~{#r~j{1TAq7HMq-_$MAX`u#~>V$WiLu zwQHCFb)P;H(9(~qcO3sZi|n+tUpt)e^zYLrpWV9rREX3pYFKzMvr*haGw9%JsoQe6 z<?ltveJN>MJ{MY1_4e<l=zp7Gt&c7UlBC(W1lxOti!d5f<w0D5S0efa&7*IY@`Cm~ zDAnjGT>Mq*>3aN}w%y|<iF83(GpuCDSD?tMwYo#x3pP*v+xso{c=OYz*Bb2~G4Nkj z1=nE{fml+a5=MXw_)h+DMStIESma)YpuhUy%VLi^ONsFzr}4#);3uDFhexNKu|v5n z25EDn?mv<~CQFMij`jk-9Arh(;?&Jt(+ckZ64aJmL5nV6lv_(K85$QEg8V)q{ad>f z?weH{(zGL36Z>$BzqtygW{Hcy@^fEOk;e<kW<fyxglsQU-YZ)Atk@baE_6C{4pZ!3 z8g_$I6@6#+Z7yRe#oFF$2jFz7VSfaM7OqQjg&!Pi7Zj^@)=d}($r3gPdyhbPO-5l@ zFf{R1x8{Iv)6q~()x+8o=Q{1j1~;BPO5R8vHQ#)k!aD*HZM_YKdOTt#TEtBzZoe6E zSo;w->cf?}((a6HG9H1R{0auM@9JFDxBT?>L*m?zfhgJT=Z80Z0qfD@@}K9`cd9~x zNwsIO6+x5_8B#rVfh_IhmMRmv6yt7muQqiNELOuj$<N@an#S{8^AE43s#<gctt7tX z<YU|;fmMA5!2N&AA_G5rdAJ=X1-?AK{9ganF`-GUEA-1H<(H2S>Q;}vT()<z2z|r0 z8#rUPX<e+5pfTz0a{nt`WBH66Hz6vvPEOH9ckoM?Yd;#%D!O9<X(=I#g=eOxS&U7s zotyKZVY=(xZF@XdWA2=@8H`(h<-c$*$fdLY+DyNz-YGejZvO_flgpcp%pnSDPB|7i z!AYXO7P>zv%y!4m?w&)EhX38&Fx~YnxVaAWCWaot6G!5wOp3ExsApp#HNP)zmXu@4 z*FrSyUA$$kXpXiQz${EQhYIVToEh3bB)-|V^d0)rCu9DX<PG0$6AO0(4u_JpQn>1+ zcO;})q{yo*8@v32GJ$cuuKs4MHNkOy%8RI~u{;H_J)C#Q``tO_nV69ZF=!j2xFQ-N zDj3}gaxXS~W@zqXDc$N$v{`=eSkE?*u=nf`l$+$$@5Qm51kD-lEGr|qS@lw&OLy)Z z4se^cn-{K3QQ1Wve}A3n-5Bkl;c4+*TdLdA)c{=2I9U+uUrX0o&E^8BF}=!p(b|`? zG*|OTE!2wxd9Qp6O&D8OL{aH_u}0CA+2>y@-Bb3e*wl-X_3P;3;+(K@PU@S#vu82= zxE9}`O(9dK<dH{tQNGnC9|mdIWa_>GUfuQF2$P?{5Iuq}e6ITL@)7!KyUlm}BizrM zWM#hSglT_*@O;h=^p>k{;_g)o_t7SU=bD~IVWT6TK(7N0B0eHA-;W8IMvv1Jvq!do zV$)zF=Z83;pl@Uc-(NfS!%d;9=|;-ZNYtg2Usi1-#xa-s-{Y=%eDiH%U2}4Q#hJaA z=7`UqLp{NMGX8yjXc~di55JPpY*C-;>ic&F0yqBFOLQ}I8KD4b3Z1mfY;ko|+bwG* zbpVsu_`^Oo)Oe!Fxovar(sS)W_SxU^J?0Avw!tCZ|B`B>zL)cMs_)zCWJ^xpE?hEO zAIF^8#@c$4PT|&*?NrYWZ~dZP-^Z|#Y3tbi89Kh6xE7C_^~$g?;U)^RE3aBC!%*8# zaUu)6s8yq2LFM5Ep;TYoK2slAi$?Eb(CL@%Up@K_vsha+()-Njn1}D@G@|%Jy8SB! z|C%}wJl3xmGODHlr&_@7x>EHyrtZpN&@pRIq||!iFn4jkAtRtzdD5-+#sjJmP&MH+ z?(G&ZQ0{eVvgSy@(TRkGp1zBaI5Uo?&oocm{ng*>D;KVGViS8oLRT;RbvWV6VTm_; zG8+CWQqzWES;=#8D%MJ61Jrj12u)S-&+z?!?;-dLhF9wQf9<aS`^Y*~>zB%S>w+LA z7TZn8>fLGIbNg6p4LG++w*Q8BxNkUiGFlOB`*_LZ#1YQ{=|_k6xPyPRwW@8+dUnhQ zD9OIkf$WwVdN-ZYIMww!AEIG@Hpa{M9>TowMP1z9`?FflvM`7Kt!82SoxXil@RRbb zVLrX)dn}g`{i9n09G3RY^G`S_?a`Y|=@eng1`Oh7XLL+qXR2FrfmbHChy~X5`jhU> zZvDxdkqV`n)%SUZ;HWB)Zw+{60%RBr&#La6Ir~7|QSPhV>tg4si3hHzO=Q*$MJ;T! z=jNeiZ$r9sp0)P%_`fy{(jxzyU`52q=%c&y?)O<s2G4SToSooZL1)Wv6_0Az=jM%h zP2x!x2cxeQXyRBAFyXr=u7CTYDc#kiG%e)|{^)=@@v<RTr)l;;z6uCs;w{~1{PLTs z-pWR_@fPrc$)-l7sy4{4l?t^+rh~A5)%iqm9@20GZP<j~r~O~`B+B1j4!d9Wt1V7< zN<>&%Ne14d2=dV2xVsyRukv(jSsUlD**)3Y^EF-n2me4jQ&a!|eUyh=;#cEp;G|iF zPr2E!*rtR^znOk)<#bDwSilE|P3!q#Hd;iVLPx02zz`P#c}-snw_``%qa&P5W}LlM z=UNu9<ujB%mFw<nKP1zzy(QnpIya=J>HXJ!5A@fub?1t&RDtpfQ=8x1k9>Ei3$;y} z>g=>FpED|uHh5GqM_E3Il({#-=D=d+672>Q;>s<QDy%)}PyRk5y7ji<Ty$f$l>d}E zZ=R=^|F%$ab7Uz_lhSS3VY0%pa^t>EKDNmFzO4JnNQ1v@#8_fu{C-jA8YaVPM=Wmt z)+=lu){fhnzp<=5XVdN8GFP}_Cyn1Q9I$gSEwZdu?m65rt=JlcS}Lr2ck&P@m~~$7 zQ&C>V`lzn^A5pEdpeqBeygKV;IM0>(Uth6OAtue$Y4wM=yyUw0O4q+X&lhZQ9z9z6 zr*$7!<M#+zRpc6G^+YSm`*eFD`5Az0Q4P5Muar^(+-TEYiNUG9YEu3tz-O$q%X-+6 z5a$|2M8s<H4Dw|-aEKTm)=!T0f!q;I$A<s%3aXCxRr<&S&i`_t2__^t?>OpYypjuW zJ*yTkN-l1RuVa9IvW?tn7nlCn9h7-fve5VA0X^vY1nYS=jJE@^J9c}1{UV?#!`dG3 zkAdKSy=b$HN8;#K>{8wU^349}KT$`MgxDUxs8K@daN`#KV=4}cUo5bL9Mtz1*qem@ zt5|Bpd|Pq_u!4`l&)81t`t3e0n_0Rl*`HqMZMcDdZ31P7gGzs8pnjz{H;<}r!$;QA zT<4u`g@T#oNfYhyV$JJM14csBEl~pf@8;{epD@uJ5l{>xsMzn~DMP%yq|Luys|NxG zfbsAeO?x=8d~f{wfC8VQu(npMuGW)bR=RSx|Db{ne>h~HNcpRc`oHOrb>XRo?_jGa zPtZ>?H+fZh!RSl7gg!1Tr|!<nQ0r4UN{walBI*M|f@q#^N=iqV1LV*b7a<YPAoE@8 zQ~fQ#4b`0jT#zdEXvf9EOJ7d^zAGUHE`(Zpbw^$Km0IYT(DC<`tg6e=B5<;%4CWJX z#3;AmA==N(n?b{`eXbuBYl^RGN`M)0!(|X&*OR?C_TSo<nqiPbt?@?Ebk7L~puz$D zJyB?H9MNMx!@h5`wt-*k16aZWbH3!B_Vd*@>3-!ArHKCEk+3J;J~mC)u9o|pX4K^Q zrTMhB;UjkDpERe(;s15*g=|fM9)vbMlbw}n5w}BsGfR<v;Yp8fLSifo9-dYr?5{J+ zT1QHo?Qll_G)R9QF*9)MMTq&^q$%W`SR;u~n(<G1bDX84$8k<aoYPJg&~%WNGFoXK z)xTbkCrdnuk>0H@Z>>#jnOMsKXacsNNp<SSfQm)1ru-}A9?~|;So_&5#Y@w=VKG9Z z-0;AXsKZ&NJRkOwl%z~YC?)hAYf9)BB#Rb}vo2lamvT#v1^OB1YjFIEgPRZ1S5zIz zhKE_{w68Btpy}($+7~ll{$(WLU8*v35R9Vjm-0f6tjn?zH}^CEtW`<5gk)WF(k!;E zWglEo#C`Eb!(kI@>b9Vi`oq}Sqs}!Wlso35=4o$@5=^gy4w)Wn@U)n8#wuebXJ2Ak zKFyrJmN}Fh-xBY2>-zD=|N2521M}b}?2xb8mqt=se5w0H41utl-WaAaHMCKGs9tbI z4XEvLmg8wK9%M0npT(df_nzEpW3Gljp;YSJm-e2|NV0ep0UNTZ|4BV08ng6OE>cfp z;dCV+eXXp0EPulK2+zcO58tAA;QKK>k9%D4g=+SeSv`GJn>@-jseOeZu(ws`JlOnx ziPh~F3jp9QHWZ!!D-XkhhIdqsgeoz<o(woJ7rY!zU#(Yp0RHIC`SG3z`gIbd>Nx^r zw*^QNl=-YEW%vn5feeRiTYu++RThvHSZU$P%|e=G!jReJ*)7ix1l^piw}67j2cR#4 z;|^U>C(~B!@MA%C^_@e-UCt$!aidP|-fZ|L5^sKddJl@EF8n1I@uJB?d204`7uCV6 zW53DbGqyzb`elFPg`})0IeelcGbQt;5~|mD7JSs_>afYR@i!`>xpT^PSWClw@R1bE z1B*&a_~j$^es&ZO8HQ+fi{zl3C(<|u*W@{SF~fX}8%$E>{rkTB_9nsf&DoiV?!b-O zk)UJ~A+P)09m+CE3dK?b@6LhzXoi1tv$T|jU&iiUr^_!)gCdoCB;cpVQ1HTip=~a) zk{-lC^`H}uY$3$u&S{IcRTpI=Pod`4AJ2M$r!_o7`!@HRZd#XSSK^(Mkv)J%`)-0= zl7rIlhy$n9wh5|SP!I>pWWPe5Pc%c@pSEF2d!3`3&l@J8<OgxZD*WFs5`;(Ch_)N3 z%v(FC^UuwkMqe{9PS7`TISmfSe_NpMU+!%3b~DPTSRi4#+Zyf;Pwtr>sa%qCXp?6c zPzSqrJQUg5(+l;RKT3+{nNJ8M`r8`(--PlX)uP#tNcs$tTS^$!h^-Z{tDNuG%_lEd zZEMe@hT&!A)VJ|%HSOP1EIqZ<Jw?k&C}q=58dh%Fi{ltqn<wkJHdj|`h)(cr`$5uc zMf3<d_$*N6bqwAprBj?^B!8bidYXrgF~*;?ow6>rlXhxrlbjk^`7EXmTd}We#zET{ zPa@GzH_)3N%rmIYYEVjYA#$5YJ!zDEE1FQ%Su2-))A5B_hjq6igVfY;jf3!+)3B(j z@OP?q|Gi`V)laz@wsmzsDA)ee^~ixtovytO3#IDE{2eMDQF$`v(k5v7s><(PdEVEU z>kD?;{YU%?VTx%n&avfRg1fcE{ia_JVk>WjYx&Fe9;FVsT0<qfP_k*|cTa07|9KRm zc;-Fse^ceX%O~yX9w}?F|2*`))1Ao1uzvotS13tGI{fV$+9h{Y?q#mm9qK}%tJDG& zM&Tpnl}1uz=aBFA1Y@-m9(lt--BJYRz^hTwDzWCDQ6<%btp{~zxj%tg^*-Cz5$>#F zgpuz=%-*QfJ8DoJ>=l_Zs^|HaYOF~>1r6(+Pl*hL5jL^i4WzB+gFbymYP{!HAYf&R z>z9m+E7GAizs+sFn`5}|AZw*W2wWxp77GuFPHA3nURJmdrd;QGkk8qDfk(XU9?$r7 zZtk+b($OEY;iJVfxm;KE8s>5`vi``@f~A8IqZWL!Km;9?PErRS@u!C;38Oc?zZ+zb zR*mGNGmWO5xtG7cQuwXOx}@91%|$@L*m2*@rjB{CHPfggNMkk4!25fIQ1N~8DfhH` zqp}NIgEol~ocs2!@8?A$@(UkkPnlQsW|S!%$1|^tx~5>W6*WHYSv=AD>wali-Q{(x zQ{$|hzG}LCG3A*=;Jd)iIqQ^yJ63~KylP5mt@g_+)p2xyZxZ|HzzFf4*q0Z=RmdR4 z!$*^O^4-sdXZ3ejXtgbKXiDaOtu&ju5*TGG1%5!^tlN!en8m3VTA2pwJlpop9iL`c zLLqZ`_aEUJ<N0+nr-BaSFS}uA7A<PUXQrS>Ac`HqP4A0HmB#HM`O|zgpN0?Z4c8cG z=%=6x;obnIJ~>v9+3a#gZ5~z64Nl)YimARYI-SElpKkDN|3$2L7*~qx+;?!pOl1~s zx^67M#kf!<Yl^=FuA`e-S=qP$H-*r1={&>@N9oXWIb>b!f^^!Ze7U&fm4g19#PIu_ zoFLTgRG^{F)5bweuN}9PkkK*zc(MLnz-)|F$N1I#3zT+N6;BlMW==WYjuTc?ID5l? zIDh&?65>u_Bh_FqVaGL!wW=@+hyQqd%qi~Jl|Lr|kGE5S!O<>;KijoF%n&3yA%?R) zwpb&AZ<)VZN0puXA&|nT&&=s0J=u^_+|-7|{~OmGLuy!lh8~0ymn;45ntHD3wIyLK zC|l1YEMLveLi;4E?HpE1Py-cPirNL_gDO1l!>##yPwuXJUCs8BTd{K**npNU6i6U9 z&d%gNjL)`G(}&2y)*xL{lyCLii3fi4^OXgGx08A1kos7t{kt~ri1j)pJGaQ^zGw!n zlTm;E6vv<D^|7kc3_!lk-zg-(8)eUJft453sc`mSvxhUUagL;6E=-&?Ia}P31Ba_j zUS~s75GF@_#=^yhn>8iT{YOC}z?Nor&j@HRq;k%ObR*ns%^>RFvC`s>Y1V3fpFW7V zx`iL}iYwm1XHn107K^o^KsEQBbTmeY+JBZ9fx)H&cTcKVv!3iVMK%QVK_*@Glu@v$ zN+Z0|a25$YqS3bg?A%(~h&pq#s;i?4)0kHv_Y$+7$RmWDU;buhkw5zAheQ<(=X#`u z7E0G5AHvxh^x(fmF;)&Nr9D!#ZE6qN*WfE{F@Zl0Z5%bw$+b&?OpNF9464#98e%~> zAJ=MXK#I#__Qs`d1FMXPUHSYx5#m;9%t#6~pUBgN*S@Sxr+Fkz5Xn`pob-Es@tnr& z@ru>Jv48<puXhM-tE|p$$K(wY8`+y}WqOi2@mz_GU$_1Obls;obyt7K1El7kLRaq` zJ@h6lEm@xY4*Z=)xNrSfXGxZR37A{uuhAsF;UvE5HgLb3w!7oaug>XcIx+3_`rWyW zn7UafG>9NA^0yUX{}yi55(7C1JCNdG3X*gXz1(a&o=h7D+P>dpmM^??x8<C}ihHcF z{6us7oAkE63JV)$c;}z==B9eZ`87yINw^1)d-j1j;u55<m#V7<oK<(=5YDYJoRUkc z908=7<w=%-2UqY_g>^rrd8FBGz|ajhmrC+t7I}#bNT9C%2G-UIqLrSOY}6EA_%M0| zzIRmBKaW3KT+q(ahMs+PWuU^}y=u_L)}8Ih_QMlLb;>$Vod;`FPMO#}Ushf!D^PTw z%dw8u{(EIVn5w37u|i{aJ)Y)`MqLp4tQtRvqnYp%G0Al5L!UKVkRmJV$_Wv}k~gGi z8uE&4*gGMK7jM@!V-EXHhpOQqAU|T9Ynci&#U*gCO=Fiqu!V1DLcAeg7Cyu;X3%Qi zT7<AlYv(Cjhe1g%P_XpDzV0#B1HAbbFYpH>V1QxC&pYMmb3@&SrTLToPU(G87b#ME zc2R-j?j$R|T2nkYSB@PQ{DM@)Y67Nbbz3{*h8$|Wx?_$)9DSJ0d(cg`sX>nRed@XI zw$4D3c#%M2aD!vR<UT^rb)EvH9e#$Wwe>{6;Jkik+{LK!F@2o9UD7JgzfMQQS!8pq zR>K}D&M8iqa9qYObx?sm2{zC&sM%OPcOAN0u5otP$FCoSw{<BA2c6-_jJ6vw&Y+Qa z^4+T$i8EufcH_8WQ~u-)o<escUlj$Juba8So`irGUL<aR*uW7AOt(u`fZh74i`@P# zT2RmYzRm$LhhgO%L0$H*^5?U9<GeqGg`ct}y#_8;PKO;DH))nimi@#-d>GJRIA((? zFli4uZ1Gv~S6P1_NHUL1eZ37_P==CC78^lH_K+V>GgTNy^o`xG7A9mYa6j#rKIiIq z3z*Zc1t7|GCS}D?)y1q^Z0&&U9`WVp7UJganF+kA!feO95!I_#sqrLV+%yAQcE0lj zD;FM7&i$J(V%s;=Ln7){E7Sqx9|PZV1JQv<b=;wVrhcc`d;caJee6x(9QG5le@a8e zK`S>mTPo^N=yCn{lwr)I6C1j!NHv|GF!YX7<;@bZon2ndS{rsUVk6s#%i?TR)Qv`^ z;&mdwm?wq$R^l~FI2ZulR;J�i&?$EENv6Xp0&0FUz-+ccT~?QajIQzo0Z!l%jTx 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 literal 0 HcmV?d00001 diff --git a/doc/administration/operations/speed_up_ssh.md b/doc/administration/operations/speed_up_ssh.md index 99d177522797bf30..7012c192a05581df 100644 --- a/doc/administration/operations/speed_up_ssh.md +++ b/doc/administration/operations/speed_up_ssh.md @@ -51,6 +51,18 @@ sudo service sshd reload Confirm that SSH is working by removing your user's SSH key in the UI, adding a new one, and attempting to pull a repo. +> **Warning:** Do not disable writes until SSH is confirmed to be working perfectly because the file will quickly become out-of-date. + +In the case of lookup failures (which are not uncommon), the `authorized_keys` file will still be scanned. So git SSH performance will still be slow for many users as long as a large file exists. + +You can disable any more writes to the `authorized_keys` file by unchecking `Write to "authorized_keys" file` in the Application Settings of your GitLab installation. + + + +Again, confirm that SSH is working by removing your user's SSH key in the UI, adding a new one, and attempting to pull a repo. + +Then you can backup and delete your `authorized_keys` file for best performance. + ## How to go back to using the `authorized_keys` file This is a brief overview. Please refer to the above instructions for more context. diff --git a/lib/gitlab/shell.rb b/lib/gitlab/shell.rb index 5fa5b94daffb4cda..63cd946894037444 100644 --- a/lib/gitlab/shell.rb +++ b/lib/gitlab/shell.rb @@ -197,6 +197,8 @@ def remove_repository(storage, name) # add_key("key-42", "sha-rsa ...") # def add_key(key_id, key_content) + return unless self.authorized_keys_enabled? + Gitlab::Utils.system_silent([gitlab_shell_keys_path, 'add-key', key_id, self.class.strip_key(key_content)]) end @@ -206,6 +208,8 @@ def add_key(key_id, key_content) # Ex. # batch_add_keys { |adder| adder.add_key("key-42", "sha-rsa ...") } def batch_add_keys(&block) + return unless self.authorized_keys_enabled? + IO.popen(%W(#{gitlab_shell_path}/bin/gitlab-keys batch-add-keys), 'w') do |io| yield(KeyAdder.new(io)) end @@ -217,6 +221,8 @@ def batch_add_keys(&block) # remove_key("key-342", "sha-rsa ...") # def remove_key(key_id, key_content) + return unless self.authorized_keys_enabled? + Gitlab::Utils.system_silent([gitlab_shell_keys_path, 'rm-key', key_id, key_content]) end @@ -227,6 +233,8 @@ def remove_key(key_id, key_content) # remove_all_keys # def remove_all_keys + return unless self.authorized_keys_enabled? + Gitlab::Utils.system_silent([gitlab_shell_keys_path, 'clear']) end @@ -356,5 +364,9 @@ def gitlab_shell_projects_path def gitlab_shell_keys_path File.join(gitlab_shell_path, 'bin', 'gitlab-keys') end + + def authorized_keys_enabled? + current_application_settings.authorized_keys_enabled + end end end diff --git a/spec/lib/gitlab/shell_spec.rb b/spec/lib/gitlab/shell_spec.rb index 22c69eeef2f027e3..74a207c36b87238d 100644 --- a/spec/lib/gitlab/shell_spec.rb +++ b/spec/lib/gitlab/shell_spec.rb @@ -104,13 +104,101 @@ end describe '#add_key' do - it 'removes trailing garbage' do - allow(gitlab_shell).to receive(:gitlab_shell_keys_path).and_return(:gitlab_shell_keys_path) - expect(Gitlab::Utils).to receive(:system_silent).with( - [:gitlab_shell_keys_path, 'add-key', 'key-123', 'ssh-rsa foobar'] - ) + context 'when authorized_keys_enabled is true' do + it 'removes trailing garbage' do + allow(gitlab_shell).to receive(:gitlab_shell_keys_path).and_return(:gitlab_shell_keys_path) + expect(Gitlab::Utils).to receive(:system_silent).with( + [:gitlab_shell_keys_path, 'add-key', 'key-123', 'ssh-rsa foobar'] + ) + + gitlab_shell.add_key('key-123', 'ssh-rsa foobar trailing garbage') + end + end + + context 'when authorized_keys_enabled is false' do + before do + stub_application_setting(authorized_keys_enabled: false) + end + + it 'does nothing' do + expect(Gitlab::Utils).not_to receive(:system_silent) + + gitlab_shell.add_key('key-123', 'ssh-rsa foobar trailing garbage') + end + end + end + + describe '#batch_add_keys' do + context 'when authorized_keys_enabled is true' do + it 'instantiates KeyAdder' do + expect_any_instance_of(Gitlab::Shell::KeyAdder).to receive(:add_key).with('key-123', 'ssh-rsa foobar') + + gitlab_shell.batch_add_keys do |adder| + adder.add_key('key-123', 'ssh-rsa foobar') + end + end + end + + context 'when authorized_keys_enabled is false' do + before do + stub_application_setting(authorized_keys_enabled: false) + end + + it 'does nothing' do + expect_any_instance_of(Gitlab::Shell::KeyAdder).not_to receive(:add_key) + + gitlab_shell.batch_add_keys do |adder| + adder.add_key('key-123', 'ssh-rsa foobar') + end + end + end + end - gitlab_shell.add_key('key-123', 'ssh-rsa foobar trailing garbage') + describe '#remove_key' do + context 'when authorized_keys_enabled is true' do + it 'removes trailing garbage' do + allow(gitlab_shell).to receive(:gitlab_shell_keys_path).and_return(:gitlab_shell_keys_path) + expect(Gitlab::Utils).to receive(:system_silent).with( + [:gitlab_shell_keys_path, 'rm-key', 'key-123', 'ssh-rsa foobar'] + ) + + gitlab_shell.remove_key('key-123', 'ssh-rsa foobar') + end + end + + context 'when authorized_keys_enabled is false' do + before do + stub_application_setting(authorized_keys_enabled: false) + end + + it 'does nothing' do + expect(Gitlab::Utils).not_to receive(:system_silent) + + gitlab_shell.remove_key('key-123', 'ssh-rsa foobar') + end + end + end + + describe '#remove_all_keys' do + context 'when authorized_keys_enabled is true' do + it 'removes trailing garbage' do + allow(gitlab_shell).to receive(:gitlab_shell_keys_path).and_return(:gitlab_shell_keys_path) + expect(Gitlab::Utils).to receive(:system_silent).with([:gitlab_shell_keys_path, 'clear']) + + gitlab_shell.remove_all_keys + end + end + + context 'when authorized_keys_enabled is false' do + before do + stub_application_setting(authorized_keys_enabled: false) + end + + it 'does nothing' do + expect(Gitlab::Utils).not_to receive(:system_silent) + + gitlab_shell.remove_all_keys + end end end -- GitLab From a97f5f739810917660e5f236d366007732130e33 Mon Sep 17 00:00:00 2001 From: Michael Kozono <mkozono@gmail.com> Date: Mon, 26 Jun 2017 14:40:08 -0700 Subject: [PATCH 02/18] Retroactively fix migration This allows us to identify customers who ran the broken migration. Their `authorized_keys_enabled` column does not have a default at this point. We will fix the column after we fix the `authorized_keys` file. --- ...uthorized_keys_enabled_to_application_settings.rb | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/db/migrate/20170531180233_add_authorized_keys_enabled_to_application_settings.rb b/db/migrate/20170531180233_add_authorized_keys_enabled_to_application_settings.rb index fdae309946cd9d23..1d86a531eb3dca43 100644 --- a/db/migrate/20170531180233_add_authorized_keys_enabled_to_application_settings.rb +++ b/db/migrate/20170531180233_add_authorized_keys_enabled_to_application_settings.rb @@ -7,9 +7,13 @@ class AddAuthorizedKeysEnabledToApplicationSettings < ActiveRecord::Migration # Set this constant to true if this migration requires downtime. DOWNTIME = false - def change - # allow_null: true because we want to set the default based on if the - # instance is configured to use AuthorizedKeysCommand - add_column :application_settings, :authorized_keys_enabled, :boolean, allow_null: true + disable_ddl_transaction! + + def up + add_column_with_default :application_settings, :authorized_keys_enabled, :boolean, default: true, allow_null: false + end + + def down + remove_column :application_settings, :authorized_keys_enabled end end -- GitLab From febc26e40fbbeadbd025d8f5f42b8b2e1d2f5e0a Mon Sep 17 00:00:00 2001 From: Michael Kozono <mkozono@gmail.com> Date: Tue, 27 Jun 2017 14:14:35 -0700 Subject: [PATCH 03/18] Fix authorized_keys file if needed --- GITLAB_SHELL_VERSION | 2 +- app/workers/gitlab_shell_worker.rb | 15 +- ...70626202753_update_authorized_keys_file.rb | 114 +++++++++ db/schema.rb | 2 +- lib/gitlab/shell.rb | 55 ++++- spec/lib/gitlab/shell_spec.rb | 102 ++++++++ .../update_authorized_keys_file_spec.rb | 232 ++++++++++++++++++ spec/workers/gitlab_shell_worker_spec.rb | 37 +++ 8 files changed, 553 insertions(+), 6 deletions(-) create mode 100644 db/migrate/20170626202753_update_authorized_keys_file.rb create mode 100644 spec/migrations/update_authorized_keys_file_spec.rb create mode 100644 spec/workers/gitlab_shell_worker_spec.rb diff --git a/GITLAB_SHELL_VERSION b/GITLAB_SHELL_VERSION index c20c645d7e49c40c..831446cbd27a6de4 100644 --- a/GITLAB_SHELL_VERSION +++ b/GITLAB_SHELL_VERSION @@ -1 +1 @@ -5.0.6 +5.1.0 diff --git a/app/workers/gitlab_shell_worker.rb b/app/workers/gitlab_shell_worker.rb index 964287a1793a03d7..ddb476226d12244d 100644 --- a/app/workers/gitlab_shell_worker.rb +++ b/app/workers/gitlab_shell_worker.rb @@ -4,6 +4,19 @@ class GitlabShellWorker include DedicatedSidekiqQueue def perform(action, *arg) - gitlab_shell.send(action, *arg) + if action.to_s == 'batch_add_keys_in_db_starting_from' + batch_add_keys_in_db_starting_from(arg.first) + else + gitlab_shell.send(action, *arg) + end + end + + # Not added to Gitlab::Shell because I don't expect this to be used again + def batch_add_keys_in_db_starting_from(start_id) + gitlab_shell.batch_add_keys do |adder| + Key.find_each(start: start_id, batch_size: 1000) do |key| + adder.add_key(key.shell_id, key.key) + end + end end end diff --git a/db/migrate/20170626202753_update_authorized_keys_file.rb b/db/migrate/20170626202753_update_authorized_keys_file.rb new file mode 100644 index 0000000000000000..f696489ea4a8aac0 --- /dev/null +++ b/db/migrate/20170626202753_update_authorized_keys_file.rb @@ -0,0 +1,114 @@ +# See http://doc.gitlab.com/ce/development/migration_style_guide.html +# for more information on how to write migrations for GitLab. + +class UpdateAuthorizedKeysFile < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + class ApplicationSetting < ActiveRecord::Base; end + class Key < ActiveRecord::Base; end + + # Set this constant to true if this migration requires downtime. + DOWNTIME = false + + # We started deploying 9.3.0 to GitLab.com at 2017-06-22T17:24:00+00:00. + # TODO look at the time we pushed to the package server + DATETIME_9_3_0_RELEASED = DateTime.parse('2017-06-22T17:24:00+00:00') + + def up + if authorized_keys_file_in_use_and_stale? + # Update nil authorized_keys_enabled to true to ensure that Gitlab::Shell + # key methods work properly for workers running 9.3.0 during the + # migration. If the setting remained nil, the workers would not edit the + # file. + update_nil_setting_to_true + + update_authorized_keys_file_since(DATETIME_9_3_0_RELEASED) + end + end + + def down + # Do nothing + end + + def authorized_keys_file_in_use_and_stale? + return false unless ran_broken_migration? + + uncached_setting = ApplicationSetting.last + + # If there is no ApplicationSetting record in the DB, then the instance was + # never in a state where `authorized_keys_enabled` field was `nil`. So the + # file is not stale. + return false unless uncached_setting + + if uncached_setting.authorized_keys_enabled == false # not falsey! + # If authorized_keys_enabled is explicitly false, then the file is not in + # use, so it doesn't need to be fixed. I.e. GitLab.com. + # + # Unfortunately it is possible some users may have saved Application + # Settings without realizing the new option existed, and since it + # mistakenly defaulted to unchecked, now it is explicitly false. These + # users need this warning. + say false_negative_warning + return false + end + + # If authorized_keys_enabled is true or nil, then we need to rebuild the + # file in case it is stale. + true + end + + def ran_broken_migration? + # If the column is already fixed, then the migration wasn't run before now. + if Gitlab::Database.postgresql? + !column_exists?(:application_settings, :authorized_keys_enabled, :boolean, default: 'true', null: false) + else + !column_exists?(:application_settings, :authorized_keys_enabled, :boolean, default: '1', null: false) + end + end + + def false_negative_warning + <<-MSG.strip_heredoc + WARNING + + If you did not intentionally disable the "Write to authorized_keys file" + option in Application Settings as outlined in the Speed up SSH + documentation, + + https://docs.gitlab.com/ee/administration/operations/speed_up_ssh.html + + then the authorized_keys file may be out-of-date, affecting SSH + operations. + + If you are affected, please check the "Write to authorized_keys file" + checkbox, and Save. Then rebuild the authorized_keys file as shown here: + + https://docs.gitlab.com/ee/administration/raketasks/maintenance.html#rebuild-authorized_keys-file + + For more information, see the issue: + + https://gitlab.com/gitlab-org/gitlab-ee/issues/2738 + MSG + end + + def update_nil_setting_to_true + setting = ApplicationSetting.last # guaranteed to be found since authorized_keys_file_in_use_and_stale? is true + setting.update!(authorized_keys_enabled: true) if setting.authorized_keys_enabled.nil? + end + + def update_authorized_keys_file_since(cutoff_datetime) + add_keys_since(cutoff_datetime) + + remove_keys_not_found_in_db + end + + def add_keys_since(cutoff_datetime) + start_key = Key.where("created_at >= ?", cutoff_datetime).first + if start_key + GitlabShellWorker.perform_async(:batch_add_keys_in_db_starting_from, start_key.id) + end + end + + def remove_keys_not_found_in_db + GitlabShellWorker.perform_async(:remove_keys_not_found_in_db) + end +end diff --git a/db/schema.rb b/db/schema.rb index 205a1a039d73146c..71a2d84f7a01ef9e 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20170622162730) do +ActiveRecord::Schema.define(version: 20170626202753) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" diff --git a/lib/gitlab/shell.rb b/lib/gitlab/shell.rb index 63cd946894037444..f0942b1c135bf4cb 100644 --- a/lib/gitlab/shell.rb +++ b/lib/gitlab/shell.rb @@ -220,11 +220,12 @@ def batch_add_keys(&block) # Ex. # remove_key("key-342", "sha-rsa ...") # - def remove_key(key_id, key_content) + def remove_key(key_id, key_content = nil) return unless self.authorized_keys_enabled? - Gitlab::Utils.system_silent([gitlab_shell_keys_path, - 'rm-key', key_id, key_content]) + args = [gitlab_shell_keys_path, 'rm-key', key_id] + args << key_content if key_content + Gitlab::Utils.system_silent(args) end # Remove all ssh keys from gitlab shell @@ -238,6 +239,54 @@ def remove_all_keys Gitlab::Utils.system_silent([gitlab_shell_keys_path, 'clear']) end + # Remove ssh keys from gitlab shell that are not in the DB + # + # Ex. + # remove_keys_not_found_in_db + # + def remove_keys_not_found_in_db + return unless self.authorized_keys_enabled? + + batch_read_key_ids do |ids_in_file| + keys_in_db = Key.where(id: ids_in_file) + if ids_in_file.size > keys_in_db.count + ids_to_remove = ids_in_file - keys_in_db.pluck(:id) + ids_to_remove.each do |id| + remove_key("key-#{id}") + end + end + end + end + + # Iterate over all ssh key IDs from gitlab shell, in batches + # + # Ex. + # batch_read_key_ids { |batch| keys = Key.where(id: batch) } + # + def batch_read_key_ids(batch_size: 100, &block) + return unless self.authorized_keys_enabled? + + list_key_ids do |key_id_stream| + key_id_stream.lazy.each_slice(batch_size) do |lines| + key_ids = lines.map { |l| l.chomp.to_i } + yield(key_ids) + end + end + end + + # Stream all ssh key IDs from gitlab shell, separated by newlines + # + # Ex. + # list_key_ids + # + def list_key_ids(&block) + return unless self.authorized_keys_enabled? + + IO.popen(%W(#{gitlab_shell_path}/bin/gitlab-keys list-key-ids)) do |key_id_stream| + yield(key_id_stream) + end + end + # Add empty directory for storing repositories # # Ex. diff --git a/spec/lib/gitlab/shell_spec.rb b/spec/lib/gitlab/shell_spec.rb index 74a207c36b87238d..6713ab0b1f4bcb95 100644 --- a/spec/lib/gitlab/shell_spec.rb +++ b/spec/lib/gitlab/shell_spec.rb @@ -177,6 +177,17 @@ gitlab_shell.remove_key('key-123', 'ssh-rsa foobar') end end + + context 'when key content is not given' do + it 'calls rm-key with only one argument' do + allow(gitlab_shell).to receive(:gitlab_shell_keys_path).and_return(:gitlab_shell_keys_path) + expect(Gitlab::Utils).to receive(:system_silent).with( + [:gitlab_shell_keys_path, 'rm-key', 'key-123'] + ) + + gitlab_shell.remove_key('key-123') + end + end end describe '#remove_all_keys' do @@ -202,6 +213,89 @@ end end + describe '#remove_keys_not_found_in_db' do + context 'when keys are in the file that are not in the DB' do + before do + gitlab_shell.remove_all_keys + gitlab_shell.add_key('key-1234', 'ssh-rsa ASDFASDF') + gitlab_shell.add_key('key-9876', 'ssh-rsa ASDFASDF') + @another_key = create(:key) # this one IS in the DB + end + + it 'removes the keys' do + expect(find_in_authorized_keys_file(1234)).to be_truthy + expect(find_in_authorized_keys_file(9876)).to be_truthy + expect(find_in_authorized_keys_file(@another_key.id)).to be_truthy + gitlab_shell.remove_keys_not_found_in_db + expect(find_in_authorized_keys_file(1234)).to be_falsey + expect(find_in_authorized_keys_file(9876)).to be_falsey + expect(find_in_authorized_keys_file(@another_key.id)).to be_truthy + end + end + end + + describe '#batch_read_key_ids' do + context 'when there are keys in the authorized_keys file' do + before do + gitlab_shell.remove_all_keys + (1..4).each do |i| + gitlab_shell.add_key("key-#{i}", "ssh-rsa ASDFASDF#{i}") + end + end + + it 'iterates over the key IDs in the file, in batches' do + loop_count = 0 + first_batch = [1, 2] + second_batch = [3, 4] + + gitlab_shell.batch_read_key_ids(batch_size: 2) do |batch| + expected = (loop_count == 0 ? first_batch : second_batch) + expect(batch).to eq(expected) + loop_count += 1 + end + end + end + end + + describe '#list_key_ids' do + context 'when there are keys in the authorized_keys file' do + before do + gitlab_shell.remove_all_keys + (1..4).each do |i| + gitlab_shell.add_key("key-#{i}", "ssh-rsa ASDFASDF#{i}") + end + end + + it 'outputs the key IDs in the file, separated by newlines' do + ids = [] + gitlab_shell.list_key_ids do |io| + io.each do |line| + ids << line + end + end + + expect(ids).to eq(["1\n", "2\n", "3\n", "4\n"]) + end + end + + context 'when there are no keys in the authorized_keys file' do + before do + gitlab_shell.remove_all_keys + end + + it 'outputs nothing, not even an empty string' do + ids = [] + gitlab_shell.list_key_ids do |io| + io.each do |line| + ids << line + end + end + + expect(ids).to eq([]) + end + end + end + describe Gitlab::Shell::KeyAdder, lib: true do describe '#add_key' do it 'removes trailing garbage' do @@ -276,4 +370,12 @@ end end end + + def find_in_authorized_keys_file(key_id) + gitlab_shell.batch_read_key_ids do |ids| + return true if ids.include?(key_id) + end + + false + end end diff --git a/spec/migrations/update_authorized_keys_file_spec.rb b/spec/migrations/update_authorized_keys_file_spec.rb new file mode 100644 index 0000000000000000..dbb3b8d5fbef8cee --- /dev/null +++ b/spec/migrations/update_authorized_keys_file_spec.rb @@ -0,0 +1,232 @@ +require 'spec_helper' +require Rails.root.join('db', 'migrate', '20170626202753_update_authorized_keys_file.rb') + +describe UpdateAuthorizedKeysFile do + let(:migration) { described_class.new } + + describe '#up' do + context 'when authorized_keys_enabled is nil' do + before do + # Ensure the column can be null for the test + ActiveRecord::Base.connection.change_column_null :application_settings, :authorized_keys_enabled, true + ActiveRecord::Base.connection.change_column :application_settings, :authorized_keys_enabled, :boolean, default: nil + + ApplicationSetting.create!(authorized_keys_enabled: nil) + end + + it 'sets authorized_keys_enabled to true' do + migration.up + + expect(ApplicationSetting.last.authorized_keys_enabled).to be_truthy + end + + context 'there are keys created before and after the cutoff datetime' do + before do + Timecop.freeze + end + after do + Timecop.return + end + + before do + @cutoff_datetime = UpdateAuthorizedKeysFile::DATETIME_9_3_0_RELEASED + @keys = [] + Timecop.travel(@cutoff_datetime - 1.day) + 2.times { @keys << create(:key) } # 2 keys before cutoff + Timecop.travel(@cutoff_datetime + 1.day) + 2.times { @keys << create(:key) } # 2 keys after cutoff + end + + it 'adds the keys created after the cutoff datetime to the authorized_keys file' do + Gitlab::Shell.new.remove_all_keys + + migration.up + + file = File.read(Rails.root.join('tmp/tests/.ssh/authorized_keys')) + expect(file.scan(/ssh-rsa/).count).to eq(2) + + expect(file).not_to include(Gitlab::Shell.strip_key(@keys[0].key)) + expect(file).not_to include(Gitlab::Shell.strip_key(@keys[1].key)) + expect(file).to include(Gitlab::Shell.strip_key(@keys[2].key)) + expect(file).to include(Gitlab::Shell.strip_key(@keys[3].key)) + end + end + + context 'when an SSH key exists in authorized_keys but not in the DB' do + before do + @key_to_stay = create(:key) + @key_to_delete = create(:key) + @key_to_delete.delete + end + + it 'deletes the SSH key from authorized_keys' do + file = File.read(Rails.root.join('tmp/tests/.ssh/authorized_keys')) + expect(file).to include(Gitlab::Shell.strip_key(@key_to_stay.key)) + expect(file).to include(Gitlab::Shell.strip_key(@key_to_delete.key)) + + migration.up + + file = File.read(Rails.root.join('tmp/tests/.ssh/authorized_keys')) + expect(file).to include(Gitlab::Shell.strip_key(@key_to_stay.key)) + expect(file).not_to include(Gitlab::Shell.strip_key(@key_to_delete.key)) + end + end + end + end + + describe '#authorized_keys_file_in_use_and_stale?' do + subject { migration.authorized_keys_file_in_use_and_stale? } + + context 'when the customer ran the broken migration' do + before do + allow(migration).to receive(:ran_broken_migration?).and_return(true) + end + + context 'when is a record in application_settings table' do + before do + ApplicationSetting.create!(authorized_keys_enabled: true) + end + + context 'when authorized_keys_enabled is true' do + it { is_expected.to be_truthy } + end + + context 'when authorized_keys_enabled is nil' do + before do + # Ensure the column can be null for the test + ActiveRecord::Base.connection.change_column_null :application_settings, :authorized_keys_enabled, true + ActiveRecord::Base.connection.change_column :application_settings, :authorized_keys_enabled, :boolean, default: nil + + ApplicationSetting.first.update(authorized_keys_enabled: nil) + end + + it { is_expected.to be_truthy } + end + + context 'when authorized_keys_enabled is explicitly false' do + before do + ApplicationSetting.first.update(authorized_keys_enabled: false) + end + + it { is_expected.to be_falsey } + + it 'outputs a warning message for users who unintentionally Saved the setting unchecked' do + expect{ subject }.to output(/warning.*intentionally/mi).to_stdout + end + end + end + + context 'when there is no record in application_settings table' do + before do + expect(ApplicationSetting.count).to eq(0) + end + + it { is_expected.to be_falsey } + end + end + + context 'when the customer did not run the broken migration' do + before do + allow(migration).to receive(:ran_broken_migration?).and_return(false) + end + + it { is_expected.to be_falsey } + end + end + + describe '#ran_broken_migration?' do + subject { migration.ran_broken_migration? } + + context 'for unaffected customers: the authorized_keys_enabled column has a default (so the fixed migration ran)' do + before do + ActiveRecord::Base.connection.change_column :application_settings, :authorized_keys_enabled, :boolean, default: true + ActiveRecord::Base.connection.change_column_null :application_settings, :authorized_keys_enabled, false, true + end + + it 'returns false' do + expect(subject).to be_falsey + end + end + + context 'for affected customers: the authorized_keys_enabled column does not have a default (so the broken migration ran)' do + before do + ActiveRecord::Base.connection.change_column_null :application_settings, :authorized_keys_enabled, true + ActiveRecord::Base.connection.change_column :application_settings, :authorized_keys_enabled, :boolean, default: nil + end + + it 'returns true' do + expect(subject).to be_truthy + end + end + end + + describe '#update_authorized_keys_file_since' do + let!(:cutoff_datetime) { DateTime.now } + + subject { migration.update_authorized_keys_file_since(cutoff_datetime) } + + context 'when an SSH key was created after the cutoff datetime' do + before do + Timecop.freeze + end + after do + Timecop.return + end + + before do + Timecop.travel 1.day.from_now + @key = create(:key) + end + + it 'queues a batch_add_keys_from call to GitlabShellWorker, including the start key ID' do + expect(GitlabShellWorker).to receive(:perform_async).with(:batch_add_keys_in_db_starting_from, @key.id) + allow(GitlabShellWorker).to receive(:perform_async).with(:remove_keys_not_found_in_db) + subject + end + end + + it 'queues a remove_keys_not_found_in_db call to GitlabShellWorker' do + expect(GitlabShellWorker).to receive(:perform_async).with(:remove_keys_not_found_in_db) + subject + end + end + + describe '#add_keys_since' do + let!(:cutoff_datetime) { DateTime.now } + + subject { migration.add_keys_since(cutoff_datetime) } + + before do + Timecop.freeze + end + after do + Timecop.return + end + + context 'when an SSH key was created after the cutoff datetime' do + before do + Timecop.travel 1.day.from_now + @key = create(:key) + end + + it 'queues a batch_add_keys_from call to GitlabShellWorker, including the start key ID' do + expect(GitlabShellWorker).to receive(:perform_async).with(:batch_add_keys_in_db_starting_from, @key.id) + subject + end + end + + context 'when an SSH key was not created after the cutoff datetime' do + it 'does not use GitlabShellWorker' do + expect(GitlabShellWorker).not_to receive(:perform_async) + subject + end + end + end + + describe '#remove_keys_not_found_in_db' do + it 'queues a rm_keys_not_in_db call to GitlabShellWorker' do + expect(GitlabShellWorker).to receive(:perform_async).with(:remove_keys_not_found_in_db) + migration.remove_keys_not_found_in_db + end + end +end diff --git a/spec/workers/gitlab_shell_worker_spec.rb b/spec/workers/gitlab_shell_worker_spec.rb new file mode 100644 index 0000000000000000..6147a24760c0ca69 --- /dev/null +++ b/spec/workers/gitlab_shell_worker_spec.rb @@ -0,0 +1,37 @@ +require 'spec_helper' + +describe GitlabShellWorker do + let(:worker) { described_class.new } + + describe '#perform with add_key' do + it 'calls add_key on Gitlab::Shell' do + expect_any_instance_of(Gitlab::Shell).to receive(:add_key).with('foo', 'bar') + worker.perform(:add_key, 'foo', 'bar') + end + end + + describe '#perform with batch_add_keys_in_db_starting_from' do + context 'when there are many keys in the DB' do + before do + @keys = [] + 10.times do + @keys << create(:key) + end + end + + it 'adds all the keys in the DB, starting from the given ID, to the authorized_keys file' do + Gitlab::Shell.new.remove_all_keys + + worker.perform(:batch_add_keys_in_db_starting_from, @keys[3].id) + + file = File.read(Rails.root.join('tmp/tests/.ssh/authorized_keys')) + expect(file.scan(/ssh-rsa/).count).to eq(7) + + expect(file).to_not include(Gitlab::Shell.strip_key(@keys[0].key)) + expect(file).to_not include(Gitlab::Shell.strip_key(@keys[2].key)) + expect(file).to include(Gitlab::Shell.strip_key(@keys[3].key)) + expect(file).to include(Gitlab::Shell.strip_key(@keys[9].key)) + end + end + end +end -- GitLab From f6ace56eefb5a05d00085dc82478bf2388c5436e Mon Sep 17 00:00:00 2001 From: Michael Kozono <mkozono@gmail.com> Date: Fri, 23 Jun 2017 19:27:41 -0700 Subject: [PATCH 04/18] Ensure `authorized_keys_enabled` defaults to true Locally, if Spring was not restarted, `current_application_settings` was still cached, which prevented the migration from editing the file. This will also ensure that any app server somehow hitting old cache data will properly default this setting regardless. --- lib/gitlab/shell.rb | 2 ++ spec/lib/gitlab/shell_spec.rb | 57 +++++++++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+) diff --git a/lib/gitlab/shell.rb b/lib/gitlab/shell.rb index f0942b1c135bf4cb..15b0e1511ae30cdd 100644 --- a/lib/gitlab/shell.rb +++ b/lib/gitlab/shell.rb @@ -415,6 +415,8 @@ def gitlab_shell_keys_path end def authorized_keys_enabled? + return true if current_application_settings.authorized_keys_enabled.nil? + current_application_settings.authorized_keys_enabled end end diff --git a/spec/lib/gitlab/shell_spec.rb b/spec/lib/gitlab/shell_spec.rb index 6713ab0b1f4bcb95..ff533e6ffecb8ec0 100644 --- a/spec/lib/gitlab/shell_spec.rb +++ b/spec/lib/gitlab/shell_spec.rb @@ -126,6 +126,21 @@ gitlab_shell.add_key('key-123', 'ssh-rsa foobar trailing garbage') end end + + context 'when authorized_keys_enabled is nil' do + before do + stub_application_setting(authorized_keys_enabled: nil) + end + + it 'removes trailing garbage' do + allow(gitlab_shell).to receive(:gitlab_shell_keys_path).and_return(:gitlab_shell_keys_path) + expect(Gitlab::Utils).to receive(:system_silent).with( + [:gitlab_shell_keys_path, 'add-key', 'key-123', 'ssh-rsa foobar'] + ) + + gitlab_shell.add_key('key-123', 'ssh-rsa foobar trailing garbage') + end + end end describe '#batch_add_keys' do @@ -152,6 +167,20 @@ end end end + + context 'when authorized_keys_enabled is nil' do + before do + stub_application_setting(authorized_keys_enabled: nil) + end + + it 'instantiates KeyAdder' do + expect_any_instance_of(Gitlab::Shell::KeyAdder).to receive(:add_key).with('key-123', 'ssh-rsa foobar') + + gitlab_shell.batch_add_keys do |adder| + adder.add_key('key-123', 'ssh-rsa foobar') + end + end + end end describe '#remove_key' do @@ -178,6 +207,21 @@ end end + context 'when authorized_keys_enabled is nil' do + before do + stub_application_setting(authorized_keys_enabled: nil) + end + + it 'removes trailing garbage' do + allow(gitlab_shell).to receive(:gitlab_shell_keys_path).and_return(:gitlab_shell_keys_path) + expect(Gitlab::Utils).to receive(:system_silent).with( + [:gitlab_shell_keys_path, 'rm-key', 'key-123', 'ssh-rsa foobar'] + ) + + gitlab_shell.remove_key('key-123', 'ssh-rsa foobar') + end + end + context 'when key content is not given' do it 'calls rm-key with only one argument' do allow(gitlab_shell).to receive(:gitlab_shell_keys_path).and_return(:gitlab_shell_keys_path) @@ -211,6 +255,19 @@ gitlab_shell.remove_all_keys end end + + context 'when authorized_keys_enabled is nil' do + before do + stub_application_setting(authorized_keys_enabled: nil) + end + + it 'removes trailing garbage' do + allow(gitlab_shell).to receive(:gitlab_shell_keys_path).and_return(:gitlab_shell_keys_path) + expect(Gitlab::Utils).to receive(:system_silent).with([:gitlab_shell_keys_path, 'clear']) + + gitlab_shell.remove_all_keys + end + end end describe '#remove_keys_not_found_in_db' do -- GitLab From 002363394e023cecf670209565d15367fd923977 Mon Sep 17 00:00:00 2001 From: Michael Kozono <mkozono@gmail.com> Date: Tue, 27 Jun 2017 14:24:12 -0700 Subject: [PATCH 05/18] Add default to authorized_keys_enabled setting Reminder: The original migration was fixed retroactively a few commits ago, so people who did not ever run GitLab 9.3.0 already have a column that defaults to true and disallows nulls. I have tested on PostgreSQL and MySQL that it is safe to run this migration regardless. Affected customers who did run 9.3.0 are the ones who need this migration to fix the authorized_keys_enabled column. The reason for the retroactive fix plus this migration is that it allows us to run a migration in between to fix the authorized_keys file only for those who ran 9.3.0. --- ...orized_keys_enabled_application_setting.rb | 20 +++++++++++++++++++ db/schema.rb | 4 ++-- 2 files changed, 22 insertions(+), 2 deletions(-) create mode 100644 db/migrate/20170627211700_add_default_to_authorized_keys_enabled_application_setting.rb diff --git a/db/migrate/20170627211700_add_default_to_authorized_keys_enabled_application_setting.rb b/db/migrate/20170627211700_add_default_to_authorized_keys_enabled_application_setting.rb new file mode 100644 index 0000000000000000..aa26af35b8fa7a48 --- /dev/null +++ b/db/migrate/20170627211700_add_default_to_authorized_keys_enabled_application_setting.rb @@ -0,0 +1,20 @@ +# See http://doc.gitlab.com/ce/development/migration_style_guide.html +# for more information on how to write migrations for GitLab. + +class AddDefaultToAuthorizedKeysEnabledApplicationSetting < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + disable_ddl_transaction! + + def up + change_column :application_settings, :authorized_keys_enabled, :boolean, default: true + change_column_null :application_settings, :authorized_keys_enabled, false, true + end + + def down + change_column_null :application_settings, :authorized_keys_enabled, true + change_column :application_settings, :authorized_keys_enabled, :boolean, default: nil + end +end diff --git a/db/schema.rb b/db/schema.rb index 71a2d84f7a01ef9e..b07fd912fba1e3d0 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20170626202753) do +ActiveRecord::Schema.define(version: 20170627211700) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -141,7 +141,7 @@ t.integer "mirror_max_delay", default: 5, null: false t.integer "mirror_max_capacity", default: 100, null: false t.integer "mirror_capacity_threshold", default: 50, null: false - t.boolean "authorized_keys_enabled" + t.boolean "authorized_keys_enabled", default: true, null: false t.boolean "help_page_hide_commercial_content", default: false t.string "help_page_support_url" end -- GitLab From 4b3d8f3873db033d30c0eeb37f35f124fbacfc73 Mon Sep 17 00:00:00 2001 From: Michael Kozono <mkozono@gmail.com> Date: Wed, 28 Jun 2017 12:14:25 -0700 Subject: [PATCH 06/18] Tweaks to address feedback --- ...70626202753_update_authorized_keys_file.rb | 33 ++++++++++--------- lib/gitlab/shell.rb | 6 ++-- 2 files changed, 20 insertions(+), 19 deletions(-) diff --git a/db/migrate/20170626202753_update_authorized_keys_file.rb b/db/migrate/20170626202753_update_authorized_keys_file.rb index f696489ea4a8aac0..ef9e935387b7f956 100644 --- a/db/migrate/20170626202753_update_authorized_keys_file.rb +++ b/db/migrate/20170626202753_update_authorized_keys_file.rb @@ -4,15 +4,19 @@ class UpdateAuthorizedKeysFile < ActiveRecord::Migration include Gitlab::Database::MigrationHelpers - class ApplicationSetting < ActiveRecord::Base; end - class Key < ActiveRecord::Base; end + class ApplicationSetting < ActiveRecord::Base + self.table_name = 'application_settings' + end + class Key < ActiveRecord::Base + self.table_name = 'keys' + end # Set this constant to true if this migration requires downtime. DOWNTIME = false - # We started deploying 9.3.0 to GitLab.com at 2017-06-22T17:24:00+00:00. - # TODO look at the time we pushed to the package server - DATETIME_9_3_0_RELEASED = DateTime.parse('2017-06-22T17:24:00+00:00') + # We started deploying 9.3.0 to GitLab.com at 2017-06-22 17:24 UTC, and the + # package was pushed some time before that. Some buffer room is included here. + DATETIME_9_3_0_RELEASED = DateTime.parse('2017-06-22T00:00:00+00:00') def up if authorized_keys_file_in_use_and_stale? @@ -33,14 +37,14 @@ def down def authorized_keys_file_in_use_and_stale? return false unless ran_broken_migration? - uncached_setting = ApplicationSetting.last + @uncached_application_setting = ApplicationSetting.last # If there is no ApplicationSetting record in the DB, then the instance was # never in a state where `authorized_keys_enabled` field was `nil`. So the # file is not stale. - return false unless uncached_setting + return false unless @uncached_application_setting - if uncached_setting.authorized_keys_enabled == false # not falsey! + if @uncached_application_setting.authorized_keys_enabled == false # not falsey! # If authorized_keys_enabled is explicitly false, then the file is not in # use, so it doesn't need to be fixed. I.e. GitLab.com. # @@ -59,11 +63,9 @@ def authorized_keys_file_in_use_and_stale? def ran_broken_migration? # If the column is already fixed, then the migration wasn't run before now. - if Gitlab::Database.postgresql? - !column_exists?(:application_settings, :authorized_keys_enabled, :boolean, default: 'true', null: false) - else - !column_exists?(:application_settings, :authorized_keys_enabled, :boolean, default: '1', null: false) - end + default_value = Gitlab::Database.postgresql? ? 'true' : '1' + + !column_exists?(:application_settings, :authorized_keys_enabled, :boolean, default: default_value, null: false) end def false_negative_warning @@ -91,8 +93,7 @@ def false_negative_warning end def update_nil_setting_to_true - setting = ApplicationSetting.last # guaranteed to be found since authorized_keys_file_in_use_and_stale? is true - setting.update!(authorized_keys_enabled: true) if setting.authorized_keys_enabled.nil? + @uncached_application_setting.update_attribute(:authorized_keys_enabled, true) end def update_authorized_keys_file_since(cutoff_datetime) @@ -102,7 +103,7 @@ def update_authorized_keys_file_since(cutoff_datetime) end def add_keys_since(cutoff_datetime) - start_key = Key.where("created_at >= ?", cutoff_datetime).first + start_key = Key.select(:id).where("created_at >= ?", cutoff_datetime).take if start_key GitlabShellWorker.perform_async(:batch_add_keys_in_db_starting_from, start_key.id) end diff --git a/lib/gitlab/shell.rb b/lib/gitlab/shell.rb index 15b0e1511ae30cdd..528beeda6ae1b9d5 100644 --- a/lib/gitlab/shell.rb +++ b/lib/gitlab/shell.rb @@ -282,9 +282,7 @@ def batch_read_key_ids(batch_size: 100, &block) def list_key_ids(&block) return unless self.authorized_keys_enabled? - IO.popen(%W(#{gitlab_shell_path}/bin/gitlab-keys list-key-ids)) do |key_id_stream| - yield(key_id_stream) - end + IO.popen(%W(#{gitlab_shell_path}/bin/gitlab-keys list-key-ids), &block) end # Add empty directory for storing repositories @@ -415,6 +413,8 @@ def gitlab_shell_keys_path end def authorized_keys_enabled? + # Return true if nil to ensure the authorized_keys methods work while + # fixing the authorized_keys file during migration. return true if current_application_settings.authorized_keys_enabled.nil? current_application_settings.authorized_keys_enabled -- GitLab From 12d0a64debb75ec4f0c436a58d268d725a827d06 Mon Sep 17 00:00:00 2001 From: Michael Kozono <mkozono@gmail.com> Date: Wed, 28 Jun 2017 12:35:00 -0700 Subject: [PATCH 07/18] Extract work into background migration --- ...70626202753_update_authorized_keys_file.rb | 19 +---- .../update_authorized_keys_file_since.rb | 26 +++++++ .../update_authorized_keys_file_since_spec.rb | 73 +++++++++++++++++++ .../update_authorized_keys_file_spec.rb | 70 ------------------ 4 files changed, 101 insertions(+), 87 deletions(-) create mode 100644 lib/gitlab/background_migration/update_authorized_keys_file_since.rb create mode 100644 spec/lib/gitlab/background_migration/update_authorized_keys_file_since_spec.rb diff --git a/db/migrate/20170626202753_update_authorized_keys_file.rb b/db/migrate/20170626202753_update_authorized_keys_file.rb index ef9e935387b7f956..4e0119d1a5d5c62f 100644 --- a/db/migrate/20170626202753_update_authorized_keys_file.rb +++ b/db/migrate/20170626202753_update_authorized_keys_file.rb @@ -7,9 +7,6 @@ class UpdateAuthorizedKeysFile < ActiveRecord::Migration class ApplicationSetting < ActiveRecord::Base self.table_name = 'application_settings' end - class Key < ActiveRecord::Base - self.table_name = 'keys' - end # Set this constant to true if this migration requires downtime. DOWNTIME = false @@ -97,19 +94,7 @@ def update_nil_setting_to_true end def update_authorized_keys_file_since(cutoff_datetime) - add_keys_since(cutoff_datetime) - - remove_keys_not_found_in_db - end - - def add_keys_since(cutoff_datetime) - start_key = Key.select(:id).where("created_at >= ?", cutoff_datetime).take - if start_key - GitlabShellWorker.perform_async(:batch_add_keys_in_db_starting_from, start_key.id) - end - end - - def remove_keys_not_found_in_db - GitlabShellWorker.perform_async(:remove_keys_not_found_in_db) + job = ['UpdateAuthorizedKeysFileSince', [cutoff_datetime]] + BackgroundMigrationWorker.perform_bulk(job) end end diff --git a/lib/gitlab/background_migration/update_authorized_keys_file_since.rb b/lib/gitlab/background_migration/update_authorized_keys_file_since.rb new file mode 100644 index 0000000000000000..0fc7fe1dea738b9a --- /dev/null +++ b/lib/gitlab/background_migration/update_authorized_keys_file_since.rb @@ -0,0 +1,26 @@ +module Gitlab + module BackgroundMigration + class UpdateAuthorizedKeysFileSince + class Key < ActiveRecord::Base + self.table_name = 'keys' + end + + def perform(cutoff_datetime) + add_keys_since(cutoff_datetime) + + remove_keys_not_found_in_db + end + + def add_keys_since(cutoff_datetime) + start_key = Key.select(:id).where("created_at >= ?", cutoff_datetime).take + if start_key + GitlabShellWorker.perform_async(:batch_add_keys_in_db_starting_from, start_key.id) + end + end + + def remove_keys_not_found_in_db + GitlabShellWorker.perform_async(:remove_keys_not_found_in_db) + end + end + end +end diff --git a/spec/lib/gitlab/background_migration/update_authorized_keys_file_since_spec.rb b/spec/lib/gitlab/background_migration/update_authorized_keys_file_since_spec.rb new file mode 100644 index 0000000000000000..57e822040f094189 --- /dev/null +++ b/spec/lib/gitlab/background_migration/update_authorized_keys_file_since_spec.rb @@ -0,0 +1,73 @@ +require 'spec_helper' + +describe Gitlab::BackgroundMigration::UpdateAuthorizedKeysFileSince do + describe '#perform' do + let!(:cutoff_datetime) { DateTime.now } + + subject { described_class.new.perform(cutoff_datetime) } + + context 'when an SSH key was created after the cutoff datetime' do + before do + Timecop.freeze + end + after do + Timecop.return + end + + before do + Timecop.travel 1.day.from_now + @key = create(:key) + end + + it 'queues a batch_add_keys_from call to GitlabShellWorker, including the start key ID' do + expect(GitlabShellWorker).to receive(:perform_async).with(:batch_add_keys_in_db_starting_from, @key.id) + allow(GitlabShellWorker).to receive(:perform_async).with(:remove_keys_not_found_in_db) + subject + end + end + + it 'queues a remove_keys_not_found_in_db call to GitlabShellWorker' do + expect(GitlabShellWorker).to receive(:perform_async).with(:remove_keys_not_found_in_db) + subject + end + end + + describe '#add_keys_since' do + let!(:cutoff_datetime) { DateTime.now } + + subject { described_class.new.add_keys_since(cutoff_datetime) } + + before do + Timecop.freeze + end + after do + Timecop.return + end + + context 'when an SSH key was created after the cutoff datetime' do + before do + Timecop.travel 1.day.from_now + @key = create(:key) + end + + it 'queues a batch_add_keys_from call to GitlabShellWorker, including the start key ID' do + expect(GitlabShellWorker).to receive(:perform_async).with(:batch_add_keys_in_db_starting_from, @key.id) + subject + end + end + + context 'when an SSH key was not created after the cutoff datetime' do + it 'does not use GitlabShellWorker' do + expect(GitlabShellWorker).not_to receive(:perform_async) + subject + end + end + end + + describe '#remove_keys_not_found_in_db' do + it 'queues a rm_keys_not_in_db call to GitlabShellWorker' do + expect(GitlabShellWorker).to receive(:perform_async).with(:remove_keys_not_found_in_db) + described_class.new.remove_keys_not_found_in_db + end + end +end diff --git a/spec/migrations/update_authorized_keys_file_spec.rb b/spec/migrations/update_authorized_keys_file_spec.rb index dbb3b8d5fbef8cee..15b85366ac1a1f1c 100644 --- a/spec/migrations/update_authorized_keys_file_spec.rb +++ b/spec/migrations/update_authorized_keys_file_spec.rb @@ -159,74 +159,4 @@ end end end - - describe '#update_authorized_keys_file_since' do - let!(:cutoff_datetime) { DateTime.now } - - subject { migration.update_authorized_keys_file_since(cutoff_datetime) } - - context 'when an SSH key was created after the cutoff datetime' do - before do - Timecop.freeze - end - after do - Timecop.return - end - - before do - Timecop.travel 1.day.from_now - @key = create(:key) - end - - it 'queues a batch_add_keys_from call to GitlabShellWorker, including the start key ID' do - expect(GitlabShellWorker).to receive(:perform_async).with(:batch_add_keys_in_db_starting_from, @key.id) - allow(GitlabShellWorker).to receive(:perform_async).with(:remove_keys_not_found_in_db) - subject - end - end - - it 'queues a remove_keys_not_found_in_db call to GitlabShellWorker' do - expect(GitlabShellWorker).to receive(:perform_async).with(:remove_keys_not_found_in_db) - subject - end - end - - describe '#add_keys_since' do - let!(:cutoff_datetime) { DateTime.now } - - subject { migration.add_keys_since(cutoff_datetime) } - - before do - Timecop.freeze - end - after do - Timecop.return - end - - context 'when an SSH key was created after the cutoff datetime' do - before do - Timecop.travel 1.day.from_now - @key = create(:key) - end - - it 'queues a batch_add_keys_from call to GitlabShellWorker, including the start key ID' do - expect(GitlabShellWorker).to receive(:perform_async).with(:batch_add_keys_in_db_starting_from, @key.id) - subject - end - end - - context 'when an SSH key was not created after the cutoff datetime' do - it 'does not use GitlabShellWorker' do - expect(GitlabShellWorker).not_to receive(:perform_async) - subject - end - end - end - - describe '#remove_keys_not_found_in_db' do - it 'queues a rm_keys_not_in_db call to GitlabShellWorker' do - expect(GitlabShellWorker).to receive(:perform_async).with(:remove_keys_not_found_in_db) - migration.remove_keys_not_found_in_db - end - end end -- GitLab From 8d6e72f3e952bafa919e2c23906dd4ff335fd10f Mon Sep 17 00:00:00 2001 From: Michael Kozono <mkozono@gmail.com> Date: Wed, 28 Jun 2017 12:48:03 -0700 Subject: [PATCH 08/18] Move batch-add-logic to background migration Do the work synchronously to avoid multiple workers attempting to add batches of keys at the same time. Also, make the delete portion wait until after adding is done. --- app/workers/gitlab_shell_worker.rb | 15 +----- .../update_authorized_keys_file_since.rb | 19 +++++++- .../update_authorized_keys_file_since_spec.rb | 46 +++++++++++++++---- spec/workers/gitlab_shell_worker_spec.rb | 25 ---------- 4 files changed, 55 insertions(+), 50 deletions(-) diff --git a/app/workers/gitlab_shell_worker.rb b/app/workers/gitlab_shell_worker.rb index ddb476226d12244d..964287a1793a03d7 100644 --- a/app/workers/gitlab_shell_worker.rb +++ b/app/workers/gitlab_shell_worker.rb @@ -4,19 +4,6 @@ class GitlabShellWorker include DedicatedSidekiqQueue def perform(action, *arg) - if action.to_s == 'batch_add_keys_in_db_starting_from' - batch_add_keys_in_db_starting_from(arg.first) - else - gitlab_shell.send(action, *arg) - end - end - - # Not added to Gitlab::Shell because I don't expect this to be used again - def batch_add_keys_in_db_starting_from(start_id) - gitlab_shell.batch_add_keys do |adder| - Key.find_each(start: start_id, batch_size: 1000) do |key| - adder.add_key(key.shell_id, key.key) - end - end + gitlab_shell.send(action, *arg) end end diff --git a/lib/gitlab/background_migration/update_authorized_keys_file_since.rb b/lib/gitlab/background_migration/update_authorized_keys_file_since.rb index 0fc7fe1dea738b9a..9b9fb9efc2d136d3 100644 --- a/lib/gitlab/background_migration/update_authorized_keys_file_since.rb +++ b/lib/gitlab/background_migration/update_authorized_keys_file_since.rb @@ -3,6 +3,10 @@ module BackgroundMigration class UpdateAuthorizedKeysFileSince class Key < ActiveRecord::Base self.table_name = 'keys' + + def shell_id + "key-#{id}" + end end def perform(cutoff_datetime) @@ -14,13 +18,26 @@ def perform(cutoff_datetime) def add_keys_since(cutoff_datetime) start_key = Key.select(:id).where("created_at >= ?", cutoff_datetime).take if start_key - GitlabShellWorker.perform_async(:batch_add_keys_in_db_starting_from, start_key.id) + batch_add_keys_in_db_starting_from(start_key.id) end end def remove_keys_not_found_in_db GitlabShellWorker.perform_async(:remove_keys_not_found_in_db) end + + # Not added to Gitlab::Shell because I don't expect this to be used again + def batch_add_keys_in_db_starting_from(start_id) + gitlab_shell.batch_add_keys do |adder| + Key.find_each(start: start_id, batch_size: 1000) do |key| + adder.add_key(key.shell_id, key.key) + end + end + end + + def gitlab_shell + @gitlab_shell ||= Gitlab::Shell.new + end end end end diff --git a/spec/lib/gitlab/background_migration/update_authorized_keys_file_since_spec.rb b/spec/lib/gitlab/background_migration/update_authorized_keys_file_since_spec.rb index 57e822040f094189..fdc31006b80fbed9 100644 --- a/spec/lib/gitlab/background_migration/update_authorized_keys_file_since_spec.rb +++ b/spec/lib/gitlab/background_migration/update_authorized_keys_file_since_spec.rb @@ -1,10 +1,12 @@ require 'spec_helper' describe Gitlab::BackgroundMigration::UpdateAuthorizedKeysFileSince do + let(:background_migration) { described_class.new } + describe '#perform' do let!(:cutoff_datetime) { DateTime.now } - subject { described_class.new.perform(cutoff_datetime) } + subject { background_migration.perform(cutoff_datetime) } context 'when an SSH key was created after the cutoff datetime' do before do @@ -19,9 +21,8 @@ @key = create(:key) end - it 'queues a batch_add_keys_from call to GitlabShellWorker, including the start key ID' do - expect(GitlabShellWorker).to receive(:perform_async).with(:batch_add_keys_in_db_starting_from, @key.id) - allow(GitlabShellWorker).to receive(:perform_async).with(:remove_keys_not_found_in_db) + it 'calls batch_add_keys_in_db_starting_from with the start key ID' do + expect(background_migration).to receive(:batch_add_keys_in_db_starting_from).with(@key.id) subject end end @@ -35,7 +36,7 @@ describe '#add_keys_since' do let!(:cutoff_datetime) { DateTime.now } - subject { described_class.new.add_keys_since(cutoff_datetime) } + subject { background_migration.add_keys_since(cutoff_datetime) } before do Timecop.freeze @@ -50,15 +51,15 @@ @key = create(:key) end - it 'queues a batch_add_keys_from call to GitlabShellWorker, including the start key ID' do - expect(GitlabShellWorker).to receive(:perform_async).with(:batch_add_keys_in_db_starting_from, @key.id) + it 'calls batch_add_keys_in_db_starting_from with the start key ID' do + expect(background_migration).to receive(:batch_add_keys_in_db_starting_from).with(@key.id) subject end end context 'when an SSH key was not created after the cutoff datetime' do - it 'does not use GitlabShellWorker' do - expect(GitlabShellWorker).not_to receive(:perform_async) + it 'does not call batch_add_keys_in_db_starting_from' do + expect(background_migration).not_to receive(:batch_add_keys_in_db_starting_from) subject end end @@ -67,7 +68,32 @@ describe '#remove_keys_not_found_in_db' do it 'queues a rm_keys_not_in_db call to GitlabShellWorker' do expect(GitlabShellWorker).to receive(:perform_async).with(:remove_keys_not_found_in_db) - described_class.new.remove_keys_not_found_in_db + background_migration.remove_keys_not_found_in_db + end + end + + describe '#batch_add_keys_in_db_starting_from' do + context 'when there are many keys in the DB' do + before do + @keys = [] + 10.times do + @keys << create(:key) + end + end + + it 'adds all the keys in the DB, starting from the given ID, to the authorized_keys file' do + Gitlab::Shell.new.remove_all_keys + + background_migration.batch_add_keys_in_db_starting_from(@keys[3].id) + + file = File.read(Rails.root.join('tmp/tests/.ssh/authorized_keys')) + expect(file.scan(/ssh-rsa/).count).to eq(7) + + expect(file).to_not include(Gitlab::Shell.strip_key(@keys[0].key)) + expect(file).to_not include(Gitlab::Shell.strip_key(@keys[2].key)) + expect(file).to include(Gitlab::Shell.strip_key(@keys[3].key)) + expect(file).to include(Gitlab::Shell.strip_key(@keys[9].key)) + end end end end diff --git a/spec/workers/gitlab_shell_worker_spec.rb b/spec/workers/gitlab_shell_worker_spec.rb index 6147a24760c0ca69..6b222af454d8c819 100644 --- a/spec/workers/gitlab_shell_worker_spec.rb +++ b/spec/workers/gitlab_shell_worker_spec.rb @@ -9,29 +9,4 @@ worker.perform(:add_key, 'foo', 'bar') end end - - describe '#perform with batch_add_keys_in_db_starting_from' do - context 'when there are many keys in the DB' do - before do - @keys = [] - 10.times do - @keys << create(:key) - end - end - - it 'adds all the keys in the DB, starting from the given ID, to the authorized_keys file' do - Gitlab::Shell.new.remove_all_keys - - worker.perform(:batch_add_keys_in_db_starting_from, @keys[3].id) - - file = File.read(Rails.root.join('tmp/tests/.ssh/authorized_keys')) - expect(file.scan(/ssh-rsa/).count).to eq(7) - - expect(file).to_not include(Gitlab::Shell.strip_key(@keys[0].key)) - expect(file).to_not include(Gitlab::Shell.strip_key(@keys[2].key)) - expect(file).to include(Gitlab::Shell.strip_key(@keys[3].key)) - expect(file).to include(Gitlab::Shell.strip_key(@keys[9].key)) - end - end - end end -- GitLab From 405a75c4dc2b95ce07c06bf9e4103c0dde9778b2 Mon Sep 17 00:00:00 2001 From: Michael Kozono <mkozono@gmail.com> Date: Wed, 28 Jun 2017 13:34:29 -0700 Subject: [PATCH 09/18] Do read and delete work in background migration --- .../update_authorized_keys_file_since.rb | 2 +- .../update_authorized_keys_file_since_spec.rb | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/gitlab/background_migration/update_authorized_keys_file_since.rb b/lib/gitlab/background_migration/update_authorized_keys_file_since.rb index 9b9fb9efc2d136d3..6d257a532a2cb486 100644 --- a/lib/gitlab/background_migration/update_authorized_keys_file_since.rb +++ b/lib/gitlab/background_migration/update_authorized_keys_file_since.rb @@ -23,7 +23,7 @@ def add_keys_since(cutoff_datetime) end def remove_keys_not_found_in_db - GitlabShellWorker.perform_async(:remove_keys_not_found_in_db) + gitlab_shell.remove_keys_not_found_in_db end # Not added to Gitlab::Shell because I don't expect this to be used again diff --git a/spec/lib/gitlab/background_migration/update_authorized_keys_file_since_spec.rb b/spec/lib/gitlab/background_migration/update_authorized_keys_file_since_spec.rb index fdc31006b80fbed9..94a6b7987a9ea12e 100644 --- a/spec/lib/gitlab/background_migration/update_authorized_keys_file_since_spec.rb +++ b/spec/lib/gitlab/background_migration/update_authorized_keys_file_since_spec.rb @@ -27,8 +27,8 @@ end end - it 'queues a remove_keys_not_found_in_db call to GitlabShellWorker' do - expect(GitlabShellWorker).to receive(:perform_async).with(:remove_keys_not_found_in_db) + it 'calls remove_keys_not_found_in_db on Gitlab::Shell' do + expect(background_migration.gitlab_shell).to receive(:remove_keys_not_found_in_db) subject end end @@ -66,8 +66,8 @@ end describe '#remove_keys_not_found_in_db' do - it 'queues a rm_keys_not_in_db call to GitlabShellWorker' do - expect(GitlabShellWorker).to receive(:perform_async).with(:remove_keys_not_found_in_db) + it 'calls remove_keys_not_found_in_db on Gitlab::Shell' do + expect(background_migration.gitlab_shell).to receive(:remove_keys_not_found_in_db) background_migration.remove_keys_not_found_in_db end end -- GitLab From 56e7548bf321b829c62bbf4c80aab391b4b857c3 Mon Sep 17 00:00:00 2001 From: Michael Kozono <mkozono@gmail.com> Date: Wed, 28 Jun 2017 13:48:43 -0700 Subject: [PATCH 10/18] Fix Rubocop offenses --- .../update_authorized_keys_file_since.rb | 6 ++---- .../update_authorized_keys_file_since_spec.rb | 4 ++-- spec/lib/gitlab/shell_spec.rb | 2 +- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/lib/gitlab/background_migration/update_authorized_keys_file_since.rb b/lib/gitlab/background_migration/update_authorized_keys_file_since.rb index 6d257a532a2cb486..ec05d4f9822e6a8e 100644 --- a/lib/gitlab/background_migration/update_authorized_keys_file_since.rb +++ b/lib/gitlab/background_migration/update_authorized_keys_file_since.rb @@ -9,6 +9,8 @@ def shell_id end end + delegate :remove_keys_not_found_in_db, to: :gitlab_shell + def perform(cutoff_datetime) add_keys_since(cutoff_datetime) @@ -22,10 +24,6 @@ def add_keys_since(cutoff_datetime) end end - def remove_keys_not_found_in_db - gitlab_shell.remove_keys_not_found_in_db - end - # Not added to Gitlab::Shell because I don't expect this to be used again def batch_add_keys_in_db_starting_from(start_id) gitlab_shell.batch_add_keys do |adder| diff --git a/spec/lib/gitlab/background_migration/update_authorized_keys_file_since_spec.rb b/spec/lib/gitlab/background_migration/update_authorized_keys_file_since_spec.rb index 94a6b7987a9ea12e..ba3283785157727a 100644 --- a/spec/lib/gitlab/background_migration/update_authorized_keys_file_since_spec.rb +++ b/spec/lib/gitlab/background_migration/update_authorized_keys_file_since_spec.rb @@ -89,8 +89,8 @@ file = File.read(Rails.root.join('tmp/tests/.ssh/authorized_keys')) expect(file.scan(/ssh-rsa/).count).to eq(7) - expect(file).to_not include(Gitlab::Shell.strip_key(@keys[0].key)) - expect(file).to_not include(Gitlab::Shell.strip_key(@keys[2].key)) + expect(file).not_to include(Gitlab::Shell.strip_key(@keys[0].key)) + expect(file).not_to include(Gitlab::Shell.strip_key(@keys[2].key)) expect(file).to include(Gitlab::Shell.strip_key(@keys[3].key)) expect(file).to include(Gitlab::Shell.strip_key(@keys[9].key)) end diff --git a/spec/lib/gitlab/shell_spec.rb b/spec/lib/gitlab/shell_spec.rb index ff533e6ffecb8ec0..39b1ccf9554efed9 100644 --- a/spec/lib/gitlab/shell_spec.rb +++ b/spec/lib/gitlab/shell_spec.rb @@ -331,7 +331,7 @@ end end - expect(ids).to eq(["1\n", "2\n", "3\n", "4\n"]) + expect(ids).to eq(%W{1\n 2\n 3\n 4\n}) end end -- GitLab From ad12feafbd628b71647998081581a318afafe944 Mon Sep 17 00:00:00 2001 From: Michael Kozono <mkozono@gmail.com> Date: Wed, 28 Jun 2017 13:52:55 -0700 Subject: [PATCH 11/18] Add changelog entry --- .../fix-authorized-keys-enabled-default-2738.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 changelogs/unreleased-ee/fix-authorized-keys-enabled-default-2738.yml diff --git a/changelogs/unreleased-ee/fix-authorized-keys-enabled-default-2738.yml b/changelogs/unreleased-ee/fix-authorized-keys-enabled-default-2738.yml new file mode 100644 index 0000000000000000..6b1f7b67c7873a1e --- /dev/null +++ b/changelogs/unreleased-ee/fix-authorized-keys-enabled-default-2738.yml @@ -0,0 +1,4 @@ +--- +title: Fix locked and stale SSH keys file from 9.3.0 upgrade +merge_request: 2240 +author: -- GitLab From e4e7e9c32505f133a4e7600ee87be85dfd1e3cdb Mon Sep 17 00:00:00 2001 From: Michael Kozono <mkozono@gmail.com> Date: Wed, 28 Jun 2017 15:48:12 -0700 Subject: [PATCH 12/18] Inform the user of actions taken or not taken --- db/migrate/20170626202753_update_authorized_keys_file.rb | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/db/migrate/20170626202753_update_authorized_keys_file.rb b/db/migrate/20170626202753_update_authorized_keys_file.rb index 4e0119d1a5d5c62f..aea5fbb56a305442 100644 --- a/db/migrate/20170626202753_update_authorized_keys_file.rb +++ b/db/migrate/20170626202753_update_authorized_keys_file.rb @@ -17,6 +17,8 @@ class ApplicationSetting < ActiveRecord::Base def up if authorized_keys_file_in_use_and_stale? + say 'The authorized_keys file is in use, and may be stale. Now bringing it up-to-date in the background...' + # Update nil authorized_keys_enabled to true to ensure that Gitlab::Shell # key methods work properly for workers running 9.3.0 during the # migration. If the setting remained nil, the workers would not edit the @@ -24,6 +26,8 @@ def up update_nil_setting_to_true update_authorized_keys_file_since(DATETIME_9_3_0_RELEASED) + else + say 'The authorized_keys file does not need to be updated. Skipping...' end end @@ -62,7 +66,10 @@ def ran_broken_migration? # If the column is already fixed, then the migration wasn't run before now. default_value = Gitlab::Database.postgresql? ? 'true' : '1' - !column_exists?(:application_settings, :authorized_keys_enabled, :boolean, default: default_value, null: false) + column_has_no_default = !column_exists?(:application_settings, :authorized_keys_enabled, :boolean, default: default_value, null: false) + say "This GitLab installation was #{'never ' unless column_has_no_default}upgraded to exactly version 9.3.0." + + column_has_no_default end def false_negative_warning -- GitLab From 17fc5fc557b043b49b0bfef1a487e9b40e303d89 Mon Sep 17 00:00:00 2001 From: Michael Kozono <mkozono@gmail.com> Date: Wed, 28 Jun 2017 16:01:42 -0700 Subject: [PATCH 13/18] Prevent unnecessary `select`s and `remove_key`s --- lib/gitlab/shell.rb | 12 +++++++----- spec/lib/gitlab/shell_spec.rb | 37 +++++++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+), 5 deletions(-) diff --git a/lib/gitlab/shell.rb b/lib/gitlab/shell.rb index 528beeda6ae1b9d5..4253100ed8894d6e 100644 --- a/lib/gitlab/shell.rb +++ b/lib/gitlab/shell.rb @@ -248,12 +248,14 @@ def remove_keys_not_found_in_db return unless self.authorized_keys_enabled? batch_read_key_ids do |ids_in_file| + ids_in_file.uniq! keys_in_db = Key.where(id: ids_in_file) - if ids_in_file.size > keys_in_db.count - ids_to_remove = ids_in_file - keys_in_db.pluck(:id) - ids_to_remove.each do |id| - remove_key("key-#{id}") - end + + return unless ids_in_file.size > keys_in_db.count # optimization + + ids_to_remove = ids_in_file - keys_in_db.pluck(:id) + ids_to_remove.each do |id| + remove_key("key-#{id}") end end end diff --git a/spec/lib/gitlab/shell_spec.rb b/spec/lib/gitlab/shell_spec.rb index 39b1ccf9554efed9..0754990c6f7c1795 100644 --- a/spec/lib/gitlab/shell_spec.rb +++ b/spec/lib/gitlab/shell_spec.rb @@ -289,6 +289,43 @@ expect(find_in_authorized_keys_file(@another_key.id)).to be_truthy end end + + context 'when keys there are duplicate keys in the file that are not in the DB' do + before do + gitlab_shell.remove_all_keys + gitlab_shell.add_key('key-1234', 'ssh-rsa ASDFASDF') + gitlab_shell.add_key('key-1234', 'ssh-rsa ASDFASDF') + end + + it 'removes the keys' do + expect(find_in_authorized_keys_file(1234)).to be_truthy + gitlab_shell.remove_keys_not_found_in_db + expect(find_in_authorized_keys_file(1234)).to be_falsey + end + + it 'does not run remove more than once per key (in a batch)' do + expect(gitlab_shell).to receive(:remove_key).with('key-1234').once + gitlab_shell.remove_keys_not_found_in_db + end + end + + context 'when keys there are duplicate keys in the file that ARE in the DB' do + before do + gitlab_shell.remove_all_keys + @key = create(:key) + gitlab_shell.add_key(@key.shell_id, @key.key) + end + + it 'does not remove the key' do + gitlab_shell.remove_keys_not_found_in_db + expect(find_in_authorized_keys_file(@key.id)).to be_truthy + end + + it 'does not need to run a SELECT query for that batch, on account of that key' do + expect_any_instance_of(ActiveRecord::Relation).not_to receive(:pluck) + gitlab_shell.remove_keys_not_found_in_db + end + end end describe '#batch_read_key_ids' do -- GitLab From 4949edce6dfbfbdb145f862d0597bd9c2f1f5190 Mon Sep 17 00:00:00 2001 From: Michael Kozono <mkozono@gmail.com> Date: Wed, 28 Jun 2017 16:09:19 -0700 Subject: [PATCH 14/18] Add logs for action taken --- .../background_migration/update_authorized_keys_file_since.rb | 2 ++ lib/gitlab/shell.rb | 3 +++ 2 files changed, 5 insertions(+) diff --git a/lib/gitlab/background_migration/update_authorized_keys_file_since.rb b/lib/gitlab/background_migration/update_authorized_keys_file_since.rb index ec05d4f9822e6a8e..be5f24d2cefec487 100644 --- a/lib/gitlab/background_migration/update_authorized_keys_file_since.rb +++ b/lib/gitlab/background_migration/update_authorized_keys_file_since.rb @@ -26,6 +26,8 @@ def add_keys_since(cutoff_datetime) # Not added to Gitlab::Shell because I don't expect this to be used again def batch_add_keys_in_db_starting_from(start_id) + Rails.logger.info("Adding all keys starting from ID: #{start_id}") + gitlab_shell.batch_add_keys do |adder| Key.find_each(start: start_id, batch_size: 1000) do |key| adder.add_key(key.shell_id, key.key) diff --git a/lib/gitlab/shell.rb b/lib/gitlab/shell.rb index 4253100ed8894d6e..d6147c9b6e80f64a 100644 --- a/lib/gitlab/shell.rb +++ b/lib/gitlab/shell.rb @@ -247,6 +247,8 @@ def remove_all_keys def remove_keys_not_found_in_db return unless self.authorized_keys_enabled? + Rails.logger.info("Removing keys not found in DB") + batch_read_key_ids do |ids_in_file| ids_in_file.uniq! keys_in_db = Key.where(id: ids_in_file) @@ -255,6 +257,7 @@ def remove_keys_not_found_in_db ids_to_remove = ids_in_file - keys_in_db.pluck(:id) ids_to_remove.each do |id| + Rails.logger.info("Removing key-#{id} not found in DB") remove_key("key-#{id}") end end -- GitLab From 9756deec94cf49d1bd15f6e3a537794178fb1a71 Mon Sep 17 00:00:00 2001 From: Michael Kozono <mkozono@gmail.com> Date: Wed, 28 Jun 2017 17:04:46 -0700 Subject: [PATCH 15/18] Fix optimization --- lib/gitlab/shell.rb | 2 +- spec/lib/gitlab/shell_spec.rb | 16 ++++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/lib/gitlab/shell.rb b/lib/gitlab/shell.rb index d6147c9b6e80f64a..91a21b73724f7c69 100644 --- a/lib/gitlab/shell.rb +++ b/lib/gitlab/shell.rb @@ -253,7 +253,7 @@ def remove_keys_not_found_in_db ids_in_file.uniq! keys_in_db = Key.where(id: ids_in_file) - return unless ids_in_file.size > keys_in_db.count # optimization + next unless ids_in_file.size > keys_in_db.count # optimization ids_to_remove = ids_in_file - keys_in_db.pluck(:id) ids_to_remove.each do |id| diff --git a/spec/lib/gitlab/shell_spec.rb b/spec/lib/gitlab/shell_spec.rb index 0754990c6f7c1795..efc836da7d426128 100644 --- a/spec/lib/gitlab/shell_spec.rb +++ b/spec/lib/gitlab/shell_spec.rb @@ -326,6 +326,22 @@ gitlab_shell.remove_keys_not_found_in_db end end + + unless ENV['CI'] # Skip in CI, it takes 1 minute + context 'when the first batch can be skipped, but the next batch has keys that are not in the DB' do + before do + gitlab_shell.remove_all_keys + 100.times { |i| create(:key) } # first batch is all in the DB + gitlab_shell.add_key('key-1234', 'ssh-rsa ASDFASDF') + end + + it 'removes the keys not in the DB' do + expect(find_in_authorized_keys_file(1234)).to be_truthy + gitlab_shell.remove_keys_not_found_in_db + expect(find_in_authorized_keys_file(1234)).to be_falsey + end + end + end end describe '#batch_read_key_ids' do -- GitLab From 07e79989cefd66ee94299f97d5a59bc6d82dcd3f Mon Sep 17 00:00:00 2001 From: Michael Kozono <mkozono@gmail.com> Date: Wed, 28 Jun 2017 17:11:56 -0700 Subject: [PATCH 16/18] Reuse `Gitlab::ShellAdapter` --- .../update_authorized_keys_file_since.rb | 6 ++---- .../update_authorized_keys_file_since_spec.rb | 4 ++-- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/lib/gitlab/background_migration/update_authorized_keys_file_since.rb b/lib/gitlab/background_migration/update_authorized_keys_file_since.rb index be5f24d2cefec487..eed5179129681401 100644 --- a/lib/gitlab/background_migration/update_authorized_keys_file_since.rb +++ b/lib/gitlab/background_migration/update_authorized_keys_file_since.rb @@ -1,6 +1,8 @@ module Gitlab module BackgroundMigration class UpdateAuthorizedKeysFileSince + include Gitlab::ShellAdapter + class Key < ActiveRecord::Base self.table_name = 'keys' @@ -34,10 +36,6 @@ def batch_add_keys_in_db_starting_from(start_id) end end end - - def gitlab_shell - @gitlab_shell ||= Gitlab::Shell.new - end end end end diff --git a/spec/lib/gitlab/background_migration/update_authorized_keys_file_since_spec.rb b/spec/lib/gitlab/background_migration/update_authorized_keys_file_since_spec.rb index ba3283785157727a..a94900c4493c8613 100644 --- a/spec/lib/gitlab/background_migration/update_authorized_keys_file_since_spec.rb +++ b/spec/lib/gitlab/background_migration/update_authorized_keys_file_since_spec.rb @@ -28,7 +28,7 @@ end it 'calls remove_keys_not_found_in_db on Gitlab::Shell' do - expect(background_migration.gitlab_shell).to receive(:remove_keys_not_found_in_db) + expect_any_instance_of(Gitlab::Shell).to receive(:remove_keys_not_found_in_db) subject end end @@ -67,7 +67,7 @@ describe '#remove_keys_not_found_in_db' do it 'calls remove_keys_not_found_in_db on Gitlab::Shell' do - expect(background_migration.gitlab_shell).to receive(:remove_keys_not_found_in_db) + expect_any_instance_of(Gitlab::Shell).to receive(:remove_keys_not_found_in_db) background_migration.remove_keys_not_found_in_db end end -- GitLab From 5ea374fecaadccfb6a0db42b1be41a256d8531d4 Mon Sep 17 00:00:00 2001 From: Michael Kozono <mkozono@gmail.com> Date: Wed, 28 Jun 2017 17:13:44 -0700 Subject: [PATCH 17/18] Guarantee the earliest key --- .../background_migration/update_authorized_keys_file_since.rb | 2 +- .../update_authorized_keys_file_since_spec.rb | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/gitlab/background_migration/update_authorized_keys_file_since.rb b/lib/gitlab/background_migration/update_authorized_keys_file_since.rb index eed5179129681401..134e583b78f17b7a 100644 --- a/lib/gitlab/background_migration/update_authorized_keys_file_since.rb +++ b/lib/gitlab/background_migration/update_authorized_keys_file_since.rb @@ -20,7 +20,7 @@ def perform(cutoff_datetime) end def add_keys_since(cutoff_datetime) - start_key = Key.select(:id).where("created_at >= ?", cutoff_datetime).take + start_key = Key.select(:id).where("created_at >= ?", cutoff_datetime).order('id ASC').take if start_key batch_add_keys_in_db_starting_from(start_key.id) end diff --git a/spec/lib/gitlab/background_migration/update_authorized_keys_file_since_spec.rb b/spec/lib/gitlab/background_migration/update_authorized_keys_file_since_spec.rb index a94900c4493c8613..be4e6f7734ed078b 100644 --- a/spec/lib/gitlab/background_migration/update_authorized_keys_file_since_spec.rb +++ b/spec/lib/gitlab/background_migration/update_authorized_keys_file_since_spec.rb @@ -49,6 +49,7 @@ before do Timecop.travel 1.day.from_now @key = create(:key) + create(:key) # other key end it 'calls batch_add_keys_in_db_starting_from with the start key ID' do -- GitLab From e1289a64fadf77dc948ae596df0936c472ff6787 Mon Sep 17 00:00:00 2001 From: Michael Kozono <mkozono@gmail.com> Date: Wed, 28 Jun 2017 17:25:37 -0700 Subject: [PATCH 18/18] Fix migration spec for MySQL --- spec/migrations/update_authorized_keys_file_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/migrations/update_authorized_keys_file_spec.rb b/spec/migrations/update_authorized_keys_file_spec.rb index 15b85366ac1a1f1c..43642d2f25acb72e 100644 --- a/spec/migrations/update_authorized_keys_file_spec.rb +++ b/spec/migrations/update_authorized_keys_file_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' require Rails.root.join('db', 'migrate', '20170626202753_update_authorized_keys_file.rb') -describe UpdateAuthorizedKeysFile do +describe UpdateAuthorizedKeysFile, :migration do let(:migration) { described_class.new } describe '#up' do @@ -105,7 +105,7 @@ context 'when authorized_keys_enabled is explicitly false' do before do - ApplicationSetting.first.update(authorized_keys_enabled: false) + ApplicationSetting.first.update!(authorized_keys_enabled: false) end it { is_expected.to be_falsey } -- GitLab