From 18ef13ea39f8a707f42e8e5df91315222e47e00a Mon Sep 17 00:00:00 2001 From: "th.l" <thl-cmk@outlook.com> Date: Sat, 15 Jun 2024 14:01:08 +0200 Subject: [PATCH] update project --- {doc => img}/sample_bug.png | Bin {doc => img}/sample_contract.png | Bin {doc => img}/sample_eox.png | Bin {doc => img}/sample_psirt.png | Bin mkp/inv_cisco_support-0.3.0-20231025.mkp | Bin 24992 -> 24998 bytes .../agent_based}/inv_cisco_bug.py | 0 .../agent_based}/inv_cisco_contract.py | 0 .../agent_based}/inv_cisco_eox.py | 0 .../agent_based}/inv_cisco_psirt.py | 0 .../agent_based}/utils/inv_cisco_support.py | 0 source/bin/ciscoapi/cisco-bug.py | 275 +++++++++ source/bin/ciscoapi/cisco-eox.py | 218 ++++++++ source/bin/ciscoapi/cisco-psirt.py | 187 +++++++ source/bin/ciscoapi/cisco-sn2info.py | 120 ++++ source/bin/ciscoapi/cisco_live_cycle_utils.py | 202 +++++++ source/bin/ciscoapi/ciscoapi.py | 521 ++++++++++++++++++ .../gui}/views/inv_cisco_livecycle.py | 0 {gui => source/gui}/wato/inv_cisco_bug.py | 0 .../gui}/wato/inv_cisco_contract.py | 0 {gui => source/gui}/wato/inv_cisco_eox.py | 0 {gui => source/gui}/wato/inv_cisco_psirt.py | 0 .../packages}/inv_cisco_support | 2 +- .../web}/htdocs/css/inv_cisco_support.css | 0 23 files changed, 1524 insertions(+), 1 deletion(-) rename {doc => img}/sample_bug.png (100%) rename {doc => img}/sample_contract.png (100%) rename {doc => img}/sample_eox.png (100%) rename {doc => img}/sample_psirt.png (100%) rename {agent_based => source/agent_based}/inv_cisco_bug.py (100%) rename {agent_based => source/agent_based}/inv_cisco_contract.py (100%) rename {agent_based => source/agent_based}/inv_cisco_eox.py (100%) rename {agent_based => source/agent_based}/inv_cisco_psirt.py (100%) rename {agent_based => source/agent_based}/utils/inv_cisco_support.py (100%) create mode 100755 source/bin/ciscoapi/cisco-bug.py create mode 100755 source/bin/ciscoapi/cisco-eox.py create mode 100755 source/bin/ciscoapi/cisco-psirt.py create mode 100755 source/bin/ciscoapi/cisco-sn2info.py create mode 100755 source/bin/ciscoapi/cisco_live_cycle_utils.py create mode 100755 source/bin/ciscoapi/ciscoapi.py rename {gui => source/gui}/views/inv_cisco_livecycle.py (100%) rename {gui => source/gui}/wato/inv_cisco_bug.py (100%) rename {gui => source/gui}/wato/inv_cisco_contract.py (100%) rename {gui => source/gui}/wato/inv_cisco_eox.py (100%) rename {gui => source/gui}/wato/inv_cisco_psirt.py (100%) rename {packages => source/packages}/inv_cisco_support (98%) rename {web => source/web}/htdocs/css/inv_cisco_support.css (100%) diff --git a/doc/sample_bug.png b/img/sample_bug.png similarity index 100% rename from doc/sample_bug.png rename to img/sample_bug.png diff --git a/doc/sample_contract.png b/img/sample_contract.png similarity index 100% rename from doc/sample_contract.png rename to img/sample_contract.png diff --git a/doc/sample_eox.png b/img/sample_eox.png similarity index 100% rename from doc/sample_eox.png rename to img/sample_eox.png diff --git a/doc/sample_psirt.png b/img/sample_psirt.png similarity index 100% rename from doc/sample_psirt.png rename to img/sample_psirt.png diff --git a/mkp/inv_cisco_support-0.3.0-20231025.mkp b/mkp/inv_cisco_support-0.3.0-20231025.mkp index 8b6b4a2aaef9fa571f1ba56e0765b696a2329f77..5f2bc5d6ebc0a74a8f9d442d8dc4a166b6f0c8a2 100644 GIT binary patch delta 23306 zcmV(+K;6Hf!vUtl0e>Hh2mpS9ZDs=h?Y(_}+qkhX+Q0QF*yQe|w2@><e%n>6woc+~ zeNN)!+RkqJWc}zzOO(wMnN&r}FU`;Av)`G)3jq+MDA`W3yF_1N5x`&o3<iV2%peFS z(VreUbs+6*ZPCAw%zw8ww|jr;ZtZMtZg#re-5vP8v$Na#laYQIf8?V5y6x1Hi)m|g z`RSvVoJNae8bzNzj-%vs6kRslX3cTOemt57vm}T@tQCY;ewak_Tjw$wFQ&dTiRR9W zARa}|p`Tnw^H0v$zY0cv-2OFmYmSlJz4179_M;CDOhmGXo6aZ-lesraoB(KZe(7OD zP3PV5!O@B1jjw_@f0_qAlzYB7cP#J@UdE0Wj-7aMe(uMK4}V3I<l3A2_V(Y$e$4UR zdv_2!_2=(@Y9c~S=lHO9aQNyiZybKV|LLdw<CFcD4Lhh;(DTb^?yHzPvF|$<NivK3 z8yns%fZoN?)NhZl#WoD;h8@;by959EvG25;(S;YDLqEp;f5cl%6K67yE}d}@duLPl znnaEt(w}YTU(vz|efVer2<7HJv_1*W7l7hzw3s{T+|6O;Vk;u@ua-0O=hUxo1YNwG z&7&*-66ymOF+H=OpflYL_Qo5JaZVPqSu{@+VmX89%&YGxNU-{6UL1@xC_u*%4*AFd z#KsGtpNV%Fe@t&}V=?v<e?+q*QLq2<`b85c_6A812myZV9q%=rcDvmu>QlGxOoAIA zA?nGS|8~0V?e>keh8+yR=)=rh&x6F5Kzi-Y(t+Rvj=ey7hp`*7B?QneVQR)JAptG+ z9jb;D#c=2r;*b(1R0#cRXf{r8=$ty>a+6Xxjvq0Of9N`#M&5Y1m`{;&h*iNEPWvL7 zB~d$?pHt~c02B$ufJ2=Fkqu$S{W1Lhh#Y4~z=wWx)1KX8@%*F|7o264IzX1I6raVx zyafD35=`Tbv>GR5taz%`bkBm2I!)zZTsOoYxs4b_TSbFpmW{(6@~+b5i@R%<E*eXt z>~I=ffBD1F?P%%`X|OCqbG`^@Xs!bPS{+QRN;UHvyY`Z3queB$WtS3>S)p+Jjk0n) zjjmq%XVle;WE_p+jZthP9{B2>-r?E_y-T0(&3YMHSxJygDf>XO|C9~GuF7{mxetqt z+1!GO?WhdG&F;C>;FUj*xwOz}Z?-$FUZ=O&N$vEu)!g=F5Dw@5zZb!r*16k5o^;mL z%FMjcCtwF@{;ap96<E-&I9!B4Gpx`i0lBOFsY;eatNnlCDExZz|7LfyW61xz@S~I3 z18peQRg0+GAuF;eim-D(eq4U;r**1G?^r8WddI>fyhEwaPZZ%Det&$EZUk$8-OlcI zCI5XP`CsP&k39d~-K|~r@AfXIgWl%$W~%@1>~>-PH+zu%1?S&!{&cnorsINg%kqDt z>FJ+07V&%o_QwrBymDr@AQXg~wWm(&dyr|OaS)#OokcQf{eWL;Pa)?u2;X7s_nn^( z-#b6~p+EPg&b!6g6vR57=UnxFsOTQa7=0&Sw17hIk6wcW)pv3Q3Mli^10fE|^nh|& zolOvz2>vLVPC;(mh%cgRE{|S#^D)Zs%CUfovYOki-j7UV96SEyEV*@N!5Hd<<0<qM zim8c<y#73Z4u*@%Gk@;D&g+GX83Xu3tFzVW{MZ+=B8t+m2g=MJA-~UmB|}3*b?UhC zk~y3VTCKl1;s@;Saneu$Z07=)`?IMxf>zI`(HUTP<pH7~?K$VeizrU;(B)hQ$ps3v z&d8fi9sE4R@AXF7#t*ITW?zc05{N8|5^V|)ae&D|90)?MgAk~j(byNm<J5<vxes%O zTxEzSllj=0EJ70T8^$nyv!M&II*kV%cf>Td<6AhIUADz3B8ki_hDNv5*=_YUtrEXl zVyCZ7A^W;1W&4>o2kI4=VrwLo)oN^JohTN6q0U77odlPDtwwk!w=*ClP6*D+V3hC- zq-fk`+q3E7JP6~qy7MZ34^b$+>Iw*TQm@VZbD#@<&Xj`3hi~40oxn)3Untpm0FeFi z!rNgqVSgiMOJXDe_WJ}U_9(h;)*5T!)P~OaLpYVjI(AZVaOB5uUPbUF;4^VIBS7k) z{hJxA&Ed=weSqaUjKbj=Q2r-cEWE3s?W-sZhZd!%pA0c0`!&u&IQJR4Dh5a*!>nYD zq|WCjyqvw@U-#jEpIaJ{8VsD1T^4ot6HiM`Cn6n)HwB2(#bp>b`fLcI8)x8rzM~(( z1W2%r$h3nPtBLBoFeX~_(aaBJp5p=Y=xh3Cz^b27NJDwPRtH4iZ9MPQCk?Hje>3uD ziSx5JUHJR+Ik5K(oVlMY<{{!-OBeY#nB3w*0obAoFuIq2_(>v~mWMAN-Pa!QpwWdt z`s9z@Q>ciA+i^0VP%Zbnf3?25Y<)L&n=EG<0h^<&w7Et@hY3?ppk!YWi@Ll2_Vqia zk~nzjDtJ6~-UR1!rp7sdlb;G~cqy`AQNS9U#x}SgB!HK!kP05T)F=P8349lZ6O|ty zwb87*604Vg%C&pbY$m6a>oyuq10Z0ue*?S(C~YqcBVcu;`oSGG3%96ngB2mn^V$l> zZzspz)X#ygWtWC`HQGxOZ*)-vR4Y>gGLjmWg{tDF!~~ozQUWfYnCA-+RFWV(Bq<_y z{xZd^s0QJulgOw6LX-vL{E{}n?`LD^-uMJo&2i#?UCy!qGbKvTnLk><wtV~ZV#)`v zX!7iZOSkpQ8`uvMKSb^3kq^6l?8kXcS<9Af=?H|`AoO3ss$R_fJy;~SaS#{ute}Fm zO|8*fR>UxpnSvLL84_78#agnKw{=w(JV;gAB|+;o3V{nOpoXO7(x}TyWtup|%h8qK z^BOvT@Kb%?V+?%2{>42inAP=-6ZURa=+6THMe1f?{UG>~+TGm!t-JF_sBkJ8Q8y_# zs)&OMG=oPU62MS=!G50I4q2|YeFfockql9&v+i35n0*4dZrTq3Zq|g(9K-PoDm=eE zrbamT(tALgFGFI;&QkVmm^Dk%h1e*$H~~d}xfCUDHd56^9mF#4GH${skm%kg+502R zLo+Lnoi-dea~WbQL)V*Ld$;j$0U{nB&w=yGo5nunib>*g1bH8e`CFb5NFk>xu2jTy za%<XG<nrR?0xMmEEt$OHr)*-MB7r(Ib3O<eF(X=3K?`A*&1y<Xw&`JI82x1OVcBMX zzNO@xolg}QJJs_%w`rD6!DJ2(brsC<p_1b^%2s=K@UozudV<MT;~7|qYRMxE^iVs} za2ADYnrEj)pE>RZa1xiFdXkIb1O&h_TeN8r`lu8Fer7KyGnB%3eT6q=Mp6FRtz}AL z$FX#JF05;Sur`YL4Fl`EdJI!bEs1S^Ud^<hY3(e!Kyk~^g(6vWbLyoH8nse%b8717 zvf8pbF*2c7!JST;3vs(FWU($^wa``Ke5dl4c0^@sOew)XXoXPQ6SJQ+aSjsuS15RR z8+N<6uAFS2I^%gXb5NB9WFBuEAI1&{L(g%QIIn@=)qM*0)r_{F&Ov`<jh(i$OAAc_ z0gRKG3}FMu`$v=g40nGY^YK2-5!Uf|XAHeUe6}8@*?L3mSv0E~L)`cV#wHJ6V?b&( zVI9$q!^_!_pDh@cXtOfV9R^+X8edp?43^*OPyA$bF%Y&F=^&uOY~Z%L?alTM{C^Mr zx3vrZNB@Ck1+>3^cye&^uhyG`=lwo(@*VUB&M{NcMW?z*Acd3j4H*FdlMN1U0(!fX zehy3nBzhUM%?|Ma0`P5<zz}T#Rg*3eI)5KsGEf}S{UL}z+-(n)AqBZ@YNGBsZeuxU zZ!&?+ssOsBfZo7LcVj!|-Pn?NC)4Qs94|`MdoIg0*K6`sdCSj5tOQ7Z-e?NqrY~=c z<fpH8Blh;-<$gF`b~}c@_L5{CoGlVRE_t0*{<`cKAcyDFT=TK-&v286Nu2suK7Thi z%-pCMS7a!n{v(|!`hU{XJzt!&LkkZ%38a+F#ho1c!SBcrViXT~x%%i@{MmHk+gMS$ zHB*s<ABdO>p3C$~P*~K2k7y&y%3R!<pM`!6kWiXywDDA3cVGN6{CWTA_~7l~{{x4o z!NBRHr?0d$z)F9JH3k(vc27|Ng?|Ih%%9h}4oU9^RMkI->P;>ZKE-@TtwHm9xc%R) z7u!3#z4z~&`URZolIuteFWF3DwB3HI?g%J6Jno0q+v9q2*>J)g*jJm*F%M_!)agTO z@#)7#)A^YL+HH60KQs=z^)7ro+JC*jcf9W$zkPM`%ihtx^CFtv&V%y{pp)ei6CCO0 zK5hcJj@r&k3eD@h@j{@<xom3(+Ukk6UPO}=6GngkQ3J{;Nq}g;L3p=+nx}SGz_rz5 zMBioD?Zvm@=<RX#46UJ|{QB6<X)L#fvPpdGtXMZo?l1~)&>Z{d*x6;$oN5;=Cs8hE z#S!b`mTET1ij@=U+`ppVmw1%JLkP>o(;Z_t0}0l&gH_4^qu6x&%z+X<1hF{}L73D< z`wf5RuY<H7eVg(|MTlLR5O8$L^b&MNwdyjAikwm$#%Wqz0ue?acz7e+3dsd1S3jc; zw1$1YMu#}2ia~x+jdAMJp>Qmqmzv7xHO447t;~~mO9}y{qidzFd>9IbHW*5_;V}sE z3`($$+aZQ<@b>t_zO2YpuCduHHYnCt=ahf2=(x6K#gdX<g#7rgC~>oer)<dq$VM#( z953+~aWU}}#MW^H0`3A;jv(Az`s2XkA@25$_fpJbu7uXE<K!j_B%yA_AWia6*Y)&I zzTYaDnh&V^3N^H^P_$Jqzuw~4w=-Y6E<2$or2yL?^vJt8bv@;nDXq|rRQj{&C@6oK zNT2x^-c=CIhdgFs-TWXsVSLxS@-M{iR*Sdc9^yB=YqlY4Kw~Hy9|3U_8IHcGmV=(u zV-op>3CetZ5|=+JQXsqZ6yozoE^P7p-hAl^7nK4RXc$+n;xMb_cC|~&x5tjU7**1N zimOO)<cwRhFKO<aYxv5uajs=`@O*y)c>i^MEeAXhc$nUS=wMM()vUHFX`G78Z8UE# z|KeGdp~Z$+cp1&4=|EdKIakekp}ZrXQiNMneC3PhQuoKWgl%`n@O{a>v8cJ)ov~VL zmHT3~UhZ9S`pk{xt{8j7R-Px0jXDcT*HU_y+Rl(NUeqi3@`CoRrkstGT#J8OyAawJ z=dhaiYYZ?7wGvmR%=;EV(It<DqH<eR;;yI+z%<T}7%=;V(IZ?Gxoo$=4L}J}!>^Ch zD-s|IX*21%wVWHeGJP#;si`%2Z!=A<x@iN^hVXBFqCa)k$JPk@{(l|6JyfhV`((D6 z6*$o%x~{pUDG2{L8xBRz>_>n1Bucy~FDf-$L)G*t(MmL(ju9^8G64A=<hyK5o&$A0 z-(?PBQ<x`SfSYPG5Y<k0xD;%81r1*tpzq-*q+<!V#TFFtvoWApM2oJYnZge_AR`aO zKwQ}%;9>#m83vX6qR}CSDiZcyyhZ!HGtU&^TQWczd9?~uq$DE$PZWQI^>WJ6rES=C zrPgMOwWb9vao%t$gdkmZPV);um~x>GEGyI6>ooGtn&!?O#5BXG2J7dZ{7BzT6;#u| zT+2VBPh`Ks3OA`CJ8~)MDs*HoYT7z+I_qc2_pVlilq_Z~we0>sV+R1t`y*Ser}?is zmNpH@HxYm)?8O^-98`Zs#K~|aL>FXN`a7Kgn<Ri_l1c-m%!eLKCMXN^+mH(m#YicV zH{V?J6N}ASsHT!tj>u`))KGY=m8^)CO-Z3Kk+t&rwndsv(Mq;N-!cu@3>Srf$QVm{ zMO3d9m=vYUU-l;+$Ukdqm<8c+qm)IE<TK0=C_SQPO@gv!L1ce-qvc!7eQ3~_`OA;( z-ZnR=>9x0+)y|89;}>sR2XFR%+GnOj=~YY?jB1X<ZcG##RIT`_GHqqLvc82=LATjm zV!c7Yn9equSnd{>8B-@~@WfA)NB)(id>OMRS$aED_{G#T^b=!t`djw*Pbt5Ig_NZJ z2QrRgjsI5dqOyNay?t@}pY2|+W7_}h^tzS(Pi6m;^ZpaM@aMh%L}aYJzKW-ugSW>U zbo(RBhJF`MH+5T`oxXD!gm{i)Ej2~F0(N;rzN*-`=%(^sZyn`HCPIyyPBb0+bC@VE zWKXPSBLmPCivIqSX1_#k{{wwI>QY16mJdWK+$L)aWdMI!wf@@ASgm9%mD)*(xB%oW z%wj{pZqI0tAIZpTIGqhnl}l}`ij1mCm{NU%(FJ}-_EROSrL4wM?~pL^%%!+fEPD}I zvz+2yNJLh`2MNz!)EJ$@n6U&Nr{#hkN9Y&}jB!Xydzf7KVfN03+gBlE9vD{uh9j7O z%qBVI90h-|3M3?Jq=GcJtoEB~JjkO|Tv44KBh~&W&G3f=WUit`VoY?X)l`(R^ujgE zBeR#ZoQ};D52$>e0D`E(8pcfi%nnM)C*2=^=n$s|0a++3Pnb1^snyoN+5?drO}h$o z6I@-PCg*Qtc4Add)#(^bCumeS(XY!-^jjRgXY_wdZ5w(f58{nR!`X0t*xBlI8Y~vO zy&(<vzYVMeZgHTJ!z+VDz>-g3PX&&W-o6P^ziX5Lpw3Z$?fEO!6d9?Ja1~!zS~IdA zpg}m!9K0k|ht#0!P4WWlIh^u+U`8YPV3k5EfF51>MYvM-D&VgDB52os3e4sk7OSh7 z-3Ncw9zf>~i!rT%VDnY;vh`FqU(US|1sGUHDpyP*X+{-T+!`~?5H`)&pC|QB)6qVT z{Tn%u4K2nB2d}g#q)<OpH#4OQzrD*e-T)TWQ8ln!mi)-A7RE|hsU*)rQNt6%L^1sh zjC!V|(Uj6*9C^h!k6W*#1ypk;*z+gpo?m}Aj9pO#Ao~;ew=ecv7tb<|yTmq@O0_=h z+X|xnjZ252g3Q!-_+w#PN*7($q+1X}SSjhI*9|UNE-&wrHwyAF-g|fO!YPd?mM(+l z;r@sB8luaa$u_}ivco9_wZJp$ycCdMURz-7jXfw1%SOC8<?PLX%i|ML!rr<ZhR1(< zS>W#uKHL{BfKcHItLEh-OPWmQMd?}G7ad3&V<6s?266l*2gGq{5Wncdcn~H&zJvuL z3NwNy7o!#D{osw9cZ(sM8%CUA@UZ34d>H%qSklUV7G$N@Hw;nl={m8Z-$b^XD>_k> zb{UWx2s7d5w(ruEU)5MkgZ|9RKLvjd2ALi5p<D-aBg&Or2dpN>xo!h?gXHBd1J-(( zSypZ_GsfJ7kG3d=>Z^DP1mhfQ{vu}qKVCA1IugGnb90MPc#dr{orX(UCo4*gwak*2 z+9fN-ztm3M?CfA=ewSWS=_;4Oa*?c2E3zYI!)CQ4CfwHyVl^YxI%y5x&!>N?)o;AG zy!0^EoD%WsS_@0X4nP?@VZ1NYz6Vjv-=EaJ&f??XJoI1+^x~td`NY5_y7H#!r&Ehc zOLNvERTh7GRDM-84$`BDxfIzB`>4R^p<^3YV0_(ZTUwV?2x$gXPk(4k5kJ(GJmdr8 zV3ZgSN15Z9*5FNP1z=OL@!)^#NSk1kTvyX7>OgYl#Dr!m=BB;@6c|g_eJW=w+|%J8 zN^{BzIlT9%N_SY?VAX}PmOEI|L|V&8WjAF4F@7f<QkwEqqr}-Om<B<EG0|Xh5tlNF zXZ3AH@swjRi%)AP#_)Dqc)h4%0dF&a*9%LtcR5)`*O!cwhOuTBot=LIJMI}Hv4V<9 zFAO`XoJw>{GcXN}ED~vcM_j(rR_KDgN-px*;v8+GSH-RqX+{Z;2|aVEqlB8(P!|9< ztBAQOh3|X9gLwr<3bqO!#Fav4kbPqmHOGpl+CAP19uu_Jujd;<RZCmCnJx>>$25e~ z!y|JGKsG}1(qn48!CHTYUC^_x5p4rpjHYvv%Xe-lSO2hYSiq_mFxK0#bX574+mzx& zQxh5rHuS7sXsveFxRf&woz{&OeJeWD5&W-P=(~^w;Dw|GwL!ML;3Z{+Kc`|@$p)3$ z!<gc(V!Ntxcjfyt(@b2{Imq6RC|QQhxTY!B%B>eHj*P@r9rJ&KMvoKtHO;tj7Dgov zy79_0PU02px>x61+3IU>uh3#x{Wu&<7xrik<!IANT632&%QnZg&<vZijW2I@z4r9w z1#^9*4d?;QteflDSu<&*l%+MB%gWaJk!-Ci`{{?VpH?Zlgax%i)emGtEfA=pOMdA@ z=58AqbGHYz17Cmk{@d2>j$!}3y}4Dr|5n+5=h=U=_up#hUo)C6#!Mn=f=D%;I%mGK zh~cC{@)d37@>xa}k0|{lV=+I{%W&LacvBb^`#C1X_yF0Qtk1?wt4Zjgwd?Fwgse1I zr^R~Mh{`zkHM8Q}_>A-z*&`Vgm$KE(4K1iW0n!&i{z!k<YmGAal=Kg1fc@^^CD|{H z=h3WoKGa{jAbr#G_Xn>}S_g-?lfF54arBn{da(tE={F~@UcPisE#OcYeRiwQxB%~s zQ{e9pkKgUTICyoi{}Q3T+IxO<@B;rrn+t5Fx6|!@c+={=+Nb=J*B^e^-tD~HeE#A! z|M2RE?e2e%?|SJ6EYam9ELW6J<mkUzSgEy3jlO$*^x@s>*71wh@z$n`ckW7ujyDd~ z@i{&;hUYZrVo;4GZrl$lQ%d(-MX$Mt06OeV`@*8BtuYTO$KIFF-agiEg2@CxMa?{` z(Sd{}j6tZYu(CJ)_J=#v78};9>J%Y84qNUGy7qsZ^FaGqWEP+|WjFh^prjFLZF%@O zZU#U$0Ja;YbVf6{FRzG(CVWw`uoRp&tnXMGPYYxoTF($sy(sh@(bFLR;gY;yQkovd zVq9uBv?M!s*jVV=fzTIkr{orukE&=<E%@s(NnI-<ZDSlVDETP9pEh!$Td>+Jx@sQm zV4Qz~ZE(23I2WYRFMy0-^cSL75zb_c8!^q`5g5*7$Q=`e{({{-z$QPhyZ^fZ$LbBS zU&=q)xaIK!k$U+C<*&TCEar0c8F2(|nJd~uSz+#bW92G2NBJ-7SX~~{+_EkYSR*(u z@^&!0#cF(XnKzkx$_=%##5ll*-elp24;p_YwuC;@2Ni`4;<GQ2;qz~Mrb_$CjZc6m zPWcdXw>fiXm$#I9b=e<_Bp_)$llRO3(`cCZ@-4rv{#b@9FQ#ECs+xGFB87>i$a$s# z&@hbkcc#K}<Ecpk(grZh8BJ4eP|(z~p6M_oqHHS$8Xe+oB2#bH;io^MXtB}&`YeBn zrmW#7Po6NwKuZ?yMuIulb2r6q&20v_wlqlEjQQ-%)!;~JD=hbjh*K$WGGBmLqqTsh z#sn4ZnXWP1<|kSFm4^&plt(mp9b_b0CDIpv(t0D2QP>=OBY#hs*Qi+(&a4roP}+eE zbAdXS-X|Z*#wOI18(6JesdMTY#=(E#b7)=#Tg=u)H(60UpUr8M@CP~`V89RTmp}63 z*n?4!W9BPEgOPg^Wn?wb7ZFB9o#$;6ZX>n+fjm{a_P?r?CugVv+B5oq_L7>l3V{#D z9Kc2fz_S5%sg{vB4MHb|oHg%t)qz&|WSDt8jtM;ZtK<bX8!O@!kRSnIH?@D3Wg%c< zfNcF_iz^DjEn6;nExp0)V$n<Us8!iYqC-m<6#)}cCd!dU!OUP29cKugAAYhzKR;{c zj5dQ26Hw0F%XG!EhoLBfXEV%W-OvkZXBraFQi2xs#4t3hD_VA(ETXX)KJd>awxa07 zT>Q2{Que}Ch6o$RMnPOyGC+Uo#+iB!O{ZwLE3Ew3v9BrQ8pw^V{V^3_wj;_1SmkrS zJz}xw>+Z+@yVv?Zoz{=T!LJ*?wtju~Yx~z<e?8@FgOoZ<N>)L%<HcFsb;+!>A??Yv z2<dWk#RY&9OsD=ibSn<dL)uU{p)hTeRl^`0G9?;+ChO(~)=odt(XW4Dr*MvoIR3+6 zGMxrKB5;itoBmknH_*~<Kl;ET2V4c7u|knDlM7g<Sf7K%>ZDFM^SLOxn3MB8R`=L- zCE7SfPyz&71cCOL&R<$}N$ohlI<aS@qAaQNN(B_2N46OqKxtlT8xVa?>t1*ZLlmA1 zIuw-Ede;86QN(mtVz_?=JX8@&a@evYM<fM8TkES3_>l<e?h9kE`U2nYV%Pajd-_jm z05~g_#_A69oHSR*W3FTCDwYUZ7c%dPRJW8F3<azh5#^K02YB20tbp+VPOgdI&~oYX z`hOOqt1tl3ErSOvK^^|=i^oNY6kNr|E3=GB5d{J|kH-3mPhD1ddf4oK)?AR<p=_;H zI5h^N7sOralsgbPzw8|y9vuE89z(ex5!>-8)+Zilrx7g0+1$ShqD4%vKB0ICc*3a_ z{pjl0_gl0fhIwd|Z1mvp)!XW}vXe1j8Gq8ZoX&!<op|#{Z9}pD$E5$;>u%xS!v4Rz zy93{Mdpp}-(*7Ts{DaPa)^zUSUjV>q-re5L@&DL{Jk$SUtJACeKfXw^`hRfu4?ol# zK>h-`EsSg#Caq{PiT$MSY>`(8`b7xujWJ%6L^FTR0=47K+{QQ>#T(prp#?M+Z+|E* z7n|h&z`_sW#q-{~1LqZ*TwX`>Pj-(B9+I29DcB?QhdsQIp}KXv!4_7Dj(9`(Y5H`) zkNsRY6sCzm!5El$Peehy)cW0m`Z8$d@#6g4kICc<peX_Sd)rhSZ-=wh@l`I2_%GZ; z*gFDP1;&#?$73+Y;Bq#Pu2^8fCx0UPKs<}W*l&v|BLnY~SFIo1Crz>>iBoIR*cep0 za`n!lMFP`#8H5zDKDtEv9~ed|p7muB0rDBaIGgPsSh3AkXNQJZeK3GIPR9v#&JV}4 zC;;5+@ara}5;K(l+@E-8_=UYe6X7KK<g;*TKzRv#HTyw0tD*V%03*SD9DjT9#p#7V zouLmBwV8S#r5-cn$GjvQKesQl1%yXqGxTS%_}ZY4LsJRf&x~iM!IeK8-2&1>a>Ubt zPNVbTC0fLtb1xb08pFHa=MJY6XE+SPAQ=wpu|J(C*T1Uubg`+lwTfavms&B`9>m5O z5p55LVkRHxWm7R)^@%IG*MApH*bS4L4!hQM`6cr52Tm1~UO|r!qvREa4JeNpyG~|S zF=?m<K&G8y^Wx~><lx2L>wI)-#z-!;a#zZFi{t|1+}+#`7jv|Qa0T-w_bz{Q(F&tc zd+cuzp<r<;4WdB?7G`Kq0w{Uc9uTEBN&NYcjCP=qG&@s@U3xdHD1VPr(A_pm-~iId zAg<g9J%foK-2fk)0H3)S(sQ3sc(YqnY7vTZyJoRC?4g;1EII;)JEvG_S&(K~42hAJ z&wysnz_Owu`cki`kQ$~b)41UOI=m&t3L(aF>&k%LEK^tozFVtlE<W0#H~kfm2pL(L zaaRpmOXIa;6^i#%8h`LzV?~VOHBgGn;S?)K5%7P&Z`kRRTgll+(*S)cgE3?6npL&t zP-8U4wijIdr|?4naZi`D13NxIe1NtXp#1juxTP(nK7i<!kk=@$mIoEKsv(OAILtJV z8uS(QMW3WBJYdme%+x4CA?2^DHm?k2ia`o)Y3qFSC;uLY>wixof9xOy<>t0GkC4}4 z(Es~(R6<(XM}7CYZ#Q*l`8`b?)K+WI;*y0>$ktHbxO1B}x6%92v<WT6#LUS>NZYVe zY(S=86&Vduii!3)7Y!*D+-piCl(c#IDYxm2tg+%KKz&L0Dwq_9ObexO;S$ZGV#C|W z#V1E}S`jA>#ec+$14}W>X{|JNim>)D7%Bksu~Z7>a~xAnToFHfm(;+G$5_jiL`&f! z<-144M+rZgCqiT;-xV?9VMPmZvb0hXqjks0ij@SGhuQM+%9HC?C3^{G$;jokw+e#m zj$uBMmVqp4H`TiAjnFyq1eH)0#Y^UFpa&9=5;!@SSby`F#B9muk|zt3!2{Oy&2yM+ z0KLJ*-#LX?OKCy&JO5xWgl?mNE}E)g+_O!h6p3f|%%NS1eL1>;hg4N~G=p4^XhUWi zOnk0`=$LtV-wDAB-y8eniekh_L^U+3%TF-)d*5+Ad{Kvm^53{=U0+|fP^;U5u94z4 z8$l6qo_{4~Rb6G5(Yu__y--G?7lJ&@F>3BH?t)z*WQ=bxRuuRVmgsG84~>nh?gouw z1`zCM!Q{4RF6aKghZyU}GFI}G+RspR(^`}tGW%BgHmam!l9o@uDPx(#oT$G!f@Z2n z&`1nb)#}@be>LgM>b7QqR1lF^r%t6c12nrHAb(#ersHnUqS*4Fqd^eaa44MA2Nv?r z7RHGl*k(Ullrx!u&V8D;XJ&xH1{%FJG%b_73yD4%(VH0tawyUZk-Dst_%}(zHo^*T zP{;fky^E<qrv2uLe=`f<3+xY18u%>cPR9n-U`ZA@YKbR}vIK)Sa_1PCEf}$z2D*6> zp?_W}&hm6huAb!PDLqcP!>NeMLNe>wE%)$J0<Fr>O)`)~I#VhleRbPxD%<wIk-Ch{ z4o`g&Fbt~_3{&Xv<=sb3dX7=lSYJN)%oa-u3J!ANIcdeWIx-8}pW!i=F1R3%X`EFh z^tODL<uZu<F~=)?@0EXm4y0Sj2qcC?s((~zWLYdN?&F3*bkY!99AXeUqYF4w#rP4B zb9T1{@TTK?qYL`D)qzP$U^`~rk#4iX=}yMf(YVr-SQc{?{Oce)Dkd+HU3w;541vI( zG@t153ia}tsyU_yDyi=SZ5H+D6UJD!VMp^xvJ=gI?6<n7jXa#R9XmVRrNP$}Hh;## zhv)CH7_yB*7zY2LuUP20k54~2E)0!I>TUXezJCbRI>toB;>}MLQ#I1`%OEDt=^(uF zrXbCsXcSBkC*R>0A8}iIK~u1EZ}iCnF}4lsY8sqvw#N+8P}Hic&lqR717tRQSz@#n zL4bnY3l^{NX|@pC{6nKwKp#AO^?$bHK(P4u?XrWn<=huHm!9<XYFsZ;Q0b}f&+3ZG z$VMlfQoBUKRk|J-4wko|vD}?Re{>70C^4^4sRGP(7L4ok-kHKo3z(>d8S1=Hvcg#M zI}Fkw`Cw0Bg#6{L-hEgjhr#(G{>tZx(B*jnPo$Ux8HU))V}91h;?P?f`F|cm1f!Ml zUk#MN^wBV0S7N9?qu>gq8DCwaRC-{H+_H0#_}jq1qLITVv9h2r2FSu7&GBBs91CHt z&lD$uD}J`HME9<yZ2IaMLpJ~s7D7oBP2&w9l?}8R+1TFLfBWJ2Eq}S?;N?bFyhoJf zvi!Xfq)3&~>J;*YxGd88Qh!F9BK$k%waQ3j*e&K+*r-~Cb5ezoVs4g^3MthhoUX-B z6-g%Lk@uuxCiNYXm`#MNNQyRzTW-W^2buzlrix?QlSaX&Lt1Ab;URxJ@bL$3cf0Js zF(vuNRc}LJxxumA;9@I(L-Q+)8`HKz3xV>~Bz=Sr_Z~6htnhf!X@AL?;4eULr0>~m zKsWy`p4@cP@GUDcMm&^Im?%DS4H5F|qjKj@A$8JWrafMabS=-YO1wg%*2H)$>~Rqh znWe7e@@Gf9ctk1_;|&r;m(nmpo$5xH`P^M=Yz_7<q8{lso+b-5N5UGFb>c>!+WOdh zR({<9E#=f>!}q!LSbr6On&OK1OsCRhUe~(?6|>c{=5J3O3}L8Rv(_8V<s!jzas=Dk z#KU(loH`j<k~wSJ8KaR`5;<Mm2buPRtX+F=o?D_YFEgmk)KKU^-NFn&$}YCz#7X!F z*B|%4tyx@g{?udBXE%MaPg^SLJ6e55Q;4ZqO?o+s-R9UrFn??8K^lb8ytJUb7S$A- z0TDB-JgpI&OH7zR>i4kT)|jt!Qr4IgQ8zm+%lG9Q|Bt#e-}v9$-u&(Dkzbgze=SZo zu`H__UogARh}NB3hSNRf($7IMUHuxKOP0R=%4nVbp&Rqx|Juig*f)6p|IYt?02UyI zFqZ|S54y_HUVm%#^JjN|#|vM+G%ULM1%w;c)OzyGO4zTx)pHEX?)Fca?gD+gFt9yP zAQ_7RygI(Av%O6Z0lGi7yY<~hcf0vRdkg;9>@@MqP6OY@dn!PFmc_6G^mn_v-4OM$ zH2=QS?$T!g4ZqXh?afZZy-Rf}HoRuR7+4GU2LzgdGJmlzIm;xQ#e#P_T(50B<RWlq zyuwm9`%OODp@u&NEV-{v%^p&ho+xT)kDrnwp99|=vFX#{3P-5d=yvdAMSr)uorcCF zOqA%F(nPaw7(+26Egdo!z9{;!)jg$$TpN~$ZxndqPcbeH*ZK|(O3R{X1E-;9f5k<C z?0y%wzkiDxq+gI&bzswxKTNH?oQw4F8l$&;Sw=XAuxD${**Z9~&ekulU+A;-Lwmd3 z?RDBTUGSq$Kfby7GA2OGRoiXQTx>0!3ro#%(^oGsiOP?j`eXA!rdQ?0Uv4@ya_a81 zJFexqB^jt0D$&;YAAcUj4i^8h-RY8YWd|Q2t$*EfYOvoo*wHzXalO;j*JGo2{y*NF zD(%3p_LN0(b~`y_pwcB)&VN43B$|#})K};ndrL{8o<F9^+zaq^L5vgbwf>VSczuDc zqU!QdUpR30p1*jx|LUiI9{ksTzkYN0_TB$?bbRvu=U+bj>;Jf=R|{PN8p1&yjdV8i zXn$lBg;!|GsWBcb4*%Uc-bX6gY;V@L8pr#8XgV1O=Rp!@?`*Y>DV(OChtf?uhzVHU z6KP6o&j!v-?x;f}>VWi4W#D#Jn_{=u%J9$z*R$udPytbC?f%@8%SW=R`m`7bWX#Nh z-d^D#n0&^66dn)ZuoBVx*YR)Xs0e-DxPR)kJNQQ|&WG}kVK5d~=wu1Qc9%cvG;NbH zi-lcYP`RB}=%dN`VOC7NLdSkMfTr~>gE(gAGsUNzIfvNKlN$NJ$PAPNru!L?yW7aL zyYX4l(C2iD?+gQM^ITf9+pOwm)LK@Z8cehfWR9s?=8^+y&f!!8VL6&&K7Xf6KYw&0 zjTozY=A8$j$6X>u(R`e#q?KS9yr=<c>c#jd_&K7Bwz3(}+zk(lVQ`rn*T6f85^u`O z#>GIiq9z>mILiMWOx$+|FZ&LWNYnXQYU(9vG@TQK>2R2+msi_>{CB8VK`p!J=bfAX zh$Ij1fU#=aj3y%;slPd0t7x3kseidzb^4H{2I`Mzj{E;2cs9%fcZDY_HIVM#3!#u5 z4)#Ls&b_A6w`Wc_XR~^$+L!d~`!Kbf*E_cLU=eHGA`Y@5!%)#En(w@{rn;*vH+cYV zVdWgpO0bUQ#I!2E)9isMoYN3}WuUYMMae1{3=f9DtjWISR$^PNRs%le7JtXVG|S_+ ze5PP!OhREMqtz(=1Ge*-A<Y>OvM^*lP3JW%H1@?^i7J8YQx4X|H`)8nukL5)=-sbw z+c|*_&<E@Zd?`uuOM~keTfUU|W~@`L(rNVDNq?=p=3F*lq;kbSRtJFb0s4;BD`r_$ z95tv*gJ)<;b$#oM+@EHq$A6O!EyJ2{Xv}X#0BG6v<+?5^s&A=%eK#(u$Y&I)@W1Zz zx8qbh3@8<;t+PyJ6-KfK^C-<QN-&91jDbr^?%wOyZf*JP*;4MXd?6Pv(#KhtYc0kV z7vQ(H=++sr6~-xd!KpVX%i@2w{OIQ41ArFC|LpE|J3TZ0XSds{;(vdBwfLW#JOHQ` z{*xkoVpzr3!N?C$3G>t8d*>(gGK2%tyT#cw7>PXRsz*hcb^~9Nowi1opD^}fv`D5= z^eK(Lh%5@ov@auw^1|&45U`_mG(U$jFVQp~%CI1u+pX?4!Cx=xs2R)B&qT7+B0!do z*C^h;^f7|WxzAl2wSOp%X{<<!`NKYt0rNN>Vng^waFefF)DEW^=}s-86fCbJGw4t? zgyYyax|9(r<tC!_BIB_sR9z{uspnGWn=q=Tc$LDxXT}{eShziU0-A^3gIdH2efBmW zmL?w>asc4xM1(8?^>HsF`~)E6H|YQ(Pdfqg5=e!G{kI>E?0>2ZnWJodGP3A9scAUk zm2#lu70dzZAK>vBpybt)P+3VE7?kzPfsrNiusM?ImF4&x3H?eI6e&x}!cq&nnyM_F zD{g**p(S*=4=0<y7b{C!btd={GN0zx(uc@x6e&<7!3hxo-<YkHKf0+cA=COSaCoBT zJZaq7!h7Z(n}1W9UkNQl=fgobTO<@g)j&tdN%4_}5^^9wsE~g*9hak`<S@kUlr^9F zFyGAlQGyTW3O7HD@+^pi7?F5$So)zW+(3AAC=5Y!#;zIEV6J*)Dh7GPP_qvlcY$X8 z*^h4eJXR~y8LA-Enbe0{Yg5T^8>W3m)}>pk&@VU*Fn_d;`5UyK8H9|HUnE>!I8vi% zc3aOYlLoZZ(H4&s-&y^%1!bmi8U<)GgmLCE7_Y2y=jxX_95$mZ@5(1q5&GaHVv%%# zbf8a7C{lvn^kC!L?M_pg2dVY2G4@b4qeu9O+Z^_;=}^c%PLEVBj~dq|#;1A(V1?{` zU<9vriGSiSiwzYsXhU`CbTmYJBIKZ67y^-uFy5HPXjP1|95Ekbjg)J7Fm-5Yz-S6o z{LHDVWz)B!H}GO?=r$Vg&$VldDUFGgnd8Of4EbQwnPgcZ&5IyOwJC$Xu$XwA80T1F zIodpn*RhYGP@G4F+B@5A%VXb25@eZLjx=kjW`7{L=(A>M_^vPfxlgg$aZMx_+Pcu+ z442Jm$i*p*XKG?sb7?!}PzHJHjE^%CE5YcSeIA#~5R6u~By}(}G6Z4}hXOaBIxf8+ ztjkSEhAtNZGH0enP!~m>u1ocP8rjG(zwz)zj{PdYJY&>lu*&rAz>Hsstd)lbmu1p; z;eXE@cLV01X`AqijNUBkVG(BLnHEyRJZlBh2y1C!;hSd^<)8Uov|4gVHjwI3sjdOS z+Nic^7+B}kW0+cMNo@0Kru|H7XVC>BTZS$a$)cN6FKy7Mm7<$dQ%9H8=GNdqR=6){ z!4`mh2pq-Ev50Yz%U}+|!B#m^*3Fq})PJ&@I}WF~VsqFPYb-<(Y5L?%0jKdTKNu25 z#03#Y6$9H@nBo^Sjk#YN6KY$a!%G9>v+k{nHL=uaZ52!<s&ww>xMHAQXS3ssy<7f% zJq2ge5y~nG>RD72USkRL+H_bKFr?e6!@CrZWjJUU@VL+`kDEdG#wJ+21k$rc@qa3S zayHCxm8zI=a7z|~XjG@N6csC>$pe?pUhadHXmQ<ju@c4_Yg-X(V}VG#i`IyW-123D zFS{&RCbBzuD`m;BmqBP5GA1gyOJ><%GFx+UO89unU)|y84K!~c?H&)K%F!~zNF#Jw zX*(|?Jb+x_C6e1P92&&LVKfnkX@4il2!ziZnVl|XPMw}@iDyWK(osqF-;7(X*sv*D z&s6rZMHd&{wYWQ>igI8J*fd~VNfq}7dbkPE<RFbOp`7lRUID?2V+2Ul>FG18EM;tt zN$EOvy5dxC1y2WReN^?P^*M)D&$dirPjx>H`zFi4kNv3+yj7ACo)2Vev41k%8O5}^ zGitJgiM-zZ70?$dsz(0)o8Jkg)%CiO0Z+IRgJh7e>HG%gHqJZ6i&?NC@y5%xt8^+| z9X#G#34%6)rVOmXQ<jsqUCD#3v12h)^4z&!V-au_UV36r=~M(Ai$Y*oVKTqv5tB#{ zY3R3PD~gIBvs@Y`0h{d#V1KlU8fqI`i>gkdaF@5}egW>W2<t;lW%r$B3t8yjiilB1 zOB0HgPJ@`)hnonw@W0?xSj3m*%;la#n`uN793O<s<kDNM#<U@|>J8oB&-<?}9)s&) zZ9taTe{?!qnfG6JcDF10kIMcd$Npoh)!pt>1KH<am8WANe>MA%r+<vwwU}z4l|(IE z4D(C6!d}mKq_Q5#SdSEhAY^7E>N`DbmzOW}$ai#T3m2+*{xyAcQ3UDc8L&5Nc~)}~ z1GaLCWEnY*FnVM{S9!(uAxuZ^X)<z8Q;`QS5qW6S5PdQV%|n#f=xQaam56#*6~yuZ zCvrM>|FwUdV<}=#o_`*5yo_yz;9P<{Q&kMJ(1_<iysKJ@WXXPg#Bg{8a>ckws}W;R z&}2aW(sbM`ib}Ec<DJP3H2Ir-jF;ZU^MVQ`sH<C;L|}+CA^xzHjr~*45Ry)o+)|1g z$8y(`tjXg0NilD~vx`YFt+3JL-%W~l)-3mv%H@{|N?uoPSbwk1meiExwA#uIW<gzE zYGaMN2eIJH`v*?DOYR@6DqdbyQ}eQK8BM=f;yE`D5(&E=UyyJ*JNLy$Wjhamlk%R| z%6OS~3%s;($I!LkF(^nby<<>TR>6CLO-FVu_nu+-K$k&m8B$q<@^2eT3~{=-<F@{V zuNWTdQbFmFsegtw<W1pTUl5<$W^p$VgRehzl&iQkU>HhVi|2aYL^7c*ok!^st;|45 zW#z<qo5rnR=jYqBA#OCXd}t-r+WoLl1e%7O`K*bIUB@SQWTBRi7g|-%SLyivc=|tz z&iBwBU`zCWo7<g?{%>n%tJ42{qx!!`QtIutx}AGCb$@k#Xmxhrzi1+R1Dp>S;FzLn z;Vw)D(TpdOXnn~o`4bWx__3hQ8EiBivyh>@5SrXJRNdc}(ETNFj>>5Nr2a2Vke7;I zQJi%uOKxX`A^VO-y6ihXkw~x3`cg+IKRvug@!=GRx}Gqjt-P<|Fel)zrGOR;+@7E2 z;y(9}bbox=bHDEZW3DCO{^)VSPGpi`(ocM|W*;oTj@XE4;DRtQ!QrP`?VQh+cu|*~ z@a1#Qd{%}?l{_FVvkb?#6nUsy{?4#`7fcu+FrtQv^gWs1_H)EMtwO+-02$WF_VDc@ z-j*uyMRA*<Shp5!aH7D7SkBwyeR5t`EZpN*?|&|8NVs$;gYV)Gqod+yW*}5iN*xkK zWn)9rs=7&4HaaGgM$LB7n~w9csS^k`m0_x>2N*UYqYHoZX{ffHGbCE)`5`Xbn&Q(y zfAnYG+`DvCko{~%VEdRhl$R-{fMs(}8v_A)mxBctzLjC+weU_#=wU79Dc2b+!fP)? z<9|fu(uPUNX~p!FQr=Q~7R~A|XDEl>B}HDwb=Sx#8qDJ3;5-B=d%nDE6d$q3080vD z81MJ<>5@{>l{Z~piXR*^r4l$S59I(4(prWe7#V)}GzD6fHhhfDK!dQk%6yz7D8t*u z`X4{KhkNcREGPUYs|D2%Ppj6{WwV?Glz*gm+nP!@1$>Q<%(b<xMKBZE4A`E~6#HLa zuyuM6T85f8sQMp#E$CpitQ5Eto&AqLN*H@0o`jA?(4%mSYDu<rY@}AW?~V_SPBd#M z4%0+Y1I(0<sEuSQD<5x098ZJhguc)Gn$COy4H~4-?}UUzhc(NFwXZoBXg*UJO@HXY zOIOo>ver!p1a)rmbfCE92saCToUOLvw5Ms-;%Xzgmf1`eB&-eNe*;Im7l2xb6I5IH zX*@jAa&{gTVb!_hL6A0dtE>i7%#lJh<JXWuJx6(2gsWV^ZAJBJ^f%Ypq1;5QfafwC z?fB)pLX;eIeu<=I3a-dpe<fqo*ne)!)_9XY2rvvie61+MB^&+pO-`k@xv%T*wkjZL z_izv19-}KL{L}idZ*Cw0Js94ss32Y|DGD7`2!KC)pu4sfizF0MKt70|x3!Y=rqP8u zD0szGBJvtLc(!AIW~*N`B&+PXnN_wrrY83;z*S>!42$%1?$j^nqBKUQ*ndzgCHY86 z%<GNW(f@35?wFAia%A)+Ghv)-G}+=edL=7Z@j!?Ubwx0QaZn;u@)2X4jj<_NR?<aY zd0C*wt{W7#$!>$SYgYTsFcnuWX_;LK{A=Qz!d0$@ZOG~cm7CzEqC6*RHHylM-)wi_ z5CC{tL`}}#W|~yl&}$BF{D1t)$P|m}bNaW}=L{}eUtC?)74*@bqCsZ(I%d*2NN;fP zd%3>Tx@Eb;)AlkhpXNPFrNT1br7iHLDBg`>cho1+#GTL7s<>NT)8n8X5_O%1EvT=9 zcNXghMp?o4w|_ro-2cGd_@(3j^|p3<yP5cZo!#pG=MQxM^FyoCZGZJxP(N=xb|QAM zLPY8C>mCQlwC#*w2Z?zGN^*FS&}(<vP!Vg;BfzQxF!ufas%GE-SGV@wvRQj;#@@#D z&et;g#;@_>41VX_=g^jmMKCAMySAaCRQ0}hHul#yQakV8sF;l1Dh{avC6<^hN*HN) zl@jKPTCFtJ%2HEBt$&^fyrR@fAKl2UB_hlU@fOK1RYuC!I{0^Sju3Cn(HnkJ{fA4J zJM%IvAQd;Gj+mreH!XW6{JFe<IqLQ`$6$fPM|?F+Mwc3*N&$|Rr|PpLcH>HF=vi@K z;1!cN|Dc?ik{62!t(>G9Qa2{_d7K#3af_y75P=gs^18~oihmIVQ|>AX);e=|P)-SA zm1pb_^;@asC@U6DmZ>JcPN9)1H^T*1t!x0edJ98;#SJ6q$TFJX1iLm>a0o~jjdB>$ z5h;BFlA)VoqMAVBGgd0?@>`dN^Eao1vD}n=URy2Euhpzib_i>DK~m#j68PicSoLy! z>U5zbx_>yo_<#N<<$F(j-|WDi4UKV?tZJOLDa!0P&YLI%@mR^+5}E(<y}9yz8+rM3 zYQb3;zPlQ(gRbgPvb6N3+rU<k9ugQTAY08D8Q3{Kr|XFB;9&!Vi-s{G$D&-<7!sM$ zGsZz?Y#L)AGq#LDkQv)b#*Uh?quqCAIxadh=o#+y?0-I`po?#zmA3#y`Sc1ty+R`z z<=Y8-JMrh2=(M9{*pQ({2X75jB08d}L1S4#^@}!vP94r&uzx!f;1IxwEMzBVQTxV( zx#0*m<Pq?tB)5qn5G1}{4D3<<MXfcKbdhytAez^^YN?<+nbR}0%DAM{8hjlWyuRbo zOFo=MDSs6U^tw6(SH*SZ&@1ItEvu<p7&<igQVXf<cG~^4!rl!=sc^t_Ct_eyuYc<2 z1RX^!Z%L?ng`BtJvM)^AL<9{xye+^DBU>!rJX@c+Y+k<kwr;{LMvVnkfN@hg+P9&N zLLipqPkjYri~E`FeK@63_PGQL2<<%%eAi0_pnp7<(1ep%r?Kosc{a;*53RoHbxeRV zPh_NzePvL!dE0SeKQ~gJCQQfxrnW~i@gfKpP(txL$#DDHLlJ?!<<Vr1Lp$dYStsI~ zApqOzv#1W=0Eeib_wJQ3IrRREX&;h(U<w8e5Qe`)|9V4MloPa6b8mf12xtu_gHc-C zFMkxCN#lfeD`P(%%>$x!wr}&x9_4#si$$;G3g)2LAz+jdM3SUb3N*qiIkpFL?eABy z25oO|vp2<z_YjwRT_QrvkV>WCBk|TRzUxAN-nqq4VZw%d+CQEamRxDBw719PXyXhL zSy40**a{~82xJ6h<@ZY+p9Iok&xF4lwSU5zPsxT)G1Dz9o$=EY6O`klnR(btwU=B@ z9Ng7gzPVPPG?v!pmXQ>OmW(42HNRY|IwcY5^d-IY<M47eWcnyT58qkh7JL77{PxhH zO<p`D<gfFhUFH#?B~*uTWH^PU!?pBLIhAT945Yyn;PS!3%AD4)svx^^ql{(>qJJ%k z?l9o4;vANq(JwuiDP$gfox>w`$Uc#EI%3XDx%_Lg40q}(dv26&P%b%cv@6;x%QDK3 zT{=PP^2UClx)=L_d9`xO>La5OJgX~|L|X>!z$s&wqa>}L%0Sm2J;P>DMgiy=7v)cz zYOswQM_jR{(*kI(%7hBN#q+(#Q>@VtndE<Y(o!>GQPoAYHC|~fyRFuite7ZnlSPVu z1G5mD?00#8q5cA8_RN?m=Ayq_oyYb{I*(qZ^Qd$lmCmC`=aE$)=$h327f=d_7Av*D zZ(A*pJ<nf!(+B#;JhRun`pNu3PUTC7YU#24yLjz`yK)Pg&aSw3Z91Rt8cPnuEX02* zQOMbCDOCn9BTi$Vmr7%$-b>AUHkF<-<u(9IEAw}XKc1E-n|c1Uu*%(UMJb}rjyU#c z3X<}b?WY=B14~Zt52Ooy#AA`#De}-<c0$3C5KkS8`_w-VoX+w_^9Dn#<h6$5!G=U1 z7LJqkv8-|@LObu1?nc@Un{K5gnO}e2P-?C`s}H7*Z;Gq)nRx6ntR9#&sm;O}(A}Z) zSv}W}XZ(%V(p_dJRd;8;-^?m*;rg>k*6C#46^e$SwnSf=@g!e9oFHug?9jn0PCk(A zb-i%_95Nx7o=m(R-FU8U57bjo)*c1ORx&?kkgYUd*XtN0>-N7HTg3-423~&>|7E+g zY1;qp>~2-@U%sCGFE{`7L(KsAFOb{9<9wL3qR9kKmwji8tb)l{w!7W#Y_&0#i_>>b zF50i#@b57<-9?Ft#cJMrcd%xZ7hzzFR+=c^Wh`$|MJWDe76;;2d@BM=WQ;x2pb?}d zXgZhy$8I==g>f|X`_5SuVSIm3bO~)b(hyWu!KjTwn!{XXdYn)I4~}a4mH1Y7zpH!k zh|C^hrw?a^IzdCHGVwwE`!X6YrtH^u;)UUcI5Y!vr45iaU$ZW`^C(J$!X;c>`txAK zkY*YTz=Vuk&q)@LJ!->vL+yrojs(#t0M-I)Bm@qv!D`~syrH@I7<hj)+CU?(v8-N` zM*-0Ujl$X@lix1!o+08C<;S0k$x?>nIDymT<v&?|9SEQuC*x?L*c2LdvAJ}fw3#fd zqoat_Ad9!X@mOGBsV!`vEw4y(ms*|DUhY4C|C2&$*2!<AmW0ZjQXJzKM+YYdFZN!$ z+OW6b1fK*WZz}1FPmg~U#{SXK+am)2sX0p37T1G%eK;S6(1}tIXtn9p(b~UMT{eDt z<*^U4j#=9vOo`yc6%ThPY~VBfOE#P=R<UaFgoaL+LAXeK8LXVL$pUG>xl|kfK|NQu z>Fjiz?;W*P#=tavXZ&*t>&}VcTo;Z}3&gKt8DC<NexO8~3fF(rDu&N0%9Gw$8ttMq z$pZ(Vk7M2cciXAIYoIYJu=3;mql3NIal@&PqtH#nT^L3i#PJ@H(1wGNv7i;=)vaz< zOu>&}KstPs)I7z1o_>Ds{IIjtVGrYy#hECDLr7hvyCQxnV5Y|03IewwM1lEHV9&^% z7te^$U-maeE{T88u5J1$MOR^Tn$9$>r%RutalchD6PI8xB$tGsz(b0=1k^appd+Tz z$(9Nj)#lnDW|fL#sg1|*)~tc7(X$q1lAJLaF>vE-nR^CbKXD6h*c5!&SxJfpA)3^l zbr=oY_+%s=KDa<0b9@Ot8MWJOEm}A}u9cxZ)~t}iB@utJiQqQhg7UoZmOMvOa?4nh zlV1k>Ar>GPopY%|I>*rDW@*BJ_c#M1kjReC!Q0~v_}_<paSY3jP>Ds!og^&_lXvS% z0RIY$N3@-#-LFTf0w<afem2f9M1yUL`4HX!Z1`>ASLrP`bG)>%(mWY5$NBVtoi@uE zN|Cyx==^`oqp?XngQc?~tPGMRWjpDUTGp<T>$$bBOilXm%v!>2`!@;Fl2UgiT9POU zh}g1(o^25NA^yxVT>Eke%zL+BqJZO90(K_PC!Z}6JA<W|YmPUSGZu_h*Q+KJ@^5A^ zfKW&wUQ&PYqIB2Qq&UO08^N~b&#&;Q15b(TL`8o=@~x5WD5oAgJvjjy<V>ex`5+vi z8D-6g-3*%N^ka#eqZHDGh^9lN#ac83iEK~_C8wcyEpa)D;qz2R$*R-;fA93R;QvTS zga2!_{^s!SB$702vJ3A0>Zitv0*o>Swg_06ZUow}**xeqqEpJQEWxOQo6l_?NuW_^ z-GG132*9olEL4Q1T(n%zYWLAe5`VR<RC%F{Z8M80x<bZT#5u!t$Sz8Qq%BuAUtnr2 zWX4ny-JtPCI{KuPrwCG_0>b8%&{a*Vg?Z5@raYk_M#zY96)`L_TsLJM18MjL-;m`h zWd*4cblp76#>Q<M-lg}+CmpB(HJt?0AS8e7xH|ASay0UcJ2)57CBmhVK;uLd&dCTK z4Kh6FmmDHr=`3nW5@jH1z|5-*-Xqb%K<PRbT*Roo`Cs)7W(wql%OyreOqpuHRJt$` z;GWTkkKO)hO&wm=0Pr*hVIu?K*#P^f)v?aB(gS7qglUb4D26eQQ>h`NR9EE!LzjP@ z71X($UM$w9@BB~^GBaR>mk3OkDj0mXhic_XM;23T{nBLQg*0|-ck4T!1?an`!+62x z%teV5viK`3u%U*ya?xUBF&{C^V8xHgmX8)CZH}-ph0|Lym0*g!F&yjR=#OulrZt&| zz3}o)zc)>IP3QaXKV5tC^Voj=M(%&l^4X=fnhngb)39u6d3&092(wOZimNug!*qU! z`UEo}!m>Xq<M>R}YnY~JRNG>O#w%McCX2xmA5@0e0>GGoJSb%8jv>zYamiwgz)_)6 z0}bV8_DeyvX<jRXY);h@LV<!#!@51!Q}S4G_b<1B(@vvc(adz1yBAR;w>miqi`vpl z@0(Ni`4U_G5?>9w4$l{ip~+{CEYBqFcO3bMVGo&RU&=abaIGNI-^yTbHa4uTz3PFC zlb}u_lP_QwCg~eW=Zm17c=JbX1Ec)xY;Ey)KOOzwZjb&J@qfCzJMewCx4Zq5=uRSk zv7z+%Pun})UKRi8OQbd4e|;T{{4n<W&QFK$ouB;BpL<j1-QsK-j6|Mu)qAw3Vs~4e zA6nflg8u^D)ZYBJLsYB;nG!F??!7yRo%(+Cq3IkS_71{H)O6k*9~_;)@Ap6bw10eZ z@b<8wzxBGQzxDb8!1vmnGz2^IxmlclpEJJ<?nE&rXW+y;n^MFD=-CX$gJSUaTXnsK z?Y^v(F1!??(G(yP%|AIakG!qXKbaQ-c$=+m2Zp0B1AO!MS=m^?AOh;W_Du#NYkcgF zrWl$PM^Ax=_)RbrIz0=5F)X8rXg*fP1V=>+y~SRNg_*-mCFuSTiq~R?dKS%pRvWtR zhv+)lp8MxPOzz06xXc-jgLpReZZDwixK;>6f0%ZRK~6E$IgT%(&YWc4;h#r-yqNM2 z;<<19KAbGV5s#_e&>Ndg7w173L!<G+o8~tAih@cUgjYldC;m-B58Y~@SXjM_<Z>$N z{`2I`YYd^bf4KMj_5SeP-ofF2$^OwX4>^X;flnVg5k0=+6eYXu9G~nRonWN3+CQ=* zSZC2I<Zl*Bp2e)L^S|d9iN%41pbRwgh{6y`L-Lg$IISO<n;-_BlHWEv;_Xny1DZia z5=6&J3`-`WX>)JWFmhfTA3HCmUd&vVv5atJRggkAc+9eRuQsIztL9pN#___AtNx2l zi$znq{6xQ5WTj+2V-bDQ1_A1K|7v}A+4^pbF7VEV8X+L_%#Rf<1zIib5@7YWbmY>C zqc|RpXcz{LOI5Luo5sW9Rpmq!RmDQCB^Xw=B!8bk_W_p0a1>(K`A+Xk&A#gDpIs|) zxqUm)-8D)QDqxZfiKzU4@md7C*~`%DR%Z<=`^Je(rLn&bmQqTFwg#)yWifH3WN0BQ zXHCkoP=iKcmi*F`Hj9IKGW5o<Pr&97jO*ghgRz~;ScSzLBQxSYkiH@@asTQ$)Yrk2 zi@84;p20jm0SRM%?k9sM@Y4%FJ#k#60v3b9LX3(c*l5z2njkoTEb!Tm4SZh2pKoxF z*A0@Gj(O%D$9Lm%iuv7>zp20ey5arJ)nd#b9m$TAn*?*9v>7lnOAo&v-N65*Fz)pK z_8|vyrCbpiMPIP7@6AUS42pGL)|wmJ{IHg^3BnJ5|M$Y5-wr+#1~~NW4`tN#RbxTe zb<No^3zeHBIh@*m?8vXBr-dF&0Rf64))!0*<rYp0m3Z7~Ig69_Xc{fX-fX5W9}Fc= zNZ?-^p9vG39`?tw=}{5SogP`J-1Nv{)uu;&U6E-i6BOHLD8DPL7JJGMBlOTC6m_@h zHngdY3x`E{sFJ_Gz+!BfRb_*sJ=_|DS>1Jv*cxUVB5=@uT4a*@RK!D*PZ71F6v3WA zGD|cX4?|N5Em&Bxq$~UJVgK+Y<vjdx#eO0buOzZ3OWgK_Hy`6$BW-^C??V?pyGf8t z@!64K-R04!-Ns#$R}s&Xl&V5KclM(X4#&b_2h;ViW5j{%lp2+jueQ##8PKAuyb$Gn z@^7R0*q^(9`l9$)v%b^<G{Zvj4cSwL_~G3=8ZSo4!ONG*13vKmwIcf{<(@C5Qy-3D zha5n*q5ZcX_HTe1LT|bkh7pj2e+grPY8!;JACBKnj=iZbK43%mH+<y-EK6OD_L9UK zU9h6rrrxus4NpK8K2E&LnNdc0E5=0~0SqM4Hw`v_Ze7u2coD@3uO$hgV0ON5vj=Z9 zx_Ec+k`zk~n3<AL^jR?Jj57IalI3AqC(;0|W|ao~ItV|VM8^ETKYDEf-+%iWxG>8` zO<wsHGV@pj8HIALtSR5Zo8Wv-fl|aMWoA*9a9}>y0E`+%j1g`b^%QiV6j~p50Ad+_ ztplrnjlbE#p?B%$gP*ZN6epFp!Pp_!t2%W&Pt3hh(s)SjD1a_wkuP|;96o9A#(VR{ z0AFdl^5?z)a}|tyiL3;uH_B@a`A`n0LFf-bAR(QGQn-kN$tG4~V{#btd6}HPG8Cl1 zdswcf0Dc74ra)I_(0~c&50SGPNI5rgfLe}!MVv&JK8smvw(){zs>^A;X1wuu?#FT0 zY+;Xmzh4*dkxvtIdie7mKvARO;~KaMKuE|C4CZdU6S?=o9v?xSXFI?@uT15%#A(ga ztA`d+#}dwXZIaAmBbg4c?DbaF401u9kAW_u3?HC8=Xm3AMWB#7_rm0MnB2}XQ~C>k z&vYpB9)O4Ad`2J}S)U`wwj@i=YXb^6iX_7J!RPOPT5IDoPaqsIIBNB?YY(@&=ZkZ# zXZZlyC)$gD2DBsos4YDUSQg@V!LK!!L6rr|LgsH+ErTnbc`OMP#k%ZnSGIN(i#Yiv zkhjMoAbeR^^pNBdDF$ii;ALD=h(S$%kC0>di_G{p(`dtAq{!A@e#WmCzBfiLMrAXP zM&s8r|H8WpqWO?LATTZ|nqUX>#FAy>@_kz>;xpU9B3@~TfyFVwAv5p%^J>7xs|z2? zfyy~BApTQ)Uk#jP8tqyP2TM4t#A;1gXvt`T-ny4&2;&u2K5rsJc18owdA=im@>c|p zgJThXpW~3Mz!Nw)7MGvQB+@rs4bcBhPX#ekiuB#rm72-oyC$aD2WSnr>|G;KY+ZaM zC(1}i;>~zv%Op#jhukztOPRB6Qe$O$LqOW&2&JTp=z17Uf%^^<Ai4|#h{9jp#KBSy zzpcXeYVh`g;X8KXj2K(8Rg6J@%Yh7IAt_t8juKd)KK%n-(w0Ys8wm|lkl7=KY>XE! z4?TstY)@oFqF_;Ob4)KRX`>71ct5&|V_fgYAWP5&C`z4BtL64NrV3weqsw&q`?lV+ zeQ0kTmb=cd!f{CJt+^<EK0bDkCps*9y(T8XJWgIAgEQg3!W_=~g77|n;K{NuzQ}7L z&0en!dfae21xjBk>TOKC%V2uDW<$7za-FAj?-Y6KL7$QRCF$zk&!-vLhgoNu`oZ$x zaRC*x^pznlE-$_8@%)&7UX$dnvxNT3{@Uv&%ky6RydSSSL#diI&XG;oD^%+e@+>|M z&O;EG=h+4h-W|hUBy$^oYYoSIVj-;;KOenfgtZ3D=*pW~V7~o%@AWD$%bnB8!BRI~ z*f)naWY1*69j~_Ot}Ao+;TjEllLXa5T+FHk6UVSD<pvieBrRr;xgUK<x5MH~g`#^v z%l(d82Fc!~+h*KtRr|lYX6fd$H;87d;h0|6;MzKm3O;P}+?48n=f#vCyGr13wV0wE z{aG-Dn!<ej;ZXYJ4PDlU_=@jQ&H+GR#3|Id<W)w#b{KdNgpQ&`0_WUUUN8lIv<K93 z8wYX0IL%oFXToYY9<TCnLnJPzb4JBuV%UA#%HDhDwD)inV86pJx@=`fOqV^82>BMX z_7thqNNlxHkk@H{Hd#pIY2CLOZbF+1qvCgw%h6EOz7LAaDh(H32j(gLRo!zgWhg`P zd~vQ|RYFa|l!~U3WN|L2LbjQjpXn{W!@X>In*~Rr>^;3a51B}0>`1jqDBm=61QoB= zZpXGJWzS2t_TXd&dwXhWSiF~imCgJMZQ5UPbN*_Z@YmaaY#(D&eZ<Z4u{X&_+3~*Q zmiC3WtjFA%zVIgWIQz_F>?d-+u)gMX?In@o{*A-^aE{-l{J%S!TZ;cb^Z(xJbv7&i z@87on_anG}=QwJ^2C2Jh*Gju03&&nu2Fx{A_vrm+6emZEsUOe$5nD%d{%a__mNU-6 zSLktvMRZVqF9R}@_pk>3ehsx>TtqNnx$ee<RgSN*!0IR1t!D?K4EY-m*>%h)nl3KG zc&M#zbhO}pwspl;Kwh*{UfmUU=&4O)nm5*MBlT-=30t)@Q0}$Uwym}Y){AOc$8OPJ zy?K^(vepca?J7AfFRd!%p@;Wd-XLRMdTY#ctrx<7P%`HdnAc*^vT(B3wNNboiZ&fp z#S%nO(AQ3DHS+oyO5)VXqAuTkB?pp}R#Oiv+F%CqUJf^9^)3#OL5Vyb=ofMLR;)sd zra^zY2<lmP8`&a%OIW~YSYEum@TW7SsA6+1UY6(jlx)=zwI2_PmW`zv_k3m}I{0Y! z7E*|RCZ~Y5nsUk%RL>=sobqyEXY)^uZw+Ozm~R%UlQaB}hi~xbS&o^`={a(#K_1VN zugiML=z?th2JSe}u*92@0e4iI!`~Q-qOVPnQdkoCAdlWz*+%uisUiOfVgEsez$NnE zR%dh9jQ`WyuHyex^4}ju{u967PL3&(LHxykL_)FPrCeBda~upnqDY+ztVKs$@ZdfI z_A=c39*ap!e0xdFfdb1LP7^{@RV~_4URABwc^QlOJ_$%RlYJ{PhSvg@m^DuYUN7Hd z9V+FRshd?(ry$2_b#7})E6Yvoi^V?C!_{R!Ni;Ali$97KC_25iC|FEUIh6{?X;}?_ zi)&c{>{TT~Hmz_3LVx+uCs<l>Vw+dV(u$^bJdb9MFoHRF89QgdT*DAU>RboOg>}ME zr4}YMxdRurnczP#zTVU=AdBR}pZfrDI)w%>cGTSeH!O>Ub`Qvef2Y&v8k>cV)#FB{ zVNE7(e&a&1Ah`09u}<Tac>ITx|NZEH=8NlpcRD+}8U63(b|wE;^8fEy{wK3(iq2-0 z|G$!o7#M(}B4&Ghj#E6kja%i|+}IHq<0Bg{g4Zg#pJ%~A${5UPz^pg!3M})MJ=AU8 zR9FR<=qbPgD>V+w5|RR{n_(!-!fKcat+*aqBCib|I>Ky7AAV~%2(5|{0;2eTZ~=6* z`ndu_>l7U+4B^w8UVFFka1nzvO@RhX*)LsU7U2_vt2PT{QU=`t=uT{eG02xNgNc(M zT;fF}XbKTC?V)-qxe7cTx>M(e35<ibV3!QVV(7PYzQXR^=ZpnZv-a_-kbe}`v9pPy zDn;BNr=EQ=FDq;=;gaRq;;jyUbozm=#4@7pd*l0cULavEb_-I<2xVBo0}c{jm<40Q z2l2K98re}hK725m@lhoh6OkB5YDn46<_|<e%LYrsBmQk`2!P*~R}M(#T<HaVx95Lm zT=u{kfa3Fix6|!yn&<!Ro!#pEU!DK|Naug_tJ#Z3L14cgJOByx+@G_5AA3_W%TI!< zFQ*wG)}UMya#L2i@8fZa)ZjIBS8)!9LK%l}wN&cFEla9?-NINZ<Eo9J_{2vgAP+CE zdRz-TvDC|2Tp#L=)2P=_ou$PEB6mwB6E)$XEN%*~+4Uqj8jEjNOw+*qg>|bs<^r6W z)=9Tt(NE2j7wy9g?_F7cFpBwkg-X3Wc8)UI3*oj)$Ou<mktzIdAv5*4U2?J~I#~T| zUlhmTcYJF|qqX*#)+ElHVv;)ZZXU1m&!vn~i><{t!RA?~DO8wOM5pLnR5+>T<V$K@ z=U~g+wrn?@3NtMtBnug*7wws9adJCFaU9Mx<LKIY(tm0YL5@OyDra$&Q*z0%1Llx7 zuDaH1Ja*KcB2y7BN93$tOUIH3{sa4&Ic_=7Xvx9R58X@0g`mY!%~7u*eAdz`IOlyY zJ+iG2`|A!aRDIa-%>nq3LyS#4+$h`jj`BmO^ziJK1+)=*>Y%uv%hr{`I=H(N5C76! z@-ToD<mx3}5Re^z6Ija_GbJjEsd}f?C0W?N@h)dm5Yi9#KfFI~I=>w6HJ$zAJqPmk zj`zyJ_fY3C^e<=0?ckL+jr}r};^062ptrTGXk!@$byo?+v2@V6c)Ze(mki>8>K}?f zE<<RQXhdi2yPKUdm7cCX;*DkbOGnunb>wkqJZHd3$U5^hVXS@UUyxdF-n3r6TxXnJ zd`!00f%9S4eG~#Oq}o%<WSkag2ftmtAPGz5HG_(fTj>U?lObR*lQ3Wge;zko`)BRM zn?GtBivKSr<KNy^M|uCDySoG5cYC|tFX{gaP5wdW{~{Sjqj+N!$BGxYd~hU=A9((4 z@!z`eAH)B9XLGmm|NbIr<9qUP(B|FwexsI*+w`cMh(^So5IgfmpV0O^9JfZ%6kWTY zb~>X@ujBlg`4@X(@)rkSf1b~MKSVfGE4pdL7v4C!?mL~?4gGHu{`Yh=noLF`CkSId z`3n_Ip<+^@*6wN&scUZ@=3_L0)X89U>!YoI5lTHo30qwmpV2%>itsV(B0h6}yb3bd zT+M4=%>BvmY`XASbG(UZaOtxVR}n<d3;!l*jr~!?q676Ez{{Uw2NYzpE7eX|lMy8p ZlW<`qlb}un4c~V9{{cbmGwuM;0sz-0vjPAB delta 23306 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 ZD3iTT3zNN02@G#L{r_r1yJ`T?0suAJizNU6 diff --git a/agent_based/inv_cisco_bug.py b/source/agent_based/inv_cisco_bug.py similarity index 100% rename from agent_based/inv_cisco_bug.py rename to source/agent_based/inv_cisco_bug.py diff --git a/agent_based/inv_cisco_contract.py b/source/agent_based/inv_cisco_contract.py similarity index 100% rename from agent_based/inv_cisco_contract.py rename to source/agent_based/inv_cisco_contract.py diff --git a/agent_based/inv_cisco_eox.py b/source/agent_based/inv_cisco_eox.py similarity index 100% rename from agent_based/inv_cisco_eox.py rename to source/agent_based/inv_cisco_eox.py diff --git a/agent_based/inv_cisco_psirt.py b/source/agent_based/inv_cisco_psirt.py similarity index 100% rename from agent_based/inv_cisco_psirt.py rename to source/agent_based/inv_cisco_psirt.py diff --git a/agent_based/utils/inv_cisco_support.py b/source/agent_based/utils/inv_cisco_support.py similarity index 100% rename from agent_based/utils/inv_cisco_support.py rename to source/agent_based/utils/inv_cisco_support.py diff --git a/source/bin/ciscoapi/cisco-bug.py b/source/bin/ciscoapi/cisco-bug.py new file mode 100755 index 0000000..bca0cb3 --- /dev/null +++ b/source/bin/ciscoapi/cisco-bug.py @@ -0,0 +1,275 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# Author: thl-cmk[at]outlook[dot]com +# URL : https://thl-cmk.hopto.org +# Date : 2018-01-25 +# +# add on for cisco support api. calls cisco bug api 2.0. +# for more information see: https://developer.cisco.com/docs/support-apis/#bug +# +# 2021-07-24: rewrite for python3.8 +# +import os +import json + +from cisco_live_cycle_utils import ( + configure_logger, + log_message, + expand_path, + get_ids_from_dir, + get_subdirs_from_dir, + remove_empty_sub_dirs, + sleep_random, + move_dir, +) +from ciscoapi import ( + AccessToken, + Settings, + get_bug_by_pid_and_release, +) + + +def main(): + settings = Settings() + access_token = AccessToken(settings.client_id, settings.client_secret, settings.proxies) + configure_logger(log_level=settings.log_level) + + bug_path = settings.base_path + '/bug' + path_found = expand_path(bug_path + '/found') + path_not_found = expand_path(bug_path + '/not_found') + path_request = expand_path(bug_path + '/request') + path_missing = expand_path(bug_path + '/missing') + + pids_requested = {} + pids_refresh = {} + + # get list of bug reports to refresh + pids = get_subdirs_from_dir(path_found) + for pid in pids: + pids_refresh[pid.replace('_', '/')] = ','.join(get_ids_from_dir(path_found+pid, + refresh_time=settings.bug_refresh_found)) + + # move not_found bug reports older then 'refresh_notfound' days to request (for refresh) + move_dir(path_not_found, path_request, refresh_time=settings.bug_refresh_not_found) + + # get list of PIDs requests + pids = get_subdirs_from_dir(path_request) + log_message(f'bug requests (PIDs): {pids}') + for pid in pids: + pids_requested[pid.replace('_', '/')] = ','.join(get_ids_from_dir(path_request+pid)) + log_message(f'bug requests (PIDs and releases): {pids_requested}') + + if len(pids_refresh.keys()) > 0 or len(pids_requested.keys()) > 0: + + reqoptions = [] + + # modified_date + # 1 = Last Week + # 2 = Last 30 Days(default) + # 3 = Last 6 Months + # 4 = Last Year + # 5 = All + reqoptions.append('modified_date=1') +# reqoptions.append('modified_date=3') # for testing + + # severity (default is all) + # 1 = Severity 1 + # 2 = Severity 2 + # 3 = Severity 3 + # 4 = Severity 4 + # 5 = Severity 5 + # 6 = Severity 6 + # reqoptions.append('severity=2') + + # status (default is all) + # O = Open + # F = Fixed + # T = Terminated + # reqoptions.append('status=O') + + # sort_by + # status + # modified_date (recent first, default) + # severity + # support_case_count + # modified_date_earliest (earliest first) + # reqoptions.append('sort_by=severity') + + # reqoptions.append('page_index=2') + + if len(reqoptions) > 0: + reqoptions = '?' + '&'.join(reqoptions) + else: + reqoptions = '' + + # wait random time after startup (load spread) + if settings.wait_after_start: + sleep_random(settings.max_wait_time) + + # first refresh bug reports + for pid in pids_refresh.keys(): + + # get bug records for time frame + bug_records = get_bug_by_pid_and_release( + pid, + pids_refresh.get(pid), + access_token, + reqoptions, + settings=settings + ) + for entry in bug_records: + pid = entry.get('pid') + software_releases = entry.get('software_releases') + status_code = int(entry.get('status_code', 200)) + log_message(f'bug return:PID: {pid}, Status: {status_code}, Version: {software_releases}') + path = expand_path(path_found + pid.replace('/', '_')) + # check if there were was an error, if so go to next pid + if status_code == 200: + bugs = entry.get('bugs', None) + if bugs: # if new/changed bugs found + for bug in bugs: + bug.pop('description') # remove description + for release in software_releases.keys(): # create one bug list per software release + bug_release = software_releases.get(release) + log_message(f'bug found:PID: {pid}, Releases: {release}') + new_bugs = [] + for bug in bugs: + if bug_release in bug.get('known_affected_releases'): + new_bugs.append(bug) + + # open found file + with open(path + release) as f: + try: + bug_record = json.load(f) + except ValueError as e: + log_message(f'{pid}:{release}:snmp_cisco_bug:bug_found:JSON load error: {e}', + level='WARNING') + + found_bugs = bug_record.get('bugs') + for found_bug in found_bugs: + for new_bug in new_bugs: + if found_bug.get('bug_id') == new_bug.get('bug_id'): + found_bug.update(new_bug) # update old bug with new data (changed bugs) + new_bugs.remove(new_bug) + for bug in new_bugs: + found_bugs.append(bug) # add new bugs + bug_record['bugs'] = found_bugs # replace bug list with new list + bug_record['total_records'] = len(found_bugs) # change number of records + + with open(path + release, 'w') as f: + json.dump(bug_record, f) # write updated bug report + + else: + for release in software_releases.keys(): + if os.path.exists(path + release): + os.utime(path + release, None) + + reqoptions = [] + + # modified_date + # 1 = Last Week + # 2 = Last 30 Days(default) + # 3 = Last 6 Months + # 4 = Last Year + # 5 = All + reqoptions.append('modified_date=5') +# reqoptions.append('modified_date=2') # for testing + + # severity (default is all) + # 1 = Severity 1 + # 2 = Severity 2 + # 3 = Severity 3 + # 4 = Severity 4 + # 5 = Severity 5 + # 6 = Severity 6 + # reqoptions.append('severity=2') + + # status (default is all) + # O = Open + # F = Fixed + # T = Terminated + # reqoptions.append('status=O') + + # sort_by + # status + # modified_date (recent first, default) + # severity + # support_case_count + # modified_date_earliest (earliest first) + # reqoptions.append('sort_by=severity') + + # reqoptions.append('page_index=2') + + if len(reqoptions) > 0: + reqoptions = '?' + '&'.join(reqoptions) + else: + reqoptions = '' + + for pid in pids_requested.keys(): + bug_records = get_bug_by_pid_and_release( + pid, + pids_requested.get(pid), + access_token, + reqoptions, + settings=settings + ) + for entry in bug_records: + pid = entry.get('pid') + software_releases = entry.get('software_releases') + status_code = int(entry.get('status_code', 200)) + log_message(f'bug return:PID: {pid}, Status: {status_code}, Version: {software_releases}') + # check if there where was an error, if so go to next pid + if status_code == 200: + bugs = entry.get('bugs', None) + if bugs: + for bug in bugs: + bug.pop('description') + for release in software_releases.keys(): # create one bug list per software release + bug_release = software_releases.get(release) + if bugs: + log_message(f'bug found:PID: %s{pid}, Releases: {release}') + missing = entry.get('missing', {}) + # split bugs by release + release_bugs = { + 'pid': pid, + 'software_release': release, + 'bugs': [], + 'missing': missing + } + for bug in bugs: + if bug_release in bug.get('known_affected_releases'): + release_bugs['bugs'].append(bug) + release_bugs['total_records'] = len(release_bugs['bugs']) + + path = expand_path(path_found + pid.replace('/', '_')) + with open(path + release, 'w') as f: + json.dump(release_bugs, f) + + if len(missing.keys()) != 0: + path = expand_path(path_missing + pid.replace('/', '_')) + with open(path + release, 'w') as f: + json.dump(missing, f) + else: + log_message(f'bug not found:PID: {pid}, Version: {release}') + path = expand_path(path_not_found + pid.replace('/', '_')) + log_message(f'not found: {entry}') + with open(path + release, 'w') as f: + json.dump(entry, f) + pass + + # remove request file + try: + log_message(f'bug delete request:PID: {pid}, Version: {release}') + os.remove(path_request + pid.replace('/', '_') + '/' + release) + except OSError: + pass + + # clean up (remove empty directories) + remove_empty_sub_dirs(path_request) + remove_empty_sub_dirs(path_found) + remove_empty_sub_dirs(path_not_found) + remove_empty_sub_dirs(path_missing) + + +main() diff --git a/source/bin/ciscoapi/cisco-eox.py b/source/bin/ciscoapi/cisco-eox.py new file mode 100755 index 0000000..cfcc556 --- /dev/null +++ b/source/bin/ciscoapi/cisco-eox.py @@ -0,0 +1,218 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# License: GNU General Public License v2 +# +# Author: thl-cmk[at]outlook[dot]com +# URL : https://thl-cmk.hopto.org +# Date : 2017-05-15 +# +# https://developer.cisco.com/docs/support-apis/ +# +# 2021-07-23: rewrite for python 3.8 +# +import json +from cisco_live_cycle_utils import ( + configure_logger, + log_message, + expand_path, + get_ids_from_dir, + remove_ids_from_list, + refresh_ids_from_dir, + remove_ids_from_dir, + sleep_random, +) +from ciscoapi import ( + AccessToken, + Settings, + get_eox_by_pid, + get_eox_by_serials, +) + + +# split pids in known and unknown eox state +def split_pids(eoxr): + eox_known = [] + eox_unkown = [] + + for response in eoxr: + EOXRecord = response.get('EOXRecord') + # PaginationResponseRecord = response.get('PaginationResponseRecord') + # PageIndex = PaginationResponseRecord.get('PageIndex') + # LastIndex = PaginationResponseRecord.get('LastIndex') + # PageRecords = PaginationResponseRecord.get('PageRecords') + # TotalRecords = PaginationResponseRecord.get('TotalRecords') + if EOXRecord is not None: + for PID in EOXRecord: + if PID.get('EOLProductID') != '': + eox_known.append(PID) + log_message(message=f'EOLProductID : {PID.get("EOLProductID")}') + else: + eox_unkown.append(PID) + log_message(f'EOXInputValue : {PID.get("EOXInputValue")}') + return {'eox_known': eox_known, 'eox_unknown': eox_unkown} + + +# split serials, expects a list of EoX Records from get EoX by serial +def split_serials(eoxr): + serials = [] + + for PID in eoxr: + EOLProductID = PID.get('EOLProductID') + log_message(f'serial split Eox: found PID: {EOLProductID}') + EOXInputValue = PID.get('EOXInputValue').split(',') + for serial in EOXInputValue: + log_message(f'found Serial: {serial}') + eox_serial = PID.copy() + eox_serial.update({'EOXInputValue': serial}) + serials.append(eox_serial) + log_message(f'Serial EoX: {eox_serial}') + + return serials + + +# save EoX records to file by PID, +# expects a list of EoX Records from Cisco EoX API 5.0, +# returns a list of saved PIDs +def save_eox(eox, path): + saved = [] + + for PID in eox: + EOLProductID = PID.get('EOLProductID') + # EOLProductID is empty for EoX Records with unknown EoX state (error or not announced) + if EOLProductID == '': + EOLProductID = PID.get('EOXInputValue') + + if EOLProductID: + with open(path + (EOLProductID.replace('/', '_')), 'w') as f: + json.dump(PID, f) + saved.append(EOLProductID) + + return saved + + +def save_serials(eox, path): + """ + Saves EoX records to file by serial number. + Args: + eox: List of EoX Records from Cisco EoX API 5.0 + path: file path where to save the EoX records + + Returns: List of serial numbers of saved EoX records + + """ + + saved = [] + + for serial in eox: + EOXInputValue = serial.get('EOXInputValue') + if EOXInputValue: + with open(path + EOXInputValue, 'w') as f: + json.dump(serial, f) + saved.append(EOXInputValue) + + return saved + + +def main(): + settings = Settings() + access_token = AccessToken(settings.client_id, settings.client_secret, settings.proxies) + configure_logger(log_level=settings.log_level) + + eox_path = settings.base_path + '/EoX' + path_found = eox_path + '/found' + path_not_found = eox_path + '/not_found' + path_request = eox_path + '/request' + + path_request_pid = expand_path(path_request + '/pid') + path_found_pid = expand_path(path_found + '/pid') + path_not_found_pid = expand_path(path_not_found + '/pid') + + path_request_ser = expand_path(path_request + '/ser') + path_found_ser = expand_path(path_found + '/ser') + path_not_found_ser = expand_path(path_not_found + '/ser') + + # create list of PIDs to request EoX status for + pids = get_ids_from_dir(path_request_pid) + log_message(f'pid requests : {pids}') + # remove already known PIDs from list + pids = remove_ids_from_list(pids, path_found_pid) + log_message(f'pid requests : {pids}') + # remove PIDs already requested with unknown EoX status from list + pids = remove_ids_from_list(pids, path_not_found_pid) + log_message(f'pid requests : {pids}') + + # refresh PIDs after 30 days by default + pids = refresh_ids_from_dir(path_not_found_pid, settings.eox_refresh_unknown, pids, True) + log_message(f'pid requests : {pids}') + pids = refresh_ids_from_dir(path_found_pid, settings.eox_refresh_known, pids, False) + log_message(f'pid requests : {pids}') + + # create list of serial numbers to request EoX status for + serials = get_ids_from_dir(path_request_ser) + log_message(f'ser requests : {serials}') + # remove already known serials from list + serials = remove_ids_from_list(serials, path_found_ser) + log_message(f'ser requests : {serials}') + # remove serials already requested with unknown EoX status from list + serials = remove_ids_from_list(serials, path_not_found_ser) + log_message(f'ser requests : {serials}') + + # refresh serials after 30 days by default + serials = refresh_ids_from_dir(path_not_found_ser, settings.eox_refresh_unknown, serials, True) + log_message(f'ser requests : {serials}') + serials = refresh_ids_from_dir(path_found_ser, settings.eox_refresh_known, serials, False) + log_message(f'ser requests : {serials}') + + if pids == [] and serials == []: + log_message('all list are empty. Do nothing.') + return + + # wait random time after startup (load spread) + if settings.wait_after_start: + sleep_random(settings.max_wait_time) + + if pids is not []: + eox = get_eox_by_pid(pids=pids, access_token=access_token, settings=settings) + + # split eox records in a list of known and unknown pid records + eox = split_pids(eox) + + # save known pid reports + pids = save_eox(eox.get('eox_known'), path_found_pid) + # delete requests for known pids + remove_ids_from_dir(pids, path_request_pid) + + # save unknown pid reports + pids = save_eox(eox.get('eox_unknown'), path_not_found_pid) + # delete requests for unknown pids + remove_ids_from_dir(pids, path_request_pid) + # delete pids from known were the status changed to unknown + remove_ids_from_dir(pids, path_found_pid) + + if serials is not []: + eox = get_eox_by_serials(serials=serials, access_token=access_token, settings=settings) + log_message(f'eox by ser: {eox}') + + # split eox records in a list of known and unknown pid records + eox = split_pids(eox) + + # split EoX records for known PIDs in one entry per serial + serials = split_serials(eox.get('eox_known')) + # save EoX records for serials with known EoX state + serials = save_serials(serials, path_found_ser) + log_message(f'EoX Serials: known: {serials}') + # delete requests for known serials + remove_ids_from_dir(serials, path_request_ser) + + # split EoX records for unknown PIDs in one entry per serial + serials = split_serials(eox.get('eox_unknown')) + # save EoX records for serials with known EoX state + serials = save_serials(serials, path_not_found_ser) + # delete requests for unknown serials + remove_ids_from_dir(serials, path_request_ser) + # delete serials from known were the status changed to unknown + remove_ids_from_dir(serials, path_found_ser) + + +main() diff --git a/source/bin/ciscoapi/cisco-psirt.py b/source/bin/ciscoapi/cisco-psirt.py new file mode 100755 index 0000000..876d50c --- /dev/null +++ b/source/bin/ciscoapi/cisco-psirt.py @@ -0,0 +1,187 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# Author: thl-cmk[at]outlook[dot]com +# URL : https://thl-cmk.hopto.org +# Date : 2017-07-10 +# +# https://developer.cisco.com/docs/support-apis/ +# +# 2018-06-06: fixed handling if state changes form found to not_found (delete old psirt found file) +# 2021-07-24: rewritten for python 3.8 +# +# +import ntpath +import os +import json +from typing import List +from dataclasses import dataclass + +from cisco_live_cycle_utils import ( + configure_logger, + log_message, + get_ids_from_dir, + remove_ids_from_list, + refresh_ids_from_dir, + sleep_random, + expand_path, +) +from ciscoapi import ( + AccessToken, + Settings, + get_psirt_by_product_family, + get_psirt_by_iosxe_version, + get_psirt_by_ios_version, +) + + +@dataclass +class Paths: + found: str + not_found: str + request: str + + +@dataclass +class Refresh: + found: int + not_found: int + + +g_logger = None + + +def _psirt_remove_id_file(psirt_path: str, psirt_id: str): + # delete psirt file + try: + log_message(f'delete psirt id file : {psirt_path + psirt_id}') + os.remove(psirt_path + psirt_id) + except OSError: + pass + + +def _psirt_dump_record(psirt_record, psirt_id: str, psirt_path: str, psirt_path_request: str): + with open(psirt_path + psirt_id, 'w') as f: + json.dump(psirt_record, f) + # delete request file + _psirt_remove_id_file(psirt_path_request, psirt_id) + + return + + +def _check_psirt_record(psirt_record, psirt_id, psirt_path_found, psirt_path_request): + """ + + :param psirt_record: + :param psirt_id: + :param psirt_path_found: + :param psirt_path_request: + :returns: + """ + + for advisory in psirt_record.get('advisories'): + # remove unwanted information from advisories + advisory.pop('productNames', None) + advisory.pop('ipsSignatures', None) + advisory.pop('iosRelease', None) + advisory.pop('cvrfUrl', None) + advisory.pop('ovalUrl', None) + advisory.pop('summary', None) + temp_advisory = advisory.copy() + for key in temp_advisory.keys(): + if advisory.get(key, None) in [['NA'], 'NA']: + advisory.pop(key, None) + + _psirt_dump_record(psirt_record, psirt_id, psirt_path_found, psirt_path_request) + + return + + +def _get_psirt_id_list(product_family: str, paths: Paths, refresh: Refresh) -> List[str]: + """ + + @param product_family: + @param paths: Path object with path to founnd/not found/requested PSIRT records + @param refresh: Refresh object with number of days before a PSIRT record needs to be refreshed for found/not found + @return: list of PIDs + """ + # create list of ID's to request PSIRT status for + psirt_id_list = get_ids_from_dir(paths.request + product_family) + log_message(f'psirt requests : {psirt_id_list}') + # remove already found ID's from list + psirt_id_list = remove_ids_from_list(psirt_id_list, paths.found + product_family) + log_message(f'psirt requests : {psirt_id_list}') + # remove not found ID's from list + psirt_id_list = remove_ids_from_list(psirt_id_list, paths.not_found + product_family) + log_message(f'psirt requests : {psirt_id_list}') + + # refresh psirt after 1 day by default + psirt_id_list = refresh_ids_from_dir(paths.not_found + product_family, refresh.not_found, psirt_id_list, True) + log_message(f'psirt requests : {psirt_id_list}') + psirt_id_list = refresh_ids_from_dir(paths.found + product_family, refresh.found, psirt_id_list, False) + log_message(f'psirt requests : {psirt_id_list}') + + return psirt_id_list + + +def _update_psirt_id(psirt_records: list, family_name: str, paths: Paths): + for psirt_record in psirt_records: + if family_name in ['IOS', 'IOS-XE']: + psirt_id = psirt_record.get('version') + else: + psirt_id = psirt_record.get('family') + + if psirt_record.get('advisories') != 'notfound': + _check_psirt_record(psirt_record, psirt_id, paths.found + family_name + '/', + ntpath.sep + family_name + '/') + else: + _psirt_dump_record(psirt_record, psirt_id, paths.not_found + family_name + '/', + paths.request + family_name + '/') + # remove psirt_found file (happens when product family is removed form bug ID) + _psirt_remove_id_file(paths.found + family_name + '/', psirt_id) + return + + +def main(): + settings = Settings() + access_token = AccessToken(settings.client_id, settings.client_secret, settings.proxies) + configure_logger(log_level=settings.log_level) + + refresh = Refresh( + found=settings.psirt_refresh_found, + not_found=settings.psirt_refresh_not_found + ) + + psirt_dir = expand_path(settings.base_path + '/psirt') + paths = Paths( + found=psirt_dir + '/found/', + not_found=psirt_dir + '/not_found/', + request=psirt_dir + '/request/' + ) + + psirt_ios = _get_psirt_id_list('IOS', paths, refresh) + psirt_ios_xe = _get_psirt_id_list('IOS-XE', paths, refresh) + psirt_family = _get_psirt_id_list('family', paths, refresh) + + if (psirt_ios == []) and psirt_ios_xe == [] and psirt_family == []: + log_message('all list are empty. Do nothing.') + return + + # wait random time after startup + if settings.wait_after_start: + sleep_random(settings.max_wait_time) + + if psirt_family is not []: + psirt_records = get_psirt_by_product_family(psirt_family, access_token, settings=settings) + _update_psirt_id(psirt_records, 'family', paths) + + if psirt_ios_xe is not []: + psirt_records = get_psirt_by_iosxe_version(psirt_ios_xe, access_token, settings=settings) + _update_psirt_id(psirt_records, 'IOS-XE', paths) + + if psirt_ios is not []: + psirt_records = get_psirt_by_ios_version(psirt_ios, access_token, settings=settings) + _update_psirt_id(psirt_records, 'IOS', paths) + + +main() diff --git a/source/bin/ciscoapi/cisco-sn2info.py b/source/bin/ciscoapi/cisco-sn2info.py new file mode 100755 index 0000000..6521bd5 --- /dev/null +++ b/source/bin/ciscoapi/cisco-sn2info.py @@ -0,0 +1,120 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# License: GNU General Public License v2 +# +# Author: thl-cmk[at]outlook[dot]com +# URL : https://thl-cmk.hopto.org +# Date : 2017-04-15: +# +# Cisco SN2INFO API Framework +# +# https://developer.cisco.com/docs/support-apis/#serial-number-to-information +# +# 2021-07-23: rewrite for python 3.8 +# +import json + +from cisco_live_cycle_utils import ( + configure_logger, + log_message, + expand_path, + get_ids_from_dir, + remove_ids_from_list, + refresh_ids_from_dir, + remove_ids_from_dir, + sleep_random, +) +from ciscoapi import ( + AccessToken, + Settings, + get_coverage_summary_by_serials, +) + + +def sn2info_split_covered(sn2info_records): + sn2info_covered = [] + sn2info_notcovered = [] + + for response in sn2info_records: + sn2inforecord = response.get('serial_numbers') +# PaginationResponseRecord = response.get('PaginationResponseRecord') +# PageIndex = PaginationResponseRecord.get('PageIndex') +# LastIndex = PaginationResponseRecord.get('LastIndex') +# PageRecords = PaginationResponseRecord.get('PageRecords') +# TotalRecords = PaginationResponseRecord.get('TotalRecords') + if sn2inforecord is not None: + for serial in sn2inforecord: + if serial.get('is_covered') == 'YES': + sn2info_covered.append(serial) + log_message(f'SN2INFO covered : {serial.get("sr_no")}') + else: + sn2info_notcovered.append(serial) + log_message(f'SN2INFO not covered : {serial.get("sr_no")}') + return {'sn2info_covered': sn2info_covered, 'sn2info_notcovered': sn2info_notcovered} + + +def sn2info_save_serials(sn2infos, path): + saved = [] + + for sn2info in sn2infos: + serial = str(sn2info.get('sr_no')) + if serial: + with open(path + serial, 'w') as f: + json.dump(sn2info, f) + saved.append(serial) + + return saved + + +def main(): + settings = Settings() + access_token = AccessToken(settings.client_id, settings.client_secret, settings.proxies) + configure_logger(log_level=settings.log_level) + + sn2info_dir = settings.base_path + '/sn2info' + path_found = expand_path(sn2info_dir + '/found/') + path_not_found = expand_path(sn2info_dir + '/not_found/') + path_request = expand_path(sn2info_dir + '/request/') + + # create list of serial numbers to request SN2INFO status for + sn2info = get_ids_from_dir(path_request) + log_message(f'sn2info requests : {sn2info}') + # remove covered serials from list + sn2info = remove_ids_from_list(sn2info, path_found) + log_message(f'sn2info requests : {sn2info}') + # remove not covered serials from list + sn2info = remove_ids_from_list(sn2info, path_not_found) + log_message(f'sn2info requests : {sn2info}') + + # refresh sn2info serials after 31 days by default + sn2info = refresh_ids_from_dir(path_not_found, settings.sn2info_refresh_not_covered, sn2info, True) + log_message(f'sn2info requests : {sn2info}') + sn2info = refresh_ids_from_dir(path_found, settings.sn2info_refresh_covered, sn2info, False) + log_message(f'sn2info requests : {sn2info}') + + if sn2info is []: + log_message('all list are empty. Do nothing.') + return + + # wait random time after startup + if settings.wait_after_start: + sleep_random(settings.max_wait_time) + + if sn2info is not []: + sn2info_records = get_coverage_summary_by_serials( + serials=sn2info, + access_token=access_token, + settings=settings + ) + log_message(f'sn2info response: {sn2info_records}') + sn2info = sn2info_split_covered(sn2info_records) + serials = sn2info_save_serials(sn2info.get('sn2info_covered'), path_found) + remove_ids_from_dir(serials, path_request) + serials = sn2info_save_serials(sn2info.get('sn2info_notcovered'), path_not_found) + remove_ids_from_dir(serials, path_request) + # delete serials from covered were the status changed to uncovered + remove_ids_from_dir(serials, path_found) + + +main() diff --git a/source/bin/ciscoapi/cisco_live_cycle_utils.py b/source/bin/ciscoapi/cisco_live_cycle_utils.py new file mode 100755 index 0000000..17dc773 --- /dev/null +++ b/source/bin/ciscoapi/cisco_live_cycle_utils.py @@ -0,0 +1,202 @@ +#!/usr/bin/env python +# -*- encoding: utf-8; py-indent-offset: 4 -*- + +# +# 15.04.2017 : Th.L. : Support for Cisco API +# +# https://developer.cisco.com/docs/support-apis/ +# + +import logging +import os +import time +import random +import sys + + +def configure_logger(_path: str = '', _log_to_console: bool = True, log_level: str = 'INFO'): + log_formatter = logging.Formatter('%(asctime)s :: %(levelname)s :: %(name)s :: %(module)s ::%(message)s') + log = logging.getLogger('root') + + numeric_level = getattr(logging, log_level.upper(), None) + if isinstance(numeric_level, int): + logging.getLogger().setLevel(numeric_level) + else: + logging.getLogger().setLevel(logging.WARNING) + + log_handler_console = logging.StreamHandler(sys.stdout) + log_handler_console.setFormatter(log_formatter) + log_handler_console.setLevel(logging.INFO) + log.addHandler(log_handler_console) + + +def log_message(message: str, level: str = 'DEBUG'): + log = logging.getLogger() + if level.upper() == 'CRITICAL': + log.critical(message) + elif level.upper() == 'ERROR': + log.error(message) + elif level.upper() == 'WARNING': + log.warning(message) + elif level.upper() == 'INFO': + log.info(message) + elif level.upper() == 'DEBUG': + log.debug(message) + else: + log.warning(f'unknown log_level: {level}') + + +def sleep_random(max_minutes): + sleep_time = random.randint(1, 60 * max_minutes) + log_message(message=f'{sleep_time} seconds', level='INFO') + time.sleep(sleep_time) + return + + +# read list of files from dir (eq. (P)IDs or SERIALs) (don't change to uppercase) +def get_ids_from_dir(directory, refresh_time: int = 0): + refresh_time = refresh_time * 86400 + start_time = int(time.time()) + ids = [] + for (dir_path, dir_names, file_names) in os.walk(directory): + for entry in file_names: + modify_time = int(os.path.getmtime(dir_path + '/' + entry)) + if (start_time - modify_time) > refresh_time: + ids.append(str(entry).replace('_', '/')) + # do not read subdirs + break + # insert cleanup here (filter unwanted names, chars, etc...) + return ids + + +# read list of subdirectories from directory (PIDs) (don't anything) +def get_subdirs_from_dir(base_dir): + sub_dirs = [] + for (dir_path, sub_dirs, filenames) in os.walk(base_dir): + break + # insert cleanup here (filter unwanted names, chars, etc...) + return sub_dirs + + +# read list of IOS/IOSXE Versions from directory (don't change to uppercase) +def get_version_from_dir(directory): + versions = [] + for (dir_path, dir_names, file_names) in os.walk(directory): + for entry in file_names: + versions.append(str(entry)) + # do not read subdirs + break + # insert cleanup here (filter unwanted names, chars, etc...) + return versions + + +# delete (P)IDs or SERIALs files from directory (requests) +def remove_ids_from_dir(ids, directory): + for entry in ids: + try: + os.remove(directory + entry.replace('/', '_')) + except OSError: + pass + + +# remove (P)IDs or SERIALs from list of (P)ID or serials +def remove_ids_from_list(ids, directory): + known_ids = [] + for (dir_path, dir_names, file_names) in os.walk(directory): + known_ids.extend(file_names) + # do not read subdirs + break + + for known_id in known_ids: + known_id = known_id.replace('_', '/') + for entry in ids: + if known_id == entry: + ids.remove(entry) + return ids + + +# returns al list of ids to refresh, +# expects a directory with ids to check, the time interval, a list of IDs to add +# if remove True it will delete the ID files from refresh_dir +def refresh_ids_from_dir(refresh_dir, refresh_time, ids, remove): + refresh_dir = expand_path(refresh_dir) + # get seconds from # of days (days * 24 * 60 * 60 --> days * 86400) + refresh_time = int(refresh_time) * 86400 + start_time = int(time.time()) + refresh_ids = get_ids_from_dir(refresh_dir) + if refresh_ids is not []: + for entry in refresh_ids: + modify_time = int(os.path.getmtime(refresh_dir + entry.replace('/', '_'))) + if (start_time - modify_time) > refresh_time: + ids.append(entry) + if remove: + try: + os.remove(refresh_dir + entry.replace('/', '_')) + except OSError: + pass + return ids + + +# check if dir exists, if not try to create it. +# return True if dir exists or creation was ok. +# return False if dir not exists and creation was not ok +def check_dir_and_create(directory): + directory = os.path.dirname(directory) + if not os.path.exists(directory): + try: + os.makedirs(directory) + except: + return False + return True + + +# expand homedir and add '/' if necessary and create directory if it not exists +def expand_path(path): + homedir = os.path.expanduser('~') + + if path.startswith('~'): + path = homedir + path[1:] + + if not path.endswith('/'): + path += '/' + + if not check_dir_and_create(path): + return '' + + return path + +# remove empty directories +def remove_empty_sub_dirs(base_dir): + subdirs = get_subdirs_from_dir(base_dir) + for subdir in subdirs: + try: + os.rmdir(base_dir + subdir) + except OSError as e: + log_message(f'can not delete: {base_dir}, Error:{e}') + pass + + +# move contents of source_dir to destination_dir +# only one level deep, lave source_dir +def move_dir(source_dir, destination_dir, **kwargs): + refresh_time = int(kwargs.get('refresh_time', 0)) * 86400 + starttime = int(time.time()) + + sub_dirs = get_subdirs_from_dir(source_dir) + for sub_dir in sub_dirs: + files = get_ids_from_dir(source_dir + sub_dir) + if len(files) > 0: + source_path = expand_path(source_dir + sub_dir) + destination_path = expand_path(destination_dir + sub_dir) + for file in files: + source_file = source_path + file + destination_file = destination_path + file + modify_time = int(os.path.getmtime(source_file)) + if (starttime - modify_time) > refresh_time: + try: + os.rename(source_file, destination_file) # rename (move) contents of not_found to request + except OSError as e: + log_message(message=f'error:{e}, source: {source_file}, destionation: {destination_file}', + level='ERROR') + + remove_empty_sub_dirs(source_dir) diff --git a/source/bin/ciscoapi/ciscoapi.py b/source/bin/ciscoapi/ciscoapi.py new file mode 100755 index 0000000..2ec1e74 --- /dev/null +++ b/source/bin/ciscoapi/ciscoapi.py @@ -0,0 +1,521 @@ +#!/usr/bin/env python +# -*- encoding: utf-8; py-indent-offset: 4 -*- + +# https://developer.cisco.com/docs/support-apis/ +# +# 2017-03-30: Cisco EoX API Framework +# added support for Cisco SN2INFO API (get contract status) +# 2017-07-09: added support for Cisco Psirt API (IOS and IOSXE) +# 2017-07-19: added support for Cisco Software Suggestion API +# 2018-01-25: adding support for Cisco bug api 2.0 +# 2018-09-25: performance improvement "psirt_response.encoding = 'UTF-8'", drops json.loads +# from about 4 min to some seconds +# 2021-07-23: rewritten for python 3.8 +# 2023-06-09: changed for new rest api endpoint (apix.cisco.com) +# refactoring get_token and settings +# some cleanup +# +# supportapis[dash]help[at]cisco[dot]com +# +import requests +import json +import time +from os.path import ( + expanduser, +) +from typing import Dict, List +from cisco_live_cycle_utils import ( + log_message, +) + + +class Settings: + def __init__(self): + conf_file = '~/etc/ciscoapi/ciscoapi.json' + conf_file = expanduser(conf_file) + + with open(conf_file) as f: + try: + self.__settings = json.load(f) + except ValueError as e: + log_message(f'ciscoapi:settings:JSON load error: {e}', level='WARNING') + exit() + except FileNotFoundError as e: + log_message(f'Config file not found {e}.', level='CRITICAL') + exit() + + self.__base_path = '~/var/ciscoapi' + self.__auth_proxy_url = 'https://cmk.bech-noc.de/api/cauthproxy.py' + self.__proxies = {} + self.__wait_after_start = True + self.__max_wait_time = 15 + self.__log_level = 'warning' + self.__eox_refresh_known = 31 + self.__eox_refresh_unknown = 7 + self.__sn2info_refresh_covered = 31 + self.__sn2info_refresh_not_covered = 7 + self.__bug_refresh_found = 2 + self.__bug_refresh_not_found = 1 + self.__psirt_refresh_found = 1 + self.__psirt_refresh_not_found = 1 + self.__suggestion_refresh_found = 31 + self.__suggestion_refresh_not_found = 7 + + if self.__settings['global'].get('http_proxy'): + self.__proxies .update({'http': self.__settings['global'].get('http_proxy')}) + if self.__settings['global'].get('https_proxy'): + self.__proxies .update({'https': self.__settings['global'].get('https_proxy')}) + + @property + def client_id(self) -> str: + return self.__settings['cisco_api']['client_id'] + + @property + def client_secret(self) -> str: + return self.__settings['cisco_api']['client_secret'] + + @property + def proxies(self) -> Dict[str, str]: + return self.__proxies + + @property + def use_system_proxies(self) -> bool: + return self.__settings['global'].get('use_system_proxies', False) + + @property + def use_auth_proxy(self) -> bool: + return self.__settings['cisco_api'].get('use_auth_proxy', False) + + @property + def client_fqdn(self) -> str: + return self.__settings['cisco_api'].get('client_fqdn') + + @property + def root_cert(self) -> bool: + return self.__settings['cisco_api'].get('root_cert', False) + + @property + def auth_proxy_url(self) -> str: + return self.__settings['cisco_api'].get('auth_proxy_url') + + @property + def base_path(self) -> str: + return self.__settings['global'].get('base_path', self.__base_path) + + @property + def wait_after_start(self) -> bool: + return self.__settings['global'].get('wait_after_start', self.__wait_after_start) + + @property + def max_wait_time(self) -> int: + return self.__settings['global'].get('max_wait_time', self.__max_wait_time) + + @property + def log_level(self) -> str: + return self.__settings['global'].get('log_level', self.__log_level) + + @property + def eox_refresh_known(self) -> int: + return self.__settings['eox'].get('refresh_known', self.__eox_refresh_known) + + @property + def eox_refresh_unknown(self) -> int: + return self.__settings['eox'].get('refresh_known', self.__eox_refresh_unknown) + + @property + def sn2info_refresh_covered(self) -> int: + return self.__settings['sn2info'].get('refresh_covered', self.__sn2info_refresh_covered) + + @property + def sn2info_refresh_not_covered(self) -> int: + return self.__settings['sn2info'].get('refresh_not_covered', self.__sn2info_refresh_not_covered) + + @property + def bug_refresh_found(self) -> int: + return self.__settings['bug'].get('refresh_found', self.__bug_refresh_found) + + @property + def bug_refresh_not_found(self) -> int: + return self.__settings['bug'].get('refresh_found', self.__bug_refresh_not_found) + + @property + def psirt_refresh_found(self) -> int: + return self.__settings['psirt'].get('refresh_found', self.__psirt_refresh_found) + + @property + def psirt_refresh_not_found(self) -> int: + return self.__settings['psirt'].get('refresh_not_found', self.__psirt_refresh_not_found) + + @property + def suggestion_refresh_found(self) -> int: + return self.__settings['suggestion'].get('refresh_found', self.__suggestion_refresh_found) + + @property + def suggestion_refresh_not_found(self) -> int: + return self.__settings['suggestion'].get('refresh_not_found', self.__suggestion_refresh_not_found) + + +class AccessToken: + def __init__( + self, + client_id: str, + client_secret: str, + proxies: Dict, + ): + self.__client_id = client_id + self.__client_secret = client_secret + self.__proxies = proxies + self.__use_auth_proxy = None + self.__client_fqdn = '' + self.__root_cert = True + self.__auth_proxy_url = 'https://cmk.bech-noc.de/api/cauthproxy.py' + self.__access_token = '' + self.__lifetime = 0 + self.__time = 0 + self.__auth_headers = { + 'content-Type': 'application/x-www-form-urlencoded', + 'accept': 'application/json' + } + self.__grant_type = 'client_credentials' + self.__auth_url = 'https://id.cisco.com/oauth2/default/v1/token' + self.__verify = True + self.__auth_req_data = { + 'client_id': self.__client_id, + 'client_secret': self.__client_secret, + 'grant_type': self.__grant_type + } + + @property + def token(self) -> str: + if self.__access_token and time.time() < self.__lifetime: + return self.__access_token + else: + self.__time = time.time() + response = requests.post( + self.__auth_url, + headers=self.__auth_headers, + data=self.__auth_req_data, + proxies=self.__proxies, + verify=self.__verify) + + if response.ok: + auth_response = json.loads(response.text) + self.__lifetime = self.__time + int(auth_response.get("expires_in")) - 60 + self.__access_token = auth_response.get("access_token") + return self.__access_token + + +# generic cisco api request for all get info by serialnumber +def get_info_by_serials( + serials: List[str], + access_token: AccessToken, + req_url: str, + max_serials: int, + settings: Settings +): + # locale variablen + max_serial_length = 40 + max_req_per_second = 5 + wait_time = 5 + optimisedserials = [] + serialsstr = '' + count = 1 + info = [] + + # split list of Serials in chunks of max 75 serials, each max 40 bytes length + for serial in serials: + if len(serial) <= max_serial_length: + serialsstr += serial + "," + count += 1 + if count == max_serials: + optimisedserials.append(serialsstr[:-1]) + serialsstr = '' + count = 1 + optimisedserials.append(serialsstr[:-1]) + + headers = {'accept': 'application/json', 'Authorization': 'Bearer ' + access_token.token} + + count = 0 + for serials in optimisedserials: + # Disable invalid certificate warnings. + # requests.packages.urllib3.disable_warnings() + response = requests.get(req_url + serials, headers=headers, proxies=settings.proxies) + count += 1 + # only 5 request per second are allowed + if count == max_req_per_second: + time.sleep(wait_time) + count = 0 + if response.ok: + response.encoding = 'UTF-8' + info.append(json.loads(response.text)) + + return info + + +def get_eox_by_pid(pids: List[str], access_token: AccessToken, settings: Settings): + # local variables + max_pid_length = 240 + max_pids = 20 + max_req_per_second = 5 + wait_time = 5 + optimisedpids = [] + pidstr = '' + count = 1 + eoxr = [] + + # split list of PIDs in chunks of max 240 bytes length + for pid in pids: + if (len(pidstr) + len(pid)) < max_pid_length: + pidstr += pid + "," + count += 1 + if (count == max_pids) or ((len(pidstr) + len(pid)) >= max_pid_length): + optimisedpids.append(pidstr[:-1]) + pidstr = '' + count = 1 + optimisedpids.append(pidstr[:-1]) + headers = {'accept': 'application/json', 'Authorization': f'Bearer {access_token.token}'} + req_url = 'https://apix.cisco.com/supporttools/eox/rest/5/EOXByProductID/1/' + + count = 0 + for productids in optimisedpids: + # Disable invalid certificate warnings. + # requests.packages.urllib3.disable_warnings() + eoxresponse = requests.get(req_url + productids, headers=headers, proxies=settings.proxies) + count += 1 + # only 5 request per second are allowed + if count == max_req_per_second: + time.sleep(wait_time) + count = 0 + if eoxresponse.ok: + eoxr.append(json.loads(eoxresponse.text)) + return eoxr + + +def get_eox_by_serials(serials: List[str], access_token: AccessToken, settings: Settings): + max_serials = 20 + req_url = 'https://apix.cisco.com/supporttools/eox/rest/5/EOXBySerialNumber/1/' + info = get_info_by_serials(serials, access_token, req_url, max_serials, settings) + return info + + +def get_coverage_summary_by_serials(serials: List[str], access_token: AccessToken, settings: Settings): + max_serials = 75 + req_url = 'https://apix.cisco.com/sn2info/v2/coverage/summary/serial_numbers/' + info = get_info_by_serials(serials, access_token, req_url, max_serials, settings) + return info + + +def get_psirt_by_ios_version(psirtios: List[str], access_token: AccessToken, settings: Settings): + info = [] + + headers = {'accept': 'application/json', 'Authorization': 'Bearer ' + access_token.token} + req_url = 'https://apix.cisco.com/security/advisories/ios?version=' + + # requests.packages.urllib3.disable_warnings() + if list(psirtios) is not []: + for ios_version in psirtios: + log_message('request ios_version: %s, time: %s' % (ios_version, time.asctime(time.localtime(time.time())))) + psirt_response = requests.get(req_url + ios_version, headers=headers, proxies=settings.proxies) + if psirt_response.ok: + log_message( + f'ok. ios_version: {ios_version}, ' + f'time: {time.asctime(time.localtime(time.time()))}, ' + f'len: {len(str(psirt_response))}' + ) + # makes json.loads() mutch more faster (from 4 min. down to 1 sec for about 2MB) + psirt_response.encoding = 'UTF-8' + response = (json.loads(psirt_response.text)) + log_message( + f'response loaded: ios_version: {ios_version}, ' + f'time: {time.asctime(time.localtime(time.time()))}, ' + f'len: {len(str(response))}' + ) + info.append({'version': ios_version, 'advisories': response.get('advisories', 'notfound')}) + log_message('ciscoapi:psirt-ios-found: %s' % info) + else: + log_message( + f'notfound. ios_version: {ios_version}, ' + f'time: {time.asctime(time.localtime(time.time()))}, ' + f'len: {len(str(psirt_response))}' + ) + info.append({'version': ios_version, 'advisories': 'notfound'}) + log_message('ciscoapi:psirt-ios-notfound: %s' % info) + return info + + +def get_psirt_by_iosxe_version(psirtios: List[str], access_token: AccessToken, settings: Settings): + info = [] + + headers = {'accept': 'application/json', 'Authorization': 'Bearer ' + access_token.token} + req_url = 'https://apix.cisco.com/security/advisories/iosxe?version=' + + # requests.packages.urllib3.disable_warnings() + if list(psirtios) is not []: + for ios_version in psirtios: + psirt_response = requests.get(req_url + ios_version, headers=headers, proxies=settings.proxies) + if psirt_response.ok: + psirt_response.encoding = 'UTF-8' + response = (json.loads(psirt_response.text)) + info.append({'version': ios_version, 'advisories': response.get('advisories', 'notfound')}) + log_message(f'ciscoapi:psirt-iosxe-found: {info}') + else: + info.append({'version': ios_version, 'advisories': 'notfound'}) + log_message(f'ciscoapi:psirt-iosxe-notfound: {info}') + return info + + +def get_psirt_by_product_family(families: List[str], access_token: AccessToken, settings: Settings): + info = [] + + headers = {'accept': 'application/json', 'Authorization': 'Bearer ' + access_token.token} + req_url = 'https://apix.cisco.com/security/advisories/cvrf/product?product=' + + # requests.packages.urllib3.disable_warnings() + if list(families) is not []: + for family in families: + psirt_response = requests.get(req_url + family, headers=headers, proxies=settings.proxies) + if psirt_response.ok: + psirt_response.encoding = 'UTF-8' + response = (json.loads(psirt_response.text)) + info.append({'family': family, 'advisories': response.get('advisories', 'notfound')}) + log_message('ciscoapi:psirt-family-found: %s' % info) + else: + info.append({'family': family, 'advisories': 'notfound'}) + log_message('ciscoapi:psirt-family-notfound: %s' % info) + return info + + +# get_clean_sn_for_bug_api('ASA5510', '9.1(7)15,8.4(7)30,9.1(6)1') +# return {'9.1(7)15': '9.1(7.15)', '9.1(6)1': '9.1(6.1)', '8.4(7)30': '8.4(7.30)'} +def get_clean_sn_for_bug_api(pid, software_releases): + if software_releases == '' or pid == '': + return {} + software_releases = software_releases.split(',') + clean_sns = {} + + if pid.startswith('ASA'): + # change ASA version from 9.1(2)10 to 9.1(2.10) + for software_release in software_releases: + clean_sn = software_release + if clean_sn[-1] != ')': + clean_sn = clean_sn.split(')') + if len(clean_sn) == 2: + clean_sn = '%s.%s)' % (clean_sn[0], clean_sn[1]) + clean_sns.update({software_release: clean_sn}) + + elif pid.startswith('AIR'): + # change WLC version from 8.5.120.0 to 8.5(120.0) + for software_release in software_releases: + clean_sn = software_release + if clean_sn[-1] != ')': + if len(clean_sn.split('.')) == 4: + clean_sn = clean_sn.split('.') + clean_sn = '%s.%s(%s.%s)' % (clean_sn[0], clean_sn[1], clean_sn[2], clean_sn[3]) + clean_sns.update({software_release: clean_sn}) + + else: + for software_release in software_releases: + clean_sn = software_release + if clean_sn.startswith('16.0'): + # remove leading zeros 16.09.01 to 16.9.1 + clean_sn = clean_sn.split('.') + for x in range(0, len(clean_sn)): + clean_sn[x] = clean_sn[x].lstrip('0') + if clean_sn[x] == '': + clean_sn = '0' + clean_sn = '.'.join(clean_sn) + # remove trailing A-Za-z + while clean_sn[-1].upper() in 'ABCDEFGHIJKLMNOPQRSTUVWXYZ': + clean_sn = clean_sn[:-1] + elif clean_sn.startswith('03.0'): + # convert version from 03.03.04SE to 3.3(4)SE + clean_sn = clean_sn.split('.') + for x in range(0, len(clean_sn)): + clean_sn[x] = clean_sn[x].lstrip('0') + if clean_sn[x] == '': + clean_sn = '0' + digits = '' + for x in clean_sn[2]: + if x.isdigit(): + digits += x + clean_sn[2] = clean_sn[2].replace(digits, '') + clean_sn = '%s.%s(%s)%s' % (clean_sn[0], clean_sn[1], digits, clean_sn[2]) + clean_sns.update({software_release: clean_sn}) + + return clean_sns + + +def get_bug_by_pid_and_release(pid, release, access_token: AccessToken, reqoptions, settings: Settings): + info = [] + + headers = {'accept': 'application/json', 'Authorization': 'Bearer ' + access_token.token} + req_url = 'https://apix.cisco.com/bug/v2.0/bugs/products/product_id/' + + if release != '': + software_releases = get_clean_sn_for_bug_api(pid, release) + clean_sn = ','.join(software_releases.values()) + missing = {} + bug_response = requests.get( + url=req_url + f'{pid}/software_releases/{clean_sn}{reqoptions}', + headers=headers, + proxies=settings.proxies + ) + if bug_response.ok: + bug_response.encoding = 'UTF-8' + response = (json.loads(bug_response.text)) + bug_list = response.get('bugs') + pagination_record = response.get('pagination_response_record') + last_page = int(pagination_record.get('last_index')) + total_records = int(pagination_record.get('total_records')) + log_message(message=f'PID: {pid}, Version: {clean_sn}, Total records: {total_records}, Pages: {last_page}', + level='INFO') + if last_page > 1: + if reqoptions != '': + reqoptions = reqoptions + '&page_index=' + else: + reqoptions = '?page_index=' + for page in range(2, last_page + 1): + # time.sleep(2) + page_options = reqoptions + '%s' % page + bug_response = requests.get(req_url + f'{pid}/software_releases/{clean_sn}{page_options}', + headers=headers, proxies=settings.proxies) + if bug_response.ok: + response = (json.loads(bug_response.text)) + bug_list += response.get('bugs') + else: + bug_response.encoding = 'UTF-8' + status_code = bug_response.status_code + reason = bug_response.reason + url = bug_response.url + text = bug_response.text + log_message(message=f'ciscoapi error: {status_code}, {reason}, Page: {page}, LastPage: ' + f'{last_page}, URL: \'{url}\'. Text: \'{text}\'', + level='WARNING') + missing.update({page: {'status_code': status_code, + 'reason': reason, + 'url': url, + 'text': text}}) + info.append({'pid': pid, + 'software_releases': software_releases, + 'bugs': bug_list, + 'total_records': total_records, + 'missing': missing}) + log_message('ciscoapi:bug-found: %s' % info) + else: + bug_response.encoding = 'UTF-8' + status_code = bug_response.status_code + reason = bug_response.reason + url = bug_response.url + text = bug_response.text + page = 'ALL' + log_message(f'ciscoapi error: {status_code}, {reason}, URL: \'{url}\'. Text: \'{text}\'', level='WARNING') + missing.update({page: {'status_code': status_code, + 'reason': reason, + 'url': url, + 'text': text}}) + info.append({'pid': pid, + 'software_releases': software_releases, + 'status_code': status_code, + 'reason': reason, + 'missing': missing}) + return info diff --git a/gui/views/inv_cisco_livecycle.py b/source/gui/views/inv_cisco_livecycle.py similarity index 100% rename from gui/views/inv_cisco_livecycle.py rename to source/gui/views/inv_cisco_livecycle.py diff --git a/gui/wato/inv_cisco_bug.py b/source/gui/wato/inv_cisco_bug.py similarity index 100% rename from gui/wato/inv_cisco_bug.py rename to source/gui/wato/inv_cisco_bug.py diff --git a/gui/wato/inv_cisco_contract.py b/source/gui/wato/inv_cisco_contract.py similarity index 100% rename from gui/wato/inv_cisco_contract.py rename to source/gui/wato/inv_cisco_contract.py diff --git a/gui/wato/inv_cisco_eox.py b/source/gui/wato/inv_cisco_eox.py similarity index 100% rename from gui/wato/inv_cisco_eox.py rename to source/gui/wato/inv_cisco_eox.py diff --git a/gui/wato/inv_cisco_psirt.py b/source/gui/wato/inv_cisco_psirt.py similarity index 100% rename from gui/wato/inv_cisco_psirt.py rename to source/gui/wato/inv_cisco_psirt.py diff --git a/packages/inv_cisco_support b/source/packages/inv_cisco_support similarity index 98% rename from packages/inv_cisco_support rename to source/packages/inv_cisco_support index 34bca19..71a5323 100644 --- a/packages/inv_cisco_support +++ b/source/packages/inv_cisco_support @@ -40,5 +40,5 @@ 'suggested software', 'version': '0.3.0-20231025', 'version.min_required': '2.2.0b1', - 'version.packaged': '2.2.0p11', + 'version.packaged': '2.2.0p24', 'version.usable_until': '2.3.0b1'} diff --git a/web/htdocs/css/inv_cisco_support.css b/source/web/htdocs/css/inv_cisco_support.css similarity index 100% rename from web/htdocs/css/inv_cisco_support.css rename to source/web/htdocs/css/inv_cisco_support.css -- GitLab