From 78b353b2bb9bdd20d3f9385649e851087f1e845a Mon Sep 17 00:00:00 2001 From: Joel Carpenter <66389984+AldosAC@users.noreply.github.com> Date: Tue, 28 Mar 2023 07:40:10 -0700 Subject: [PATCH] feat: STFT-57 - Prompt for update on linter version mismatch (#3) * Adds pre-commit config validation and removes pycache files * Adds diff output for mismatched configs * Finishes implementing update check during scan Also updates the update command to re-generate the config so it matches expectations. * Adds unit tests * Fixes secureli test in build pipeline * Makes declining update a non-halting outcome for scan Also corrects 2 bugs: - Incorrectly processing case with missing/added linter and version mismatch at same time - Moved config validation after upgrade check * Updates tests for adjusted functionality * Switches to update instead of upgrade command for config mismatch fix * Renames hash variable and removes unused variables --- .coveragerc | 13 + secureli/__pycache__/__init__.cpython-39.pyc | Bin 154 -> 0 bytes secureli/__pycache__/container.cpython-39.pyc | Bin 3355 -> 0 bytes secureli/__pycache__/main.cpython-39.pyc | Bin 2935 -> 0 bytes secureli/__pycache__/patterns.cpython-39.pyc | Bin 1070 -> 0 bytes secureli/__pycache__/settings.cpython-39.pyc | Bin 4293 -> 0 bytes .../__pycache__/__init__.cpython-39.pyc | Bin 167 -> 0 bytes .../__pycache__/echo.cpython-39.pyc | Bin 4710 -> 0 bytes .../__pycache__/lexer_guesser.cpython-39.pyc | Bin 1162 -> 0 bytes .../__pycache__/pre_commit.cpython-39.pyc | Bin 18310 -> 0 bytes .../pre_commit_hooks.cpython-39.pyc | Bin 4337 -> 0 bytes secureli/abstractions/pre_commit.py | 234 +++++++++++++++++- .../__pycache__/__init__.cpython-39.pyc | Bin 162 -> 0 bytes .../actions/__pycache__/action.cpython-39.pyc | Bin 7017 -> 0 bytes .../__pycache__/initializer.cpython-39.pyc | Bin 1591 -> 0 bytes .../actions/__pycache__/scan.cpython-39.pyc | Bin 2467 -> 0 bytes .../actions/__pycache__/setup.cpython-39.pyc | Bin 1117 -> 0 bytes .../actions/__pycache__/yeti.cpython-39.pyc | Bin 1197 -> 0 bytes secureli/actions/action.py | 46 +++- secureli/actions/update.py | 6 +- secureli/container.py | 2 + .../__pycache__/__init__.cpython-39.pyc | Bin 167 -> 0 bytes .../__pycache__/repo_files.cpython-39.pyc | Bin 4200 -> 0 bytes .../secureli_config.cpython-39.pyc | Bin 2207 -> 0 bytes .../__pycache__/__init__.cpython-39.pyc | Bin 234 -> 0 bytes .../__pycache__/read_resource.cpython-39.pyc | Bin 1024 -> 0 bytes .../__pycache__/slugify.cpython-39.pyc | Bin 1292 -> 0 bytes .../__pycache__/__init__.cpython-39.pyc | Bin 163 -> 0 bytes .../__pycache__/git_ignore.cpython-39.pyc | Bin 3870 -> 0 bytes .../language_analyzer.cpython-39.pyc | Bin 4044 -> 0 bytes .../language_support.cpython-39.pyc | Bin 2502 -> 0 bytes .../__pycache__/logging.cpython-39.pyc | Bin 4334 -> 0 bytes .../__pycache__/scanner.cpython-39.pyc | Bin 2085 -> 0 bytes .../secureli_ignore.cpython-39.pyc | Bin 1668 -> 0 bytes secureli/services/updater.py | 42 +++- .../__pycache__/__init__.cpython-39.pyc | Bin 164 -> 0 bytes .../__pycache__/git_meta.cpython-39.pyc | Bin 1330 -> 0 bytes .../__pycache__/patterns.cpython-39.pyc | Bin 1094 -> 0 bytes .../__pycache__/secureli_meta.cpython-39.pyc | Bin 445 -> 0 bytes tests/abstractions/test_pre_commit.py | 155 +++++++++++- tests/actions/test_action.py | 51 ++++ tests/actions/test_initializer_action.py | 2 + tests/actions/test_scan_action.py | 4 +- tests/actions/test_update_action.py | 30 ++- tests/application/test_main.py | 2 +- tests/services/test_updater_service.py | 62 +++-- 46 files changed, 601 insertions(+), 48 deletions(-) create mode 100644 .coveragerc delete mode 100644 secureli/__pycache__/__init__.cpython-39.pyc delete mode 100644 secureli/__pycache__/container.cpython-39.pyc delete mode 100644 secureli/__pycache__/main.cpython-39.pyc delete mode 100644 secureli/__pycache__/patterns.cpython-39.pyc delete mode 100644 secureli/__pycache__/settings.cpython-39.pyc delete mode 100644 secureli/abstractions/__pycache__/__init__.cpython-39.pyc delete mode 100644 secureli/abstractions/__pycache__/echo.cpython-39.pyc delete mode 100644 secureli/abstractions/__pycache__/lexer_guesser.cpython-39.pyc delete mode 100644 secureli/abstractions/__pycache__/pre_commit.cpython-39.pyc delete mode 100644 secureli/abstractions/__pycache__/pre_commit_hooks.cpython-39.pyc delete mode 100644 secureli/actions/__pycache__/__init__.cpython-39.pyc delete mode 100644 secureli/actions/__pycache__/action.cpython-39.pyc delete mode 100644 secureli/actions/__pycache__/initializer.cpython-39.pyc delete mode 100644 secureli/actions/__pycache__/scan.cpython-39.pyc delete mode 100644 secureli/actions/__pycache__/setup.cpython-39.pyc delete mode 100644 secureli/actions/__pycache__/yeti.cpython-39.pyc delete mode 100644 secureli/repositories/__pycache__/__init__.cpython-39.pyc delete mode 100644 secureli/repositories/__pycache__/repo_files.cpython-39.pyc delete mode 100644 secureli/repositories/__pycache__/secureli_config.cpython-39.pyc delete mode 100644 secureli/resources/__pycache__/__init__.cpython-39.pyc delete mode 100644 secureli/resources/__pycache__/read_resource.cpython-39.pyc delete mode 100644 secureli/resources/__pycache__/slugify.cpython-39.pyc delete mode 100644 secureli/services/__pycache__/__init__.cpython-39.pyc delete mode 100644 secureli/services/__pycache__/git_ignore.cpython-39.pyc delete mode 100644 secureli/services/__pycache__/language_analyzer.cpython-39.pyc delete mode 100644 secureli/services/__pycache__/language_support.cpython-39.pyc delete mode 100644 secureli/services/__pycache__/logging.cpython-39.pyc delete mode 100644 secureli/services/__pycache__/scanner.cpython-39.pyc delete mode 100644 secureli/services/__pycache__/secureli_ignore.cpython-39.pyc delete mode 100644 secureli/utilities/__pycache__/__init__.cpython-39.pyc delete mode 100644 secureli/utilities/__pycache__/git_meta.cpython-39.pyc delete mode 100644 secureli/utilities/__pycache__/patterns.cpython-39.pyc delete mode 100644 secureli/utilities/__pycache__/secureli_meta.cpython-39.pyc diff --git a/.coveragerc b/.coveragerc new file mode 100644 index 00000000..d0f7789b --- /dev/null +++ b/.coveragerc @@ -0,0 +1,13 @@ +[run] +branch = True +source = secureli + +[report] +fail_under = 90 +omit = + tests/* + */__init__.py +exclude_lines: + pragma: no cover + @abstractmethod + if __name__ == "__main__": diff --git a/secureli/__pycache__/__init__.cpython-39.pyc b/secureli/__pycache__/__init__.cpython-39.pyc deleted file mode 100644 index 48e967aec9059448f9334277ab37cb5d58aa23e0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 154 zcmYe~<>g`kf_;bnCxht6AOaaM0yz#qT+9L_QW%06G#UL?G8BP?5yUTh{m|mnqGJ8x z#NybRBtcSq=s`)A6Om4>&864uLoYMR=C8L$gc5r*kK*A>%1HGSdVOnFNZ5^h3p1j4cFKj*-hRL*V#JR zExr+MvQ4s=_?2*rZIRvPSHo?#O?HRB7Y^8f>@L3+?yw!Qd;EHMgWVu|ncoa|*)G{D z{8o6I-8PLUhQI2s{b=}WU>*)%mDwG?zi$lJf6==%EGcIkM~N3iAf&2KMSK+aXbw$P z|I5h~#5d!E7@d_rolHWA68RL4K|Gm(lpqKf@Hh^`AbIpcCc+yhK^!68dqmdPFonp6 zXdHm`F!eo3FQ9rpJ5t*8*q@?kUNDX*`(((PRP9OTnF7K+**)AglAazB`B7J|qctC&&6^7C&`#9^y|P zBZt9Yrf;y4Z?ZC2uPbK;v%xy7Ld`Gv<0<&Y79{ zUFg9ww4vKKUYV~u*C@7wvFnF7poYEMhc|uuw8D1hqqAF-a~pQymR~(BvmyC+U=yzSHT3V2|31dI z{5tyg$REM3@8DQ_ zixYH{AcT09xDv1o_);}cZl23y4~u}ff}B&!Qvt3{4|G<_P%Inm1^6J2|E`<~CZq%F zsv=OIG%=}KT-OYDk`)aM7SF0Sj!e?UF|>AvrnrH_4sB%tmLApRnKE53lcwtAkW3}1 zsxsA`xT$Acol(&cxMOv_ovvP!*rgCpC5tm`cvPWWJTocY*F#$39{y1x0b+P5dI!;G z!w0J7(z&>X$-^=M% zZ)vNdo9nu0={{^UQg zshL)#R$eMOCCeZA8R zo1VDTdSmOJafCdFo=4vO7}PdHb(h@@(8d1k>-VzBBLX;f$AC_8CQ54Lq7y1~pc=tJ$a!F^OH>%m+W0Hb=8sO)6KtIf}ic z_m!kVriw*o+LWV0uT-BFwmna|AWfkqt9J5HE@o!e07+kMT#}d;eWA5|S$uwbXq8_U z&o)qy-mhKke~x73lZv$J&e2g`ymc*SD__Hyd-@7E!Z+tkj>O|a+^O}Yo=)vSdbNCo RuYQKqb+J=wn*YbQ{5z-=?=%1a diff --git a/secureli/__pycache__/main.cpython-39.pyc b/secureli/__pycache__/main.cpython-39.pyc deleted file mode 100644 index 66a52ddaff702338e0494197bd8410d58a4e9c4e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2935 zcmbVOOK%%T5}x4>(E?2;!BKg#p*VPd-RT|A{^9&)_wOV9z;a5x`s3LsGJR%MdeDHT|mkzUr^Z+U=$X z*T4VsGWiXxU(q>#HNg22Zu2kS^SH--<)uFLeY7j8k_I$zcA!EU!naU`X_Z#px2lTL z8m&1yQuVY!8_uq&X1YRGoLyI~v`yR2Zm3SWN>`oTR2R}Uy5@VLAy)ayk-;Ug!8iHVap=5@{1U&6-k>6`imQD4d4*r$SAVL)sB7m&E&GY8s2#k`ukp9}JM&or zPWW~HuA3!*S!yB>?_Boyd(Q*@KEL7SB4YF43Pk?Fu`k*O*9Y+AH=q0bL*8}m@19%h zBmObJ^V_c1d*Xfm$zkPZU;iRDk32vs*z>x7`!6!q^=IKDW=HU@e^XeQ zXG}c=Cn_EMyqB=-LCyu(D|^C@i@OQ-1Gll0XO_uKXmF}KxysRKYr%M|g~`V{!BKS> zWo0%r(}pOd%7^`wcYV51ItfXUP7=!czBJvuP?y+ z>uIgm0}h3qR(g7R|DH5j44LMToq&vuWl9N7Amo7@jy1!QxJBfEuxxS*9?%i$z!GK% zBbj)*7y~(nh|RRx>rI4#*(cLa_t=Eo1n(xXIRVJ@gh1$N0mH{OPhs)|mYa~L(vI@6 zg}I8ceNmZJU4*j$h9(sZe}17|h5NN4IDrD-{*nL8d*+{_0+mJZts~=}ut>LJ*>DVm zL)ef5(jb|VQvm^qUXrJ&1a9;EuxwYzJ&-%dwF71jxSMe1_kmU2f;i~<*+e>KZvW8L zvv0t3k!y@vuj6A$u3iOq7GlpEU>TR>y50hN8ufa(3jo1%`T#eni(nw3n#d#{--H@w z`-m1`IuHCqB#(;$8!Ou-5Hp}k$`P_0j)C;2tG%8nL?Q<==|MGN4cO_IyAqSfDU4e! z2WA)D946%cJu(2^aCvC1bV}z<2G-5F%$&A*d&0K#f5eRx1;YW808*b(uV|A zPQxXEBZv1Rp@D`Ji6P>jg^~$Gn~X$qNb-RT8qEJ4M*wqkgFkRSKlq6HzAlMsVfM>g3X3=&Um~CJ>TFF``m$slE+K>GT5Mm(MWPGvKKzB zCMco_oSMrEZ3M)Dfz`19vh4xO^%yDC?i0o z1P4unXiy&UWCJ8iB_(IIQ& z%QHvUuwi@I!1j&Ri)lqN%60wKk&6@FX#BfJ*OnvU&~V4S#y^R)kxuhyVZp diff --git a/secureli/__pycache__/patterns.cpython-39.pyc b/secureli/__pycache__/patterns.cpython-39.pyc deleted file mode 100644 index 0c04e7fd7fcc2b97cab9c7a29e5d18416191c549..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1070 zcmZWozi-n(6uvuO(kP@-TL}RY!wn3LR8qkhRS2ofWhh-DAiDTok^}qf-koVvIaB#B zN~Lz>zhU;u)RnOdJUekxiIeWh@7MQz@7b1{n{9&U&xr~@HVFA$IuB2P&Re{07mXr{ zMkJ;KN(nFso)Ms>RqO{-f#To!fUCwa={E1MnsjN=dY^io7#Ve0(MqNFV2vx97JOz+ zNXx3_L2|Nz_Ykj3(d5Ln6g?&$pDw9lUzvTL6NPlg7`dUhWKKQOVxC^oOZJX@5_9H* z8=ljgp(w6+&aTKcyA8_70Xcn**@kN5^p?#7)y#t{qBgFBInOzYZpmLLs4eguChd~<6>cpdYbeJN$UQ|v9&yT9hqi)NbBA)e^|u{# xXouPz%pG+bMbpnxT(4#57d+B9WclM)`AW|Nvt8D-4&p>*5xglk=gPKD@DCO#J39aX diff --git a/secureli/__pycache__/settings.cpython-39.pyc b/secureli/__pycache__/settings.cpython-39.pyc deleted file mode 100644 index da108075461fa165357652cbb8481441a08974da..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4293 zcma)9TW=h<6`t$v>}qwjY{f=VOq#aoqV_sP5;PBPVn?+d1FmdXb(1a%Oh-fRYUH_W zNRBKm~bNDle&<2h&I#l^0L z-{1cJ-{gZO%lZd3PXBx~ZllO=AehDM%*ySdZByOJoZKC{rtW55?hpOEHEfx-m$ip& z^zpJ_7~tQ}+IeT#v8}PiT7R{N-TM}6v*4x00^Z(RICh6UXgaJ5O_win``8;UL9@Vm z(Da%<%g`*cC1{o!%?dQjYz3N?MzaddDqDkQt3fBF+gAVDKe2zSZ)@*vQRVdiI99uTN4s~5LmfPtsI)BN zYzr3uUdl7p?@ztKq!>f)O~z9nl2c=xjv#mP2~;W_jfU<-GHCM22PB{EL&p0l)k~`9 z80-!9%N#Z{*CVjdqvBfK+` z>6Gh@bX=5zvs%Ru6fY#0A@yZ`kYp9(wP6w~#YG{v`inYT%jpReDlf8r;-bKQ^qpcjR!k~$4$u-Kr|8M9#2%$%F@%78tTF1P_!}2Cnz$2 z=-QsQW?!+5_6;uB=y996eg|U41!4koh8}Z=KJ$hx=Hpyjx^p+#Eg$lIo@xK*cRzi4 zUwaR>?mg1p=XaiLY5(q%Cy$wr3urePiu&9{&*iXKSDFnEQJz=77mwGtiW1RhjuAK65k`S zMq*BYQ)s*m1Km>TLC{GG(p-$h_hCJY#Mv9&oaV;ExENRQm~U5;Nh#DElI?E`2^R!~ z46|mUFox?rl&M68g|eQuxv$qjv>Kg_q|9@4h*X;MvQiP4T^0;?Ft`{m+Lu@*aUoRi z!1@Ks2~?!H7^+=Z&qBq0=D@kNn9Dpvoq%_^ch!R1^Nrh^S-SkR1+H&CX5lqU8?LO^R@ge=*x%geiK5NvB(|VVhlun3s59{Q!pFqDXReM8CkkhDVV8Vr1{Z zdJ(cs*k}_<22~0%twu|z0__@W)&g#AeS#ZBl?FGe{vX`jN@UG-+y%-~Um`*ZE+wJ@ zX`+-kxA1K+`!P0D!$lhx*GZgrTl+9PLz(;Cfc!3LF9z#lSkHn5ILLFE7z{X!_c9Te zv>im|uF(Rz<}RtpyL=E=_*lUd=VN#RwCe;i_&qMSEtQCr+@kBu2NjhYF!R+4Xmf)%q zMMAM`_9KRqGu}jzzkxWi_N+bog{>TBAKNdSBj=U-6~bO>ZClDaa+q^uBcpNu_}v$- z@?W)%+^_5x-jVmYJ~phZXvZ-PI#d`dcW?-c*LU&%j^5%J@gXqLkw4t0PRMqgb%K>2W^kE$71B2C#OMS zSznp$C!+c0{eK6Wq+n*X1DhVtcIxNfEzhyLwzvh`#q1}CeU|;l7MSnBp^__ZqlHfs za<6E0(CWe^w@hZbfL4z$poNU|K0atz8y^hqV`sR?0yvM34(8XPFz542B$czRhJ*%Z z;YgHu7$eurGDLmkZ(%tK34*XbD0w~HsZ^Mk$WkX=#^QRjGD-(ySQOEPIC4aBXl+|t26YM)-n7;!@cF%E(e?D22cG;jc$CDP|h&> zQJu;riX?x3WGm~)IdVso71{V;GAGsdw3}p7FQ!FGk-1?-kEk|pt=D-rPjK(%+OvdN zO_1D|c`A{S8^X(1^lI~&8#EmG*Yxc(OCP*3YOCKm>!LrzM0&w|;pANHmP}B@Ikf=8 zT|?VejmTf<)#O4zP{*;wXQ*!Vm(EUX@-s@F1$pJ#KT*CVD47=Tkho2PJdn6Uf+jle z#o}i${0YTi4aem#+2(PKrN04E-~(1T=%U+n3$t{mxxVUfg4-_Wv`nDZ!Q_y|g-R3g z5$TL8y+i|hpT?5U5H$IH?MdlEnnbZ)vk6+p7~4Z}~d7Reu3}Og>do gj@q+(_KHJSyypfVu65ViYXP1G)ViMC{ZDWGe{?za7XSbN diff --git a/secureli/abstractions/__pycache__/__init__.cpython-39.pyc b/secureli/abstractions/__pycache__/__init__.cpython-39.pyc deleted file mode 100644 index f454ddd53de0275805d18af065e4f625678efcfc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 167 zcmYe~<>g`kf_;bnCxht6AOaaM0yz#qT+9L_QW%06G#UL?G8BP?5yUSa{m|mnqGJ8x z#Nyb&oYI_>E4d}rx1zFJ zKG2xWN>4Oa;uUT^w%jUa%B+H!3fCW-ZVlrqt6^N@HH?>-^^w+C`U-n#4MUncyL)oQ zZ)dsigS^M{Zo(S6w3>0hhu_L!nukg3N3DQ9n0<5%?&8fh(QwVxndTZycTHxv7BgL& zS#F8hZkd(b3M;!+R&i^rip^`%-b;8B!ZNUI(7xh(hm z6CUTjw1R;jOZ%c5=DgKdk`>R3{T}x`S@XPJ!uk>Q>z?=bz8{TO?A`l2dml*isQI3> z500A6mMneJynp}jk+gR2KWxhKhdT$&*71(C_CDEZN&C_J_l}!anz)Svspi@HcOGV3 zWOp(@3%dSR+s`=$Jm?FaisY0B`FIqC)6PVh&|7wg2i;^V9f%TmR!CcG)X}YLe`<9> z!Su;p`sANzW);jWW_+$QlUYwJ*XAYm18(s$ue_r1Dl30(vI?s{u^;Ph4Qnr}wW?aH zVQq=k)!GuaI(ubqewD3Zei^&oz^?1;CG5H~9^GWOFj{3c%z9g{HgVdW zu`iL^yb33&Ie40J;HOzXA_Y2mmpiEl<2;N{oU|{}B;(np>9e9QZYv0vp6w?B{7e zciKKJCb8lrI3&w6Cux&KGsp&q3PvBReYrRf0|@>WOX4?kCnMp{BJPlJNo*%Mhpg&Y zaA>}%xQ4mY^+gYC`@-?tVHDCNbAwg zQLAB1XT)nXZ8wjO4v!j^Tvi71ch@a42 z1(d`Oi9KK&#$oPxG|C7MnqE_Xmm71wvo#@Y?O&M)1U$7h&F2lzx^aK?tv9a5sc3 zTp5eFuCOdjfgi&6XoOcrN-Ad+4>G7MB8oDPlwYG$f`pLxT*k+#D{4uU+d%UPVpPZAda4hmx`-iiwiR4+5Sd%!K(Ul#y?4kCX?GAl4y5-sA*hO(1-A zXg6Aeji+CZjcm>s`Pb0;ynV}KKI8=>3s)~+p11IrW&u}}p(tGa2%VvJs%14o*HHTc zF=%KE%`Z)4Bnof-{&mwXK#l@Q;k@I({+)2QH3D*0=-e0Fi4j3@TlCWuc0y?1gwiAQ zlpa-hb%2?S!_5-sb;QQki_f3qRsTo$HVp9wneq-bThz>hRtfietYnldXqA#~8H>PG zR4-)I_5Za|3wupX`28@@PsuF*)j$?`yF!k#Pg&XL<~;0(sG+O4x{w4E&~vcSEc8wR zz1%oOw$03^6Zl!r{!ysy0bO^KWa~NiT}a`IsmqNnl7h##VME>avk1P9kk;{)F}-Gx zVf?(%5a~d?ga#DTD{0=R75_s}jeOdM&h$l}R#Y#lE%66z)7lq*#1AC>8Gc5#RFdAJ z;h(6P6R}L`53ZHJVQp5(g?KuqYZ$HlhK_U$KlH8%l!2O{Nx0k^lvai84$y zXr69PLB?0L0m`8yiAJ*hmL`8k4FO+lQ}btPrc$aNrC5RjHfcP&i3Vjyd8K6Nx2t$- z8_OH@m8zl$uz2mEjp@lgMzdIYe5Q3qxU$S(>|q)NEB&j|V9igoevA<#G#Ef+BDjPpKM+gMrEol)t?|=^%cLLoK)tDV$za zpyk?Qb+>(~Q>6`I^%f=52u|=jWuG@eiqyU8c|n9E(ep;?S4t;>6Jn(@DgAQ2IOj}K zD}DirYeAWmPVXLlN{QOg5VfIks>KG=KDBcF6nXy88k$UhYJRHc`WNO^v(pU5~{c?5YG{jE) z3Iq#TUvUuf@?4{6HN<}hnDNwvI?dKGq%Dv0D_KY7cHxajxb>R&Mkqi34fUput%Rb5 z1n)$>H>LuO6PO9$>T^I7?-0AcP@{}ULMnWTcd19GRolwtu~`HBGh$*|CB+VrC@KDm zUK?*lRSb}2t(zORpxH)UntnSFWPOEWs2%@J5mc1O66^ur9YI^2Q;DMj4uMs1m^pC) dWm$P=^nZ?baTjY@9nG3v*Xu^bTwhu{{01=?mYM(n diff --git a/secureli/abstractions/__pycache__/lexer_guesser.cpython-39.pyc b/secureli/abstractions/__pycache__/lexer_guesser.cpython-39.pyc deleted file mode 100644 index 11904f81b374bbc41e00b605ed2e3ed88dc3b193..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1162 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 diff --git a/secureli/abstractions/__pycache__/pre_commit_hooks.cpython-39.pyc b/secureli/abstractions/__pycache__/pre_commit_hooks.cpython-39.pyc deleted file mode 100644 index a8cda5d4d1e6ed3519533b6257e9ebed5ef0f402..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4337 zcmeHK-Hsbc74GV8ciTOl%w)(in}ARuSOqT=XF)V$omcX%u*5JxNK_hE4Af@||=_&knk64=Th8?^VuxXxTu7t#hm7aq*BatI?g54Q$Z)mQ;|`8JY`x?o^YDwnyMm>h2W#t zZrP2ZjLk$8l|dBEa$cl(-;bg{6fCX(@uP_6aTL9_WFPc4!@nNf{!9s}ZY!qZDH|Sx ze3*#1kRnaiH*D<~bsLn9Dy5@oo63B5e5oLnPW8Jsj8#H?f)r{Xz4-#9-XZzN~s)7MV9kGO}8kxphJ zFLVT{l4o3%t^oQdx6sE?nlmnBjG54U@ZN^&2rl(a7*=FuMb^rOk*pjTZEk`7WF+z5 zfm^ytq+_`c6Zt+4=<1C)U{sN4qwOMnOz1fK7F<(!f)$YU$BKLT7|V6>(Wd~h=;CK4!A zWLm8~x7@w84%cwOW@+gbdi>LYW6&qB0=VQJA}nZ}MQ<)|+@xSFlpqPx(giKDmibSQmPAx(};XU2b0dLoN~G0WamJm<+o0FxmbmBDXVS_o5x4aE;S@&<}P{D*s0 zjfe0>jjPvMozJ&x(M0HWAJjmk-yee*lhRht1_s2nL z61D*R7(|&~#p(s*9XNa2vv1%GD@RNCe=#^a0ix#wsTzqMh}-6NT>u<|Ku5bPn>(lW zpTGUWUAbqSm2=|Z&d;HTcY*Xzp~ug5e`KkL9~tT=)?+LH{#loIxHrM_(OGp)dfb2F z$S-ujyQVPos=~0cPy@3PR2qQ%EkOJhd%21DPdIP{(r;DI+aCEDgf>rkTOC@KZi)MV zK9dQSmqZ^lHK3YcBM_&8WeU3SHo)($0@%`5sw^m5x9SkCwSfPoEU?W=&{`9lb_@%e zf$}@7y70D(N0Fxbw9>JQHgXT>aiM|eslX!(&11vC2zpZ?5#7nUj4t5S=lj2FuY#|syLp@98z1Ajx4e=s5>Mm zE#^#9C9k(m-f({o$H_?N5m?q7jF6Ay_u)My8HZP)Yn-P7>O5>QqR-{Gya_uW!KJX* zv-~}~>jZY6d}v=Gfhi-dyGNGv(o#~JJsNDky{kv7u?Lk{P9z?37mjzH4O;d8-4Ax(+wDvIcW_ny5TE`I45g1Afx>J&U3z*k2aK3^ zhu!&tGf3GuywMypha1N;HN-~R=(@3?rQ2bBBsWb!*}MNd7I37_vnn!pTnl?YXG;7M mk4}|!sNCCZD!X^9gYwT1eCiquaDoA+_iZyeyWXz17yJi&LayEb diff --git a/secureli/abstractions/pre_commit.py b/secureli/abstractions/pre_commit.py index b5401561..cb504406 100644 --- a/secureli/abstractions/pre_commit.py +++ b/secureli/abstractions/pre_commit.py @@ -41,6 +41,15 @@ class LanguagePreCommitConfig(pydantic.BaseModel): version: str +class UnexpectedReposResult(pydantic.BaseModel): + """ + The result of checking for unexpected repos in config + """ + + missing_repos: Optional[list[str]] = [] + unexpected_repos: Optional[list[str]] = [] + + class ExecuteResult(pydantic.BaseModel): """ The results of calling execute_hooks @@ -59,6 +68,15 @@ class InstallResult(pydantic.BaseModel): version_installed: str +class ValidateConfigResult(pydantic.BaseModel): + """ + The results of calling validate_config + """ + + successful: bool + output: str + + class Repo(pydantic.BaseModel): """A repository containing pre-commit hooks""" @@ -96,7 +114,7 @@ def __init__( def version_for_language(self, language: str) -> str: """ - Calculates a hash of the pre-commit file for the given language to be used as part + Calculates a hash of the generated pre-commit file for the given language to be used as part of the overall installed configuration. :param language: The language specified :raises LanguageNotSupportedError if the associated pre-commit file for the language is not found @@ -191,6 +209,60 @@ def create_repo(raw_repo: dict) -> Repo: repos = [create_repo(raw_repo) for raw_repo in config.get("repos", [])] return HookConfiguration(repos=repos) + def get_current_configuration(self): + """ + Returns the contents of the .pre-commit-config.yaml file. Note that this should be used to + see the current state and not be used for any desired state actions. + :return: Dictionary containing the contents of the .pre-commit-config.yaml file + """ + path_to_pre_commit_file = Path(".pre-commit-config.yaml") + + with open(path_to_pre_commit_file, "r") as f: + data = yaml.safe_load(f) + return data + + def validate_config(self, language: str) -> bool: + """ + Validates that the current configuration matches the expected configuration generated + by secureli. + :param language: The language to validate against + :return: Returns a boolean indicating whether the configs match + """ + current_config = yaml.dump(self.get_current_configuration()) + generated_config = self._calculate_combined_configuration_data( + language=language + ) + current_hash = self.get_current_config_hash() + expected_hash = self._hash_config(generated_config) + output = "" + + config_matches = current_hash == expected_hash + + if not config_matches: + output += "SeCureLI has detected that the .pre-commit-config.yaml file does not match the expected configuration.\n" + output += "This often occurs when the .pre-commit-config.yaml file has been modified directly.\n" + output += "All changes to SeCureLI's configuration should be performed through the .secureli.yaml file.\n" + output += "\n" + output += self._compare_repo_versions( + current_config=yaml.safe_load(current_config), + expected_config=yaml.safe_load(generated_config), + ) + + return ValidateConfigResult(successful=config_matches, output=output) + + def get_current_config_hash(self) -> str: + """ + Returns a hash of the current .pre-commit-config.yaml file. This hash is generated in the + same way that we generate the version hash for the secureli config file so should be valid + for comparison. Note this is the hash of the config file as it currently exists and not + the hash of the combined config. + :return: Returns a hash derived from the + """ + config_data = yaml.dump(self.get_current_configuration()) + config_hash = self._hash_config(config_data) + + return config_hash + def execute_hooks( self, all_files: bool = False, hook_id: Optional[str] = None ) -> ExecuteResult: @@ -276,7 +348,7 @@ def autoupdate_hooks( else: return ExecuteResult(successful=True, output=output) - def install_hooks(self) -> ExecuteResult: + def update(self) -> ExecuteResult: """ Installs the hooks defined in pre-commit-config.yml. :return: ExecuteResult, indicating success or failure. @@ -322,9 +394,7 @@ def _get_language_config(self, language: str) -> LanguagePreCommitConfig: try: config_data = self._calculate_combined_configuration_data(language) - version = hashlib.md5( - config_data.encode("utf8"), usedforsecurity=False - ).hexdigest() + version = self._hash_config(config_data) return LanguagePreCommitConfig( language=language, config_data=config_data, version=version ) @@ -539,3 +609,157 @@ def _apply_file_exclusions( if pathspec_pattern.include ] matching_hook["exclude"] = combine_patterns(raw_patterns) + + def _hash_config(self, config: str) -> str: + """ + Creates an MD5 hash from a config string + :return: A hash string + """ + config_hash = hashlib.md5( + config.encode("utf8"), usedforsecurity=False + ).hexdigest() + + return config_hash + + def _get_list_of_repo_urls(self, repo_list: list[dict]) -> list[str]: + """ + Parses a list containing repo dictionaries and returns a list of repo urls + :param repo_list: List of dictionaries containing repo configurations + :return: A list of repo urls. + """ + urls = [] + + for repo in repo_list: + urls.append(repo["repo"]) + + return urls + + def _get_dict_with_repo_revs(self, repo_list: list[dict]) -> dict: + """ + Parses a list containing repo dictionaries and returns a dictionary which + contains the repo name as the key and rev as the value. + :param repo_list: List of dictionaries containing repo configurations + :return: A dict with the repo urls as the key and the repo rev as the value. + """ + repos_dict = {} + + for repo in repo_list: + url = repo["repo"] + rev = repo["rev"] + repos_dict[url] = rev + + return repos_dict + + def _process_mismatched_repo_versions( + self, current_repos: list[dict], expected_repos: list[dict] + ): + """ + Processes the list of repos from the .pre-commit-config.yaml and the expected (generated) config + and returns a output as a string which lists any version mismatches detected. + :param current_repos: List of dictionaries containing repo configurations from the .pre-commit-config.yaml + file + :param expected_repos: List of dictionaries containing repo configurations from the expected (generated) + config + :return: Returns a string of output representing the version mismatches that were detected + """ + current_repos_dict = self._get_dict_with_repo_revs(repo_list=current_repos) + expected_repos_dict = self._get_dict_with_repo_revs(repo_list=expected_repos) + output = "" + + for repo in expected_repos_dict: + expected_rev = expected_repos_dict.get(repo) + current_rev = current_repos_dict.get(repo) + if expected_rev != current_rev: + output += ( + "Expected {} to be rev {} but it is configured to rev {}\n".format( + repo, expected_rev, current_rev + ) + ) + + return output + + def _get_mismatched_repos(self, current_repos: list, expected_repos: list): + """ + Compares the list of repos in the current config against the list of repos + in the expected (generated) config and returns an object with a list of missing + repos and a list of unexpected repos. + """ + current_repos_set = set(current_repos) + expected_repos_set = set(expected_repos) + unexpected_repos = [ + repo for repo in current_repos if repo not in expected_repos_set + ] + missing_repos = [ + repo for repo in expected_repos if repo not in current_repos_set + ] + + return UnexpectedReposResult( + missing_repos=missing_repos, unexpected_repos=unexpected_repos + ) + + def _process_repo_list_length_mismatch( + self, current_repos: list[str], expected_repos: list[str] + ): + """ + Processes the repo lists for the current config (.pre-commit-config.yaml) and the expected + (generated) config and generates text output indicating which repos are unexpected and + which repos are missing. + :param current_repos: List of repo names that are in the .pre-commit-config.yaml file + :param expected_repos: List of repo names from the expected (generated) config + :return: Returns output in string format with the results of the comparison + """ + output = "" + + mismatch_results = self._get_mismatched_repos( + current_repos=current_repos, + expected_repos=expected_repos, + ) + unexpected_repos = mismatch_results.unexpected_repos + missing_repos = mismatch_results.missing_repos + + if len(unexpected_repos) > 0: + output += "Found unexpected repos in .pre-commit-config.yaml:\n" + for repo in unexpected_repos: + output += "- {}\n".format(repo) + + output += "\n" + + if len(missing_repos) > 0: + output += ( + "Some expected repos were misssing from .pre-commit-config.yaml:\n" + ) + for repo in missing_repos: + output += "- {}\n".format(repo) + + output += "\n" + + return output + + def _compare_repo_versions(self, current_config: dict, expected_config: dict): + """ + Compares the current config and expected (generated) config and detemines if there + are version mismatches for the hooks. + :param current_config: The current configuration as a dict + :param expected_config: The expected (generated) configuration as a dict + :return: Returns a string containing the differences between the two configs. + """ + current_config_repos = current_config.get("repos", []) + expected_config_repos = expected_config.get("repos", []) + output = "Comparing current .pre-commit-config.yaml to expected configuration\n" + + length_of_repos_lists_match = len(current_config_repos) == len( + expected_config_repos + ) + + if not length_of_repos_lists_match: + output += self._process_repo_list_length_mismatch( + current_repos=self._get_list_of_repo_urls(current_config_repos), + expected_repos=self._get_list_of_repo_urls(expected_config_repos), + ) + + output += self._process_mismatched_repo_versions( + current_repos=current_config_repos, + expected_repos=expected_config_repos, + ) + + return output diff --git a/secureli/actions/__pycache__/__init__.cpython-39.pyc b/secureli/actions/__pycache__/__init__.cpython-39.pyc deleted file mode 100644 index 80c3844d692fe0188acc2b203279735b2181baaa..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 162 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 diff --git a/secureli/actions/__pycache__/initializer.cpython-39.pyc b/secureli/actions/__pycache__/initializer.cpython-39.pyc deleted file mode 100644 index 4ad804f39b742428105b0daa07e683b228fb2634..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1591 zcmZWp&2Hm15GM6w$&T%M(`}0uJ+ON-uyF21QS4$16e)tBK-vP;h0BPPEra?gDP=c- zd)ffohuA}M+?VK~kD+T%eT4!=J0r<)upz(^XE+>wocTs#G8qvVfBgAh{JTfUU#Q$4 zU?b09+7l3xNUBLjBT8}YXeVHU5i$(rCuF@i(JI$b5HHY2zTUDxDsyt4Wh27~%v0A11 z>P(r7G*-*SyC8QfqJH1)JB~gvzICOzhrTd zWg?ePSz+R}vXv1PAZNCNZW-*cqOQuivdk!3>k40Z&_(4^m9`0V_uSZRpVA3j@233} zl#09|H*`yHp*&k>OSmHm0H>#OxAARVD$|6VBPh-dmL2S#RXQ<4P+H4-+6+05wXl}+ z_vD}NkA4E2_Q(nwuf^hAR0nRTgnlJ1Ex%MY-W3)?62G4>F#a0QLbPG-B!#Rw;*b(DV*S`7wfh3^DsD>^ zr%9?LZ~!4QgnV~&SqdYveye@Wk|AyG`Le2D`)su=f&>>xsSm@nd04XJgjJ?iPnk@u zQ7d62Q?FCoGAl0fBwf|Wvm$TlWh-GSmcZEuu=WXEvgg*;nPN|VQT7Q!5?V7Tb5>UN z4#w$x*i0{!Ns~*U5(L(|34p%9a%GT?&1@|+;^4j2w#;gRM5G#cV*+&Y@VQN3>o``{ z&V4fht2spRF^b3NH0kf@y28wGqZN;E_oIErIA#ma*$@N?rgTgnJ6B)rtJXK{;)65u z18j+0WQy}<#CcZ8TH|`m`LDIm{Z7ESEMn*yV&NBWi;HaawG^kb0g&oquOxYd-}z0dhyD=Uwbix?a=iqHn5&g`Z@X=g%JObv_0g sqea?$e&@3L+bsG&N%sgWMSJr-sQUzmUw7&F1{$Hu?ARI88J)TR1HK5khX4Qo diff --git a/secureli/actions/__pycache__/scan.cpython-39.pyc b/secureli/actions/__pycache__/scan.cpython-39.pyc deleted file mode 100644 index 1d47ecb49a372b9bc19d486180371bc9aabbfbdc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2467 zcmZuzOK;mo5MDkcO0pzBVl<8W+6E{R0jUBMJsCw&!%12Lu2a-Wfbzo7+>u0O`RFcb zSB7#*fc%LZoMZk(5B(Xu_SC=70BvWMvg|aWz|qX^?CiYuTZX+}hvD=4AOA*Qv>E%8 zgv*bM!gu&;i9|4ANtRf?Wf5;nJ8^u+@Q!qohTkx}E1QYudr8Z08Mz_bNyqObEB=a+ zo3fkq{9e-c`^l=mYO(K`@I>o56D{aJbNn^YK4gQ=U$`S1Se5%zSWNK0bw4lSEDa^{ zt9PSGwsWKl6-G2Tu&e%lHXg_6_z=`_96{AadFOnxbr^-|kC}j~Ph1LWp*C=;-Xl=) zXm-CWqAY=`Sud(K>i;{CLnx+V(^f8zynnSX_HP<6=N+LO~q4<^7v z6A~X~iWewqh)M-A=8rWhKIRxg5~ku7&r~!4%1?ngK{b;U&2f%rWs#SK<_dHv3q5eF zmD*|`AlH=(nA^&ebvi4L3}eEg#-+%KcNVX{j!eOxu`_FKy~5U*yK@%Ua~r8McaXY* zVKCOgpiwn+nS-iYK@g{L5d>-t7pp#zRV1k4YLFwj9qMQj4v)eDP%!z(RW^mFSV%IyVCq0o4}%)!Gqr*HT)|hjkT9=nHSu?PhwVJc^?L{VCqc8tp-u+r?KuM>1z; zY;Fl_?!0oIHs+0~HD^=%n5j>NeTcJD=gh@Cy07e~&3SX$$iEiOQxC_RXWra1xo%Ed zdi2%^o3_t7#mcmc8n(sKt$J3tMxSPTG)ofgGH8TTo3RdGJQTV%y)}-HA>|@gn95Ae zsHb=~8ez_Pk@0bcN-8VI6HcX!Lm8i9vt=odM>rD}d=hG&X2sI-5uiD2&|E?Rg3q8B z@*Sx&E>MoJA#vde+)zLriaT+ECFLh^G2w@>i*3HYcSECrbQF)5M$Zp|4AXHLj`4s{ zhcCS>^RWsA@SDZNxQeUsK$M{Qxud{KY$L9*e(3FBX?QyiRhZyGTULCV(*djpIJE$! zVHrD^oGHjNI-LJEv+R{^{zGy^3qWn7J7bVaRoXQDI1_oKXQn zDc|w&qWiY_j@D%Y{Q57TuakeM&+#}Ud72jlP^Su$mL~)DDIEk?sEO$E~5yv9+eW~r`p!df-@;5AA<$``2cT{V*}!dDwr=ReGgm6!1>xF zlmvmnVn7=#SsY$UbJeWz(11|Y$kCI+?_6waaLM;ZVJxu)6+u2?6#D3U3#CCz5rn7@ ziCiPHMdTwSesAGdjkW#?EevX*@-P&%s?`UiZpc;QK0)#szM6*Edf)C^uHCmf7NJwu z+Cceqld@Fb3&VxYJR^wRBy3H?1cIRI1O!i|B)%I2zm}m~%y>Z{vIyZ9?|kO>_YMvp z?CkFc_jdO7@80p(&gI>mgWbD0qCQ47wL*kG2o!;sA~ZAjsSrMx@{m;SQI<(XH$&y% zsszVu4h~3_hxOKy|L|DYQ9mVeK1(B zJUWCjJ~9qe&0>~gq3hkZ&M(Gcc=45>hjg`>5d3RPh4Z^{xVZ7^=%~)`Q=(+!ZD_(Z vDe$i@EiEpWVf}A%t0<~EbL91<;FtBTTHn-F`qn}XO=?}Mi$z|;-=^~)EJL@< diff --git a/secureli/actions/__pycache__/setup.cpython-39.pyc b/secureli/actions/__pycache__/setup.cpython-39.pyc deleted file mode 100644 index 1d8f1d0929082dd8604e6e77e01b432548106951..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1117 zcmZWo&2H2%5Vn)-Zu*N*d*K%KfRsZM#1SDx)e3Gs&~nMea_#A^7jF`5r_fb7(QDs; z7wAiH;4yOL#48{n7<;>A5tjTn9?yLKzA@k0S|`}Pe)}zUVnV+A&1yp%d5PUV#z7KE zHL3g@X*!Flh?3Vt#xl7kGWi@;v7{#?AN)XHa-7qC$98So7zdJRUXMFI1}psGYP(NS zpcP0(C8;7wF<4Y3GOh+Nl*tz=2Xc6wR3kZ(={2cRxrV9M`;8NDU3(y$YU=sXfiai| zY<9toYC4;>rfI=Ag{B%ng3Mf#S#Vj`W^Mu*Z(FSd_qQ|UEa>xM{$%1@YoC?nnQ~{H zcn7X%%(%3g>t?nun%Z{SsTwUHIs;nG_m?*oJ_UE0TdR0o)Zoew>YXZW%LUl-YKfSGAPZu=N>W+m z<}%}`A~!B;&i_+v0vct6kFLOC8``Eb0`k~+82vU1b*?+81+dQOJ~hxbewB~<;Z)UA z{cwvxb?>LqB-^ zR|b#G_|N6hhZ0I@5+RysI}Y|dF`Y>M9Dj%Ld9-~VxNK}R?&#`;$Lu%^a8i@Hod3@V_O@U z)>&1$XjtWyft`o%tMl`$JRfOum8IHsz#sUW6whuB2F@W&`x1;M2~{Ks6iq_SbfjYy z+=dvGW{@FF`xMNAE{GtD04!aEU;{;f5Ir_6*zGaLtj^C&8ywR? zgH637W()rQ4EH};ZS204HocJjv8**XNJmdiHPLCk^zv-IXhgC5h_^qzc5NZDGOLBa z$~Fc=Xbivk;}gheB<-(qdelWJb~i;9tAXnXrf2m677H>V6JRz87F1CcdWRm1Mf z9!#Z?MN{lx9W}b1nQ|TY&W z9Dm%mr7_!>(Dmm9reSsKpF+GB%%5y%yUsc8b+H|#EVK}=D@0MLS&r*1AueY!U+%Po zP*n=y5I#DC>Km+V9-_hT+U%jhH?m<6k-`2VcHihWW$aPpV?fzGZ=Oz`_2v3&wtbYp pxdcnQXZKmlme~ICr8gk3x-YqLwMXI=X}@kby`cyKQSZ{-@E>zKF53VA diff --git a/secureli/actions/action.py b/secureli/actions/action.py index 2e850a3f..f62a682e 100644 --- a/secureli/actions/action.py +++ b/secureli/actions/action.py @@ -18,6 +18,7 @@ from secureli.services.language_support import LanguageSupportService from secureli.services.scanner import ScannerService, ScanMode from secureli.services.updater import UpdaterService +from secureli.abstractions.pre_commit import PreCommitAbstraction class VerifyOutcome(str, Enum): @@ -27,6 +28,9 @@ class VerifyOutcome(str, Enum): UPGRADE_CANCELED = "upgrade-canceled" UPGRADE_SUCCEEDED = "upgrade-succeeded" UPGRADE_FAILED = "upgrade-failed" + UPDATE_CANCELED = "update-canceled" + UPDATE_SUCCEEDED = "update-succeeded" + UPDATE_FAILED = "update-failed" UP_TO_DATE = "up-to-date" @@ -56,6 +60,7 @@ def __init__( scanner: ScannerService, secureli_config: SecureliConfigRepository, updater: UpdaterService, + pre_commit: PreCommitAbstraction, ): self.echo = echo self.language_analyzer = language_analyzer @@ -63,6 +68,7 @@ def __init__( self.scanner = scanner self.secureli_config = secureli_config self.updater = updater + self.pre_commit = pre_commit class Action(ABC): @@ -89,9 +95,21 @@ def verify_install( available_version = self.action_deps.language_support.version_for_language( config.overall_language ) + + # Check for a new version and prompt for upgrade if available if available_version != config.version_installed: return self._upgrade_secureli(config, available_version, always_yes) + # Validates the current .pre-commit-config.yaml against the generated config + config_validation_result = self.action_deps.pre_commit.validate_config( + language=config.overall_language + ) + + # If config mismatch between available version and current version prompt for upgrade + if not config_validation_result.successful: + self.action_deps.echo.print(config_validation_result.output) + return self._update_secureli(always_yes) + self.action_deps.echo.print( f"SeCureLI is installed and up-to-date (language = {config.overall_language})" ) @@ -111,7 +129,7 @@ def _upgrade_secureli( :return: The new SecureliConfig after upgrade or None if upgrading did not complete """ self.action_deps.echo.print( - f"The version installed is {config.version_installed}, but the latest is {available_version}" + f"The config version installed is {config.version_installed}, but the latest is {available_version}" ) response = always_yes or self.action_deps.echo.confirm( "Upgrade now?", @@ -224,3 +242,29 @@ def _install_secureli(self, folder_path: Path, always_yes: bool) -> VerifyResult config=config, analyze_result=analyze_result, ) + + def _update_secureli(self, always_yes: bool): + """ + Prompts the user to update to the latest secureli install. + :param always_yes: Assume "Yes" to all prompts + :return: Outcome of update + """ + update_prompt = "Would you like to update your pre-commit configuration to the latest secureli config?\n" + update_prompt += "This will reset any manual changes that may have been made to the .pre-commit-config.yaml file.\n" + update_prompt += "Proceed?" + update_confirmed = always_yes or self.action_deps.echo.confirm( + update_prompt, default_response=True + ) + + if not update_confirmed: + self.action_deps.echo.print("\nUpdate declined.\n") + return VerifyResult(outcome=VerifyOutcome.UPDATE_CANCELED) + + update_result = self.action_deps.updater.update() + details = update_result.output + self.action_deps.echo.print(details) + + if update_result.successful: + return VerifyResult(outcome=VerifyOutcome.UPDATE_SUCCEEDED) + else: + return VerifyResult(outcome=VerifyOutcome.UPDATE_FAILED) diff --git a/secureli/actions/update.py b/secureli/actions/update.py index 78992a2e..c2a5bb25 100644 --- a/secureli/actions/update.py +++ b/secureli/actions/update.py @@ -41,10 +41,8 @@ def update_hooks(self, latest: Optional[bool] = False): self.echo.print("Hooks successfully updated to latest version") self.logging.success(LogAction.update) else: - self.echo.print( - "Updating hooks to the version defined in pre-commit-config.yaml..." - ) - install_result = self.updater.install_hooks() + self.echo.print("Beginning update...") + install_result = self.updater.update() details = install_result.output or "Unknown output during hook installation" self.echo.print(details) if not install_result.successful: diff --git a/secureli/container.py b/secureli/container.py index 6f915fd0..9950c6f1 100644 --- a/secureli/container.py +++ b/secureli/container.py @@ -123,6 +123,7 @@ class Container(containers.DeclarativeContainer): updater_service = providers.Factory( UpdaterService, pre_commit=pre_commit_abstraction, + config=secureli_config_repository, ) # Actions @@ -135,6 +136,7 @@ class Container(containers.DeclarativeContainer): scanner=scanner_service, secureli_config=secureli_config_repository, updater=updater_service, + pre_commit=pre_commit_abstraction, ) """The Yeti Action, used to render the yeti_data using the echo""" diff --git a/secureli/repositories/__pycache__/__init__.cpython-39.pyc b/secureli/repositories/__pycache__/__init__.cpython-39.pyc deleted file mode 100644 index c523e9a6c33b2e098a4addad6a9a3273f2703c07..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 167 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 DwG4+y diff --git a/secureli/repositories/__pycache__/secureli_config.cpython-39.pyc b/secureli/repositories/__pycache__/secureli_config.cpython-39.pyc deleted file mode 100644 index 3695e76b22901468499a72efb0fdd5fb78ebc56c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2207 zcmaJ?Pj4GV6rb6DUK=+_MUh$x(MTYKrQ(8wgcL<6A_s(2460jI__CSojN?t$yUWZt zsV(Q^T!AmZAvy91xF8OE3ba>Fe1%@%y;*zXsAxNrucaQmdw{1f*)TaOxAGIIc$!8j&yFVi zNS3!jp}~PD$+UqPDSDlt45KLHV;MzdD~iUsn4~yvN70WHo>nsoJt|#oRJj>NJj-&! z@g_Qoek1A@Oxo^c|KU>&M?BPA$0Oc7oyVpbbc;(r2_Mp#CB;%Q2N*eGK1T&JXFZUtjkG^4*yAx%@0_bj5x2`V|JK)u- zt!}gr3QN2>xgeM1%$Yla-X~|Sp>tO_3+ESh<{9V0ozn~NlFYqJhmvQ2w?{m(0Agla zpAo(HDWUfF1N0RaWsI_2ZEq^G@(Uv2{x6O-^XnaQX=LL>^PlB;Ry z<%P^j&+hQVWL%Vrnyewx+F)t+aG_MOU?fyFK(2?Pw$! z@k1H{$7b*MuVvE}6}q?{5DtI{6P3jjcP-mBU=KjK1eVUJ1r`9lpqE5%8)rfK#JB)B z!u%mX?p^qE_mWz`1Av-7BhDJseF9KFz5(h#>L@?TxwwgyY^d_Foschp1h2w1Oj5uS zg2r-%{Dx~W&N6vgo4!VGmLFm&KVUI0_#te~hwM-$*%5>4QBe#N1>+>xNy6>X2Pht} zu{5Jx)OOWv2vSK^uhUR(<1mmf5^c0iadihY>K#-!H6O|-g$tHV;G>oi_|WmyU0iVw zyLYj>k83@_4Sxk?+>2{8f?lB<(!goc)|;@#UDpuCG{!n)Q+FE%2HuPX6%M2=pnQyV z1}e=@kf?JCHN~I(USD=!Ni2vrrW-p=%qYq198_^7GsEgMV95~BkV1-XZEUB)smjBO z_e%(eHy>a|+%O5JNX4okBig52v*U?2EVjb5%95!}q;xM3Knj zD5`k#9_l~94mq_(1?vdAh&%3D>jD3 lRPf9su|g_TXQIKIPAbFQ>P`I;3x!5++fZLZr|oX}{{uqGMx_7% diff --git a/secureli/resources/__pycache__/__init__.cpython-39.pyc b/secureli/resources/__pycache__/__init__.cpython-39.pyc deleted file mode 100644 index 8590ec3b2dc17489f792c881bc082e279078243a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 234 zcmYe~<>g`kf_;bnCtCyQ#~=(FQesIekOMZRpeR2pHMs=J%gIDA;HK-x$7kkcmc+;F6;$5hu*uC& PDa}c>V+5N18HgDG*$YAV diff --git a/secureli/resources/__pycache__/read_resource.cpython-39.pyc b/secureli/resources/__pycache__/read_resource.cpython-39.pyc deleted file mode 100644 index cbb8fd3a1034804f417c275301a8b41fbbf79913..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1024 zcmZ`&O=}b}7=DwPon4kzR!@aqLJz`1Y3)U%ND;*ABGwPw%bHHIZpv)3B$=vZi(tKq zKR|`{=%4W5&&bu2f5D5s$$nnUkayi{BUpT@v!g+>g36Y+C6|ZA&@Od@Ms@rLV0n zNer8>9PcgM4{l*^kzJSQSWtZdw z@x_#iTr@t>&s!JSG`nhE!waAwiX3vd%m^IA0k~IpfTZgsT2Ub8CV!7`+6>nd1Y4a+ z$NWgLi8W_RNWm5qSfs#|_J(ohyjA)&D~G&9PqARs`%nX_1YL%PHA5OUC<7K@h z$sv1+d8sny#2U+XLsiLL_GYBY5pyF`SAw0)T3|=SrF4qliE(a+4NWDaoi(ytIi&HN zSG9a(t+9tp&8q*0l`t5tjZfWC0v1VXCf_ROBe~dPUOIij8SHhs3+R}mJc+YBr8I}7PCJc+qBz7xdNUrlrD^c`#m z)Bg1KpE+sWzT>VO@x2r7B|5UKtwgm?W$EW$rB)BiR{IgbVBzjf-o>VCC-L`i8)%@} yCL6RvI`jsyx6to(^U#dADs?iGAj7FluM)5|a}&cu>;Bk?x{^on?{iVJjqD#2D=~fm diff --git a/secureli/resources/__pycache__/slugify.cpython-39.pyc b/secureli/resources/__pycache__/slugify.cpython-39.pyc deleted file mode 100644 index ce56640d8c30af0b87d5faf3449c2b7aa83fd85d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1292 zcmaJ>&2Aev5GHrE`n6L#?vL$5v2D^CYAgxpF$mHqa*YNpf;4I`8@mRzLu(mxe@b%g z$dyj*1M~rMkdA$c9{L!2?Wwn(_ma-8K}?;}G&^ zF}dDsoIJv=KSlurs3hJ}Q;%{Btk)E5sQgm#Y@kEZcK$*fdD^C#bIxSSeOk;{t%73n21~j1sYp@Qt;2qe2&DR?64drIaw zn%WC%Mt z2|J5|{w@V-@juC7d} z5mNUIe`UJKq;xolqjRo|Yht%Q96WjAic@!wNf}?9q>=Dr;Crsn?je(!cik(rPMFWN z8!K1wgfqjTbOuZZXhvUJL}SU_rJ?IS=V5%#T|Z!o`N#(`0W&UBJlaDglYm8O$d&N* ze(5QV08xIdxZW?q1d}A-k$%enV<1Pad{*%4ON}v&5{WB00}Sr6XvxVaCS+l_2!!D! z@%0(Ijff*}?J!msOu^Dc^B|Uzsi7E0UhB(OzGNXQ zVa_0yh_?F1t2~pbZg1rDPEJ3`>8Ba}DWkKT9_92wP9J9Uvz&gB(_T*R=JcbS-p}cy zoZj;)PY=I7$Q-79A+jn)zze^uzxd(k;z)OnGjdsd(K*t`_j1~~v^pp4R#rLV6P?vC z?Gj5Mv+>fi)~Zr~8J0EBN64hOjua6d(?ieGZy ztY%WY9bk#YDJB|`G98Q43H};cZ8gT%hv?5ykfufd_xroLwMOgomQ}a5-&po-s_x=0 nJlrlS^g`kf_;bnCxht6AOaaM0yz#qT+9L_QW%06G#UL?G8BP?5yUSK{m|mnqGJ8x z#Nyb1KIEyh#3I?J}Ni> diff --git a/secureli/services/__pycache__/git_ignore.cpython-39.pyc b/secureli/services/__pycache__/git_ignore.cpython-39.pyc deleted file mode 100644 index 009b0021d5f4c95a702b6858eb38da8b1eeb6513..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3870 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 L+#n#Mvx9#CCo;nf diff --git a/secureli/services/__pycache__/logging.cpython-39.pyc b/secureli/services/__pycache__/logging.cpython-39.pyc deleted file mode 100644 index 48865ce87676cb0fd40dba7aab98bf535a34a7fe..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4334 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 diff --git a/secureli/services/__pycache__/scanner.cpython-39.pyc b/secureli/services/__pycache__/scanner.cpython-39.pyc deleted file mode 100644 index 3c13ca1403c93201ea62c36f7db5b70ecee2f8d2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2085 zcmbtV!EPHj5MA!Bq}`S51a8_S?V$vEXcgEB&_fPJTG(|R!+@(4m4l*tA*dx~t+HBf z$W>Db%E`Ir57H0FsejNzKVz>w^%r`mJHxeXHOZ|6I3kC`)y$hWL%y*QIWT_z<0AR3 z;W&TdW_^Ip*@J0@AcP}G=1fUU2(I1CoqDln*IwqwKIr_cF>S_8;v6}mA(}58(UdJo z&iyz5O-lrz38X&>&%HPTO(-JJMCWeY7T&<=w*P|vak`}P5A)d+euKwFnW~&;ec0Uk zUdsbDou=jf(3F}dc$DCc^+8~>2h-dEA)S~ACw2viJ>i0nUfd8q{8OU}1_{r||*0c8(ZBz0yDRxwz%@-lh zvYkamf^a?*s~eWE>BvF36+4lJviMzI{cPB?w|Zrh5IKb z?9uVb;oyyQv4ZmnP$BlDFB`&r=m4mg=HRXadmr}};0OW! zL7G`sF78>VPREjJt1wiJXaZOPj22G@0Vr!!MP`(8*F<>2f9V6MLb<{q6wTM3C3_=|L<6vhs!hZKpdUz^ip8D{6_tjT7L`I#o+L^O zL&sbQqK@ctZO6 zL(p{H$~Q6_>5pKuh7Ur6q)qd*WQ@Yj*e{dC?f(YQTiNuxUKJXM1L-ZR4O7S@9i<5a zVwP1?%W|gk1aq+(2&fO!y7wh4mZWqh5e~o#-*laTDDyO!#=@)5F_O`9Q zR`4MZ743)e%B?y#r4(JN;9HPpnU|@?8Yq-WOQq*mwksdj;oGIhBYFv_6x%dQq4m{# zfz)OGR_bZppD;rBPG9A6yY6UNW{=JF`EH%3UAk|vtc#Ya zAvP3bUCDtFz)R?a78huSwn(Lrg7O^aoJy^gz6$8{5p?Vpy1`P>b@dI%v@OhT%QhO_ zMg{V(+NeX246ThV!!`4UzKxc5QGAT%*kYMQJ>Fd1th>tFOsCuKN`=voywQ+;At$js#(qp0K0BrK`|E`Sd&z=Zu42%0LL0+B@qG)>svO l!;QoTF00+ce|n#LRq(KWn!d)T(_jD{(s2W~<86go;lDy}3=IGP diff --git a/secureli/services/__pycache__/secureli_ignore.cpython-39.pyc b/secureli/services/__pycache__/secureli_ignore.cpython-39.pyc deleted file mode 100644 index 9c733d214fa49ed7ac095ba04b9810d9eb42753b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1668 zcmb7EL5~|X6t+E+$u!z7(6R@RKt6#Y%4|Cc12$w&sg@ z-8enb?nhJVi=Rgv+Lul9gXgoxaak)KoUFZ-L3WN|Jl%V);w4?SNx{c+h; zl?i-qo5#{}{j9XDH%;BST*`3fxv4|LCHFe;X2zYKw^ll^gwq~P@dg=qB|~|}rR}|A zByFuaH!lGbINf8ywgAC7_J`0tQgTTzi2$S_TYBn%8Zu@v_1ezd02VKa8^Y(WuMfZV z+WA8-eR(E}$C&0}pW)Iq3tfiQ%9`y-ht}HA8euvVi{*LDg)p@VLhONi--Spo-rtFK zu{H&!Gp9rA>XO2#bwiFZ@Ghq#m|T(tS7iyP}FJb`AM2 zvd_tLdc`goV35cM;JU*Q8Ga~j3D^hSQM!|ehWAPFnQN;5WPRiLb@qA3{5>wcZ!3^8 z2ZhWUYn!LNoV=_w_rbL#o^vz9q&H4cz})zi3D{N!v{=uJjm0aZAfrgk1iF70hB?sk{hcccfD@O( z2n7Aao}`mrVdEqYO%0`>bQ}QC`dJ*VA_PIQ;(mOyLujc>vD)vH*$+8f5J$(DEmYU? z=srJo)Vbb+N&>)ZBt6?Phhh4H_p3ih$E+$O=s@oL07p-zj`|6 z>o8;f4HWYP0*)iphOnmAzWa~3`*`<}3Evs3zQQsFTihKd4#ST7YcH!)drm&xMEH8* z2Rrk412K3T1~MGclw~xd`AwQ;EWOFHi;w;XBB$^WLcP`?K_TKuh^kR7ASU9T5KmfZ zSAT{=sHPOcA^GkW3^7FrV*(m;D1xoX@mX=mn?spk(j8$rO$Yl)w>M7RUD%Fq_3w-| d@TB%nT7Lna+vPCk$9;e&_)2(u8gjOm`~xY!*uDS& diff --git a/secureli/services/updater.py b/secureli/services/updater.py index a4fb30d1..8c64a407 100644 --- a/secureli/services/updater.py +++ b/secureli/services/updater.py @@ -3,6 +3,7 @@ import pydantic from secureli.abstractions.pre_commit import PreCommitAbstraction +from secureli.repositories.secureli_config import SecureliConfigRepository class UpdateResult(pydantic.BaseModel): @@ -19,8 +20,13 @@ class UpdaterService: Handles update operations """ - def __init__(self, pre_commit: PreCommitAbstraction): + def __init__( + self, + pre_commit: PreCommitAbstraction, + config: SecureliConfigRepository, + ): self.pre_commit = pre_commit + self.config = config def update_hooks( self, @@ -49,19 +55,31 @@ def update_hooks( return UpdateResult(successful=update_result.successful, output=output) - def install_hooks(self): + def update(self): """ - Installs the hooks defined in pre-commit-config.yml. + Updates secureli with the latest local configuration. :return: ExecuteResult, indicating success or failure. """ - install_result = self.pre_commit.install_hooks() - output = install_result.output - - if install_result.successful and not output: - output = "No changes necessary.\n" - - if install_result.successful and install_result.output: + secureli_config = self.config.load() + output = "Updating .pre-commit-config.yaml...\n" + install_result = self.pre_commit.install( + language=secureli_config.overall_language + ) + if not install_result.successful: + output += "Failed to update .pre-commit-config.yaml prior to hook install\n" + return UpdateResult(successful=install_result.successful, output=output) + + hook_install_result = self.pre_commit.update() + output += hook_install_result.output + + if ( + hook_install_result.successful + and output == "Updating .pre-commit-config.yaml...\n" + ): + output += "No changes necessary.\n" + + if hook_install_result.successful and hook_install_result.output: prune_result = self.pre_commit.remove_unused_hooks() - output = output + "\nRemoving unused environments:\n" + prune_result.output + output += "\nRemoving unused environments:\n" + prune_result.output - return UpdateResult(successful=install_result.successful, output=output) + return UpdateResult(successful=hook_install_result.successful, output=output) diff --git a/secureli/utilities/__pycache__/__init__.cpython-39.pyc b/secureli/utilities/__pycache__/__init__.cpython-39.pyc deleted file mode 100644 index 8b4cdb696f3064e1d8876d730a4df9f6d7bed638..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 164 zcmYe~<>g`kf_;bnCxht6AOaaM0yz#qT+9L_QW%06G#UL?G8BP?5yUS~{m|mnqGJ8x z#NybZ1U5bf^i{k9#OYh-5oO@OLdQ> ztk$Q%1qs0q;Na!PpWwjH=qo4x1tb(+&#b{lFr%sI*Im_B{a$snzTRbce*Nuj{8%#f zy9YO)0N^P;vkRk`;wd|+|NMk&p~O#I1xkJ+Pl6R{s3wqHp_Xa`H55N&{mvEkV8cFl zP13fcJofnd@tIHodydaMf|(%O&-{W}cE%UpBHQ=kf-m@4a4A)=}6dI*f}fi*gmtsK{nEv_+^iCFk{GX*9iWLY+lP8b(TyHl`oAV3JtZjEj7n zOy*thv|YLQf{j&0X`L+X_;J7Kx~4qE)-eKIKxOXa!T!M>HBh!|DjgR}yN0op%&@PF zXky$JN}8qGYBjo!Y+}nad_> zlH}phXD{cyZ3GQg3fx((Kdc=dx}c=gwT_0L4qqJ)`wh2NJI*4)LEYN(D%U}cBT3&eG3Xz<10ZA76KGEmt3_#YOrL2 zadr%=+7g<)2|PMvUq3j$F_Uk8z|7eWJpKDc6_MYQ%*xl_UC*h`A=^O=#OeNrI3Mpl zd+|Tgpyx>EHaw*$kEdZCWqRJJ#PLCn6Zw9*@(nWw)#E4g9;_Km!MYh-3Cs`aE-Yk{ z#Z2d};{{0}y>T7TZqc(vPCdA|*8ZzKqJnT$_}I0ejYV!Jz4yPhxx1=tR980gmA#8i zj8~2c!Pj^j?W{U){WqkAwez@4mmztXT;zmdV;LaJJz{b5$9GS$2OnAOd} KuE9ooqxTo%wW5VVr0~1MJtuwgEg*b zTJV`QAuX#G2g%6_-UGZYMUxZPQuK&;d|gn*zB2nFCkp9~Fmg?A$c%cV#XP;B7wj$h zEN09H*F2{=Ls4AuoL!PDb`zA5J#z9IvkleA=?$9&s+k9uM6Fx}GoEu4-I9M$P+Qso5BJs$;&5eR9t6Q|lEm=O@Ce)`{Nry6nRpGF)e2CDwuR9ab0Tz$J; z@nHfrg>c>?wnH7sH?2U4;q+lQxS7R5=0J1gU6j4t(_*U_FkWgi*9ii-%lYC2H?my3 z%hEK`ptj4@Ql1_OlROli0=CqZ746||`Ip(!6g+hUbw~4a_GRmwbzgRyMG%20c$lQq zqG6>OLlNLm#fpVAk|7lQIMJrtutC}XM2`(>QiG9>U{EV7sP1pnBQ2x-r_0d1tIqr+ zb{)a)-tByJVBLH2*;=+F+eo0vQ7Hj1X7rnq*?&@XtTarjkyR{2WjBePM~wD#ge OWfAO`8+Bz{C-?`0O-0}U diff --git a/secureli/utilities/__pycache__/secureli_meta.cpython-39.pyc b/secureli/utilities/__pycache__/secureli_meta.cpython-39.pyc deleted file mode 100644 index c5b161cef549243556e2525539499b4090f1d683..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 445 zcmYjNy-ve05Vn)jMuv1Ex{`)J*!Np!v-!OUYYz{pE5Fz^sr znRo>jIEVh75`8>VIc7$t$dZsh;Oc>J`L#vJ1S=ZQ``L70Hnw+gG+Z1pvnRV+){0H{odD#E} diff --git a/tests/abstractions/test_pre_commit.py b/tests/abstractions/test_pre_commit.py index ffbf2b39..22e2837c 100644 --- a/tests/abstractions/test_pre_commit.py +++ b/tests/abstractions/test_pre_commit.py @@ -2,6 +2,7 @@ from unittest.mock import MagicMock import pytest +import yaml from pytest_mock import MockerFixture from secureli.abstractions.pre_commit import ( @@ -41,6 +42,25 @@ def mock_data_loader() -> MagicMock: return mock_data_loader +@pytest.fixture() +def mock_open_config(mocker: MockerFixture) -> MagicMock: + mock_open = mocker.mock_open( + read_data=""" + exclude: some-exclude-regex + repos: + - hooks: + - id: some-test-hook + repo: xyz://some-test-repo-url + rev: 1.0.0 + - hooks: + - id: some-other-test-hook + repo: xyz://some-other-test-repo-url + rev: 1.0.0 + """ + ) + mocker.patch("builtins.open", mock_open) + + @pytest.fixture() def mock_subprocess(mocker: MockerFixture) -> MagicMock: mock_subprocess = MagicMock() @@ -59,6 +79,16 @@ def mock_hashlib(mocker: MockerFixture) -> MagicMock: return mock_hashlib +@pytest.fixture() +def mock_hashlib_no_match(mocker: MockerFixture) -> MagicMock: + mock_hashlib = MagicMock() + mock_md5 = MagicMock() + mock_hashlib.md5.return_value = mock_md5 + mock_md5.hexdigest.side_effect = ["first-hash-code", "second-hash-code"] + mocker.patch("secureli.abstractions.pre_commit.hashlib", mock_hashlib) + return mock_hashlib + + @pytest.fixture() def pre_commit( mock_hashlib: MagicMock, @@ -599,9 +629,122 @@ def mock_loader_side_effect(resource): assert hook_id is None -##### autoupdate_hooks ##### +def test_that_get_current_config_returns_config_data( + pre_commit: PreCommitAbstraction, mock_open_config: MagicMock +): + config = pre_commit.get_current_configuration() + + assert config["exclude"] == "some-exclude-regex" + + +##### validate_config ##### +def test_that_validate_config_returns_no_output_on_config_match( + pre_commit: PreCommitAbstraction, + mock_hashlib: MagicMock, + mock_data_loader: MagicMock, +): + validation_result = pre_commit.validate_config("Python") + + assert validation_result.successful + assert validation_result.output == "" +def test_that_validate_config_detects_mismatched_configs( + pre_commit: PreCommitAbstraction, + mock_hashlib_no_match: MagicMock, + mock_data_loader: MagicMock, + mock_open_config: MagicMock, +): + mock_data_loader.return_value = '{"exclude": "some-exclude-regex","repos":[{"hooks":[{"id":"some-test-hook"}],"repo":"xyz://some-test-repo-url","rev":"1.0.1"}]}' + validation_result = pre_commit.validate_config("Python") + + assert not validation_result.successful + + +def test_that_validate_config_detects_mismatched_hook_versions( + pre_commit: PreCommitAbstraction, + mock_hashlib_no_match: MagicMock, + mock_data_loader: MagicMock, + mock_open_config: MagicMock, +): + load_return_value = """ + exclude: some-exclude-regex + repos: + - hooks: + - id: some-test-hook + repo: xyz://some-test-repo-url + rev: 1.0.1 + - hooks: + - id: some-other-test-hook + repo: xyz://some-other-test-repo-url + rev: 1.0.0 + """ + mock_data_loader.return_value = load_return_value + validation_result = pre_commit.validate_config("Python") + output_by_line = validation_result.output.splitlines() + + assert ( + output_by_line[-1] + == "Expected xyz://some-test-repo-url to be rev 1.0.1 but it is configured to rev 1.0.0" + ) + + +def test_that_validate_config_detects_extra_repos( + pre_commit: PreCommitAbstraction, + mock_hashlib_no_match: MagicMock, + mock_data_loader: MagicMock, + mock_open_config: MagicMock, +): + load_return_value = """ + exclude: some-exclude-regex + repos: + - hooks: + - id: some-test-hook + repo: xyz://some-test-repo-url + rev: 1.0.0 + """ + mock_data_loader.return_value = load_return_value + validation_result = pre_commit.validate_config("Python") + output_by_line = validation_result.output.splitlines() + + assert output_by_line[-3] == "Found unexpected repos in .pre-commit-config.yaml:" + assert output_by_line[-2] == "- xyz://some-other-test-repo-url" + + +def test_that_validate_config_detects_missing_repos( + pre_commit: PreCommitAbstraction, + mock_hashlib_no_match: MagicMock, + mock_data_loader: MagicMock, + mock_open_config: MagicMock, +): + load_return_value = """ + exclude: some-exclude-regex + repos: + - hooks: + - id: some-test-hook + repo: xyz://some-test-repo-url + rev: 1.0.0 + - hooks: + - id: some-other-test-hook + repo: xyz://some-other-test-repo-url + rev: 1.0.0 + - hooks: + - id: some-third-test-hook + repo: xyz://some-third-test-repo-url + rev: 1.0.0 + """ + mock_data_loader.return_value = load_return_value + validation_result = pre_commit.validate_config("Python") + output_by_line = validation_result.output.splitlines() + + assert ( + output_by_line[-4] + == "Some expected repos were misssing from .pre-commit-config.yaml:" + ) + assert output_by_line[-3] == "- xyz://some-third-test-repo-url" + + +##### autoupdate_hooks ##### def test_that_pre_commit_autoupdate_hooks_executes_successfully( pre_commit: PreCommitAbstraction, mock_subprocess: MagicMock, @@ -705,23 +848,23 @@ def test_that_pre_commit_autoupdate_hooks_converts_repos_when_repos_is_a_string( assert "--repo string" in mock_subprocess.run.call_args_list[0].args[0] -##### install_hooks ##### -def test_that_pre_commit_install_hooks_executes_successfully( +##### update ##### +def test_that_pre_commit_update_executes_successfully( pre_commit: PreCommitAbstraction, mock_subprocess: MagicMock, ): mock_subprocess.run.return_value = CompletedProcess(args=[], returncode=0) - execute_result = pre_commit.install_hooks() + execute_result = pre_commit.update() assert execute_result.successful -def test_that_pre_commit_install_hooks_properly_handles_failed_executions( +def test_that_pre_commit_update_properly_handles_failed_executions( pre_commit: PreCommitAbstraction, mock_subprocess: MagicMock, ): mock_subprocess.run.return_value = CompletedProcess(args=[], returncode=1) - execute_result = pre_commit.install_hooks() + execute_result = pre_commit.update() assert not execute_result.successful diff --git a/tests/actions/test_action.py b/tests/actions/test_action.py index f2958787..3582ae74 100644 --- a/tests/actions/test_action.py +++ b/tests/actions/test_action.py @@ -8,6 +8,8 @@ from secureli.services.language_analyzer import AnalyzeResult, SkippedFile from secureli.actions.action import Action, ActionDependencies from secureli.services.language_support import LanguageMetadata +from secureli.services.updater import UpdateResult +from secureli.abstractions.pre_commit import ValidateConfigResult test_folder_path = Path("does-not-matter") @@ -32,6 +34,7 @@ def action_deps( mock_scanner: MagicMock, mock_secureli_config: MagicMock, mock_updater: MagicMock, + mock_pre_commit: MagicMock, ) -> ActionDependencies: return ActionDependencies( mock_echo, @@ -40,6 +43,7 @@ def action_deps( mock_scanner, mock_secureli_config, mock_updater, + mock_pre_commit, ) @@ -244,3 +248,50 @@ def test_that_initialize_repo_is_aborted_by_the_user_if_the_process_is_canceled( action.verify_install(test_folder_path, reset=False, always_yes=False) mock_echo.error.assert_called_with("User canceled install process") + + +def test_that_verify_install_updates_if_config_validation_fails( + action: Action, + mock_pre_commit: MagicMock, + mock_language_support: MagicMock, + mock_updater: MagicMock, + mock_secureli_config: MagicMock, +): + mock_pre_commit.validate_config.return_value = ValidateConfigResult( + successful=False, output="Configs don't match" + ) + mock_language_support.version_for_language.return_value = "abc123" + mock_secureli_config.load.return_value = SecureliConfig( + overall_language="PreviousLang", version_installed="abc123" + ) + mock_updater.update.return_value = UpdateResult( + successful=True, output="Some output" + ) + + verify_result = action.verify_install( + test_folder_path, reset=False, always_yes=True + ) + + assert verify_result.outcome == "update-succeeded" + + +def test_that_update_secureli_handles_declined_update( + action: Action, + mock_echo: MagicMock, +): + mock_echo.confirm.return_value = False + update_result = action._update_secureli(always_yes=False) + + assert update_result.outcome == "update-canceled" + + +def test_that_update_secureli_handles_failed_update( + action: Action, + mock_updater: MagicMock, +): + mock_updater.update.return_value = UpdateResult( + successful=False, outcome="update failed" + ) + update_result = action._update_secureli(always_yes=False) + + assert update_result.outcome == "update-failed" diff --git a/tests/actions/test_initializer_action.py b/tests/actions/test_initializer_action.py index 52ebcaf2..14858c54 100644 --- a/tests/actions/test_initializer_action.py +++ b/tests/actions/test_initializer_action.py @@ -31,6 +31,7 @@ def action_deps( mock_scanner: MagicMock, mock_secureli_config: MagicMock, mock_updater: MagicMock, + mock_pre_commit: MagicMock, ) -> ActionDependencies: return ActionDependencies( mock_echo, @@ -39,6 +40,7 @@ def action_deps( mock_scanner, mock_secureli_config, mock_updater, + mock_pre_commit, ) diff --git a/tests/actions/test_scan_action.py b/tests/actions/test_scan_action.py index 8ee7ab7a..1947d47c 100644 --- a/tests/actions/test_scan_action.py +++ b/tests/actions/test_scan_action.py @@ -32,6 +32,7 @@ def action_deps( mock_scanner: MagicMock, mock_secureli_config: MagicMock, mock_updater: MagicMock, + mock_pre_commit: MagicMock, ) -> ActionDependencies: return ActionDependencies( mock_echo, @@ -40,6 +41,7 @@ def action_deps( mock_scanner, mock_secureli_config, mock_updater, + mock_pre_commit, ) @@ -87,7 +89,7 @@ def test_that_scan_repo_scans_if_installed( mock_scanner.scan_repo.assert_called_once() -def test_that_scan_repo_scans_if_upgrade_canceled( +def test_that_scan_repo_continue_scan_if_upgrade_canceled( scan_action: ScanAction, mock_secureli_config: MagicMock, mock_language_support: MagicMock, diff --git a/tests/actions/test_update_action.py b/tests/actions/test_update_action.py index 52bbce99..48135425 100644 --- a/tests/actions/test_update_action.py +++ b/tests/actions/test_update_action.py @@ -17,7 +17,7 @@ def mock_scanner() -> MagicMock: def mock_updater() -> MagicMock: mock_updater = MagicMock() mock_updater.update_hooks.return_value = UpdateResult(successful=True) - mock_updater.install_hooks.return_value = UpdateResult(successful=True) + mock_updater.update.return_value = UpdateResult(successful=True) return mock_updater @@ -29,6 +29,7 @@ def action_deps( mock_scanner: MagicMock, mock_secureli_config: MagicMock, mock_updater: MagicMock, + mock_pre_commit: MagicMock, ) -> ActionDependencies: return ActionDependencies( mock_echo, @@ -37,6 +38,7 @@ def action_deps( mock_scanner, mock_secureli_config, mock_updater, + mock_pre_commit, ) @@ -59,7 +61,7 @@ def test_that_update_action_executes_successfully( mock_updater: MagicMock, mock_echo: MagicMock, ): - mock_updater.install_hooks.return_value = UpdateResult( + mock_updater.update.return_value = UpdateResult( successful=True, output="Some update performed" ) @@ -73,10 +75,32 @@ def test_that_update_action_handles_failed_execution( mock_updater: MagicMock, mock_echo: MagicMock, ): - mock_updater.install_hooks.return_value = UpdateResult( + mock_updater.update.return_value = UpdateResult( successful=False, output="Failed to update" ) update_action.update_hooks() mock_echo.print.assert_called_with("Failed to update") + + +def test_that_latest_flag_initiates_update( + update_action: UpdateAction, + mock_echo: MagicMock, +): + update_action.update_hooks(latest=True) + + mock_echo.print.assert_called_with("Hooks successfully updated to latest version") + + +def test_that_latest_flag_handles_failed_update( + update_action: UpdateAction, + mock_updater: MagicMock, + mock_echo: MagicMock, +): + mock_updater.update_hooks.return_value = UpdateResult( + successful=False, output="Update failed" + ) + update_action.update_hooks(latest=True) + + mock_echo.print.assert_called_with("Update failed") diff --git a/tests/application/test_main.py b/tests/application/test_main.py index e52337a6..47c85206 100644 --- a/tests/application/test_main.py +++ b/tests/application/test_main.py @@ -41,4 +41,4 @@ def test_that_scan_is_tbd(mock_container: MagicMock): def test_that_update_is_tbd(mock_container: MagicMock): secureli.main.update() - assert 1 == 1 # TBD + mock_container.update_action.assert_called_once() diff --git a/tests/services/test_updater_service.py b/tests/services/test_updater_service.py index 44ff9664..b42f539d 100644 --- a/tests/services/test_updater_service.py +++ b/tests/services/test_updater_service.py @@ -7,47 +7,62 @@ @pytest.fixture() -def updater_service(mock_pre_commit: MagicMock) -> UpdaterService: - return UpdaterService(mock_pre_commit) +def updater_service( + mock_pre_commit: MagicMock, mock_secureli_config: MagicMock +) -> UpdaterService: + return UpdaterService(pre_commit=mock_pre_commit, config=mock_secureli_config) -##### install_hooks ##### -def test_that_updater_service_install_hooks_updates_and_prunes_with_pre_commit( +##### update ##### +def test_that_updater_service_update_updates_and_prunes_with_pre_commit( updater_service: UpdaterService, mock_pre_commit: MagicMock, ): output = "Some update occurred" - mock_pre_commit.install_hooks.return_value = ExecuteResult( - successful=True, output=output - ) + mock_pre_commit.update.return_value = ExecuteResult(successful=True, output=output) mock_pre_commit.remove_unused_hooks.return_value = ExecuteResult( successful=True, output=output ) - update_result = updater_service.install_hooks() + update_result = updater_service.update() - mock_pre_commit.install_hooks.assert_called_once() + mock_pre_commit.update.assert_called_once() mock_pre_commit.remove_unused_hooks.assert_called_once() assert update_result.successful -def test_that_updater_service_install_hooks_does_not_prune_if_no_updates( +def test_that_updater_service_update_does_not_prune_if_no_updates( updater_service: UpdaterService, mock_pre_commit: MagicMock, ): output = "" - mock_pre_commit.install_hooks.return_value = ExecuteResult( - successful=True, output=output - ) + mock_pre_commit.update.return_value = ExecuteResult(successful=True, output=output) mock_pre_commit.remove_unused_hooks.return_value = ExecuteResult( successful=True, output=output ) - update_result = updater_service.install_hooks() + update_result = updater_service.update() - mock_pre_commit.install_hooks.assert_called_once() + mock_pre_commit.update.assert_called_once() mock_pre_commit.remove_unused_hooks.assert_not_called() assert update_result.successful +def test_that_updater_service_update_handles_failure_to_update_config( + updater_service: UpdaterService, + mock_pre_commit: MagicMock, +): + output = "" + mock_pre_commit.install.return_value = ExecuteResult( + successful=False, output=output + ) + + update_result = updater_service.update() + + mock_pre_commit.install.assert_called_once() + mock_pre_commit.update.assert_not_called() + mock_pre_commit.remove_unused_hooks.assert_not_called() + assert not update_result.successful + + ##### update_hooks ##### def test_that_updater_service_update_hooks_updates_with_pre_commit( updater_service: UpdaterService, @@ -64,3 +79,20 @@ def test_that_updater_service_update_hooks_updates_with_pre_commit( mock_pre_commit.autoupdate_hooks.assert_called_once() assert update_result.successful + + +def test_that_updater_service_update_hooks_handles_no_updates_successfully( + updater_service: UpdaterService, + mock_pre_commit: MagicMock, +): + output = "" + mock_pre_commit.autoupdate_hooks.return_value = ExecuteResult( + successful=True, output=output + ) + mock_pre_commit.remove_unused_hooks.return_value = ExecuteResult( + successful=True, output=output + ) + update_result = updater_service.update_hooks() + + mock_pre_commit.autoupdate_hooks.assert_called_once() + assert update_result.successful