From 774904be961b694a40b94aad59fd49d56f2c3f62 Mon Sep 17 00:00:00 2001 From: "th.l" <thl-cmk@outlook.com> Date: Sat, 2 Dec 2023 18:02:49 +0100 Subject: [PATCH] update project --- README.md | 2 +- bin/topology_data/create_topology_args.py | 5 +- bin/topology_data/create_topology_classes.py | 119 +++++++++++++++++-- bin/topology_data/create_topology_data.py | 24 ++-- mkp/create_topology_data-0.4.0-20231130.mkp | Bin 0 -> 13382 bytes packages/create_topology_data | 2 +- 6 files changed, 126 insertions(+), 26 deletions(-) create mode 100644 mkp/create_topology_data-0.4.0-20231130.mkp diff --git a/README.md b/README.md index 9bfcfca..e42d629 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[PACKAGE]: ../../raw/master/mkp/create_topology_data-0.3.0-20231128.mkp "create_topology_data-0.3.0-20231128.mkp" +[PACKAGE]: ../../raw/master/mkp/create_topology_data-0.4.0-20231130.mkp "create_topology_data-0.4.0-20231130.mkp" Network Visualization data creation tool from inventory data This script creates the topology data file needed for the [Checkmk Exchange Network visualization](https://exchange.checkmk.com/p/network-visualization) plugin by Andreas Boesl and [schnetz](https://exchange.checkmk.com/u/schnetz).\ diff --git a/bin/topology_data/create_topology_args.py b/bin/topology_data/create_topology_args.py index 8fde0d9..f0b696b 100755 --- a/bin/topology_data/create_topology_args.py +++ b/bin/topology_data/create_topology_args.py @@ -63,12 +63,13 @@ def parse_arguments() -> arg_Namespace: parser.add_argument( '-b', '--backend', # nargs='+', - choices=['LIVESTATUS', 'FILESYSTEM', 'MULTISITE'], + choices=['LIVESTATUS', 'FILESYSTEM', 'MULTISITE', 'RESTAPI'], default='LIVESTATUS', help=f'Backend used to retrieve the topology data\n' f' LIVESTATUS is the default for performance reasons -> only local site\n' f' FILESYSTEM fetches the data directly form the inventory files -> use in distributed environments\n' - f' MULTISITE like LIVESTATUS but for distribute environments -> may become the default', + f' MULTISITE like LIVESTATUS but for distribute environments -> may become the default\n' + f' RESTAPI uses the CMK REST API.', ) parser.add_argument( diff --git a/bin/topology_data/create_topology_classes.py b/bin/topology_data/create_topology_classes.py index 6b3a176..edbb2bc 100755 --- a/bin/topology_data/create_topology_classes.py +++ b/bin/topology_data/create_topology_classes.py @@ -11,11 +11,12 @@ from os import environ from pathlib import Path -from time import strftime +from time import strftime, time_ns from typing import Dict, List, Any, NamedTuple from enum import Enum, unique from abc import abstractmethod from ast import literal_eval +from requests import session import livestatus @@ -193,6 +194,10 @@ class HostCache: self.__inventory_pre_fetch_list: List[str] = [ PATH_INTERFACES, ] + self._count = 0 + self._debug = debug + if debug: + print('init HOST_CACHE') @abstractmethod def get_inventory_data(self, host: str, debug: bool = False) -> Dict[str, str] | None: @@ -221,7 +226,15 @@ class HostCache: def __fill_cache(self, host: str): # pre fill inventory data + if self._debug: + self._count += 1 + _pre_query = time_ns() + inventory = self.get_inventory_data(host=host) + + if self._debug: + print(f'{(time_ns() - _pre_query) / 1e9}|{self._count:0>4}|inventory|{host}') + if inventory: self.__cache[host][CacheItems.inventory.value] = {} self.__cache[host][CacheItems.inventory.value].update({ @@ -233,8 +246,16 @@ class HostCache: else: self.__cache[host][CacheItems.inventory.value] = None self.__cache[host][CacheItems.interfaces.value] = {} + + if self._debug: + self._count += 1 + _pre_query = time_ns() + self.__cache[host][CacheItems.interfaces.value][CACHE_INTERFACES_ITEM] = self.get_interface_items(host) + if self._debug: + print(f'{(time_ns() - _pre_query) / 1e9}|{self._count:0>4}|items|{host}') + def get_data(self, host: str, item: CacheItems, path: str): if host not in self.__cache.keys(): self.__cache[host]: Dict[str, Any] = {} @@ -262,6 +283,8 @@ class HostCacheLiveStatus(HostCache): print(f'exception: {e}') return return data + if debug: + print(f'Device: {host}: no inventory data found!') def get_interface_items(self, host: str, debug: bool = False) -> List: """ @@ -307,24 +330,27 @@ class HostCacheFileSystem(HostCache): data = literal_eval(inventory_file.read_text()) return data else: + if debug: + print(f'Device: {host}: not found in inventory data path!') return None def get_interface_items(self, host: str, debug: bool = False) -> List: """ - Sample autochecks data we keep only the item + Sample autochecks data, we keep only the item [ - {'check_plugin_name': 'if64', 'item': 'Fa0', 'parameters': {'discovered_oper_status': ['2'], ...}}, - {'check_plugin_name': 'if64', 'item': 'Fa1/0/1', 'parameters': {'discovered_oper_status': ['1'], ...}}, - {'check_plugin_name': 'if64', 'item': 'Fa1/0/10', 'parameters': {'discovered_oper_status': ['2'], ...}}, - {'check_plugin_name': 'if64', 'item': 'Fa1/0/11', 'parameters': {'discovered_oper_status': ['2'], ...}}, - {'check_plugin_name': 'if64', 'item': 'Fa1/0/12', 'parameters': {'discovered_oper_status': ['1'], ...}} + {'check_plugin_name': 'if64', 'item': 'Fa0', 'parameters': {'discovered_oper_status': ['2'], ...}},\n + {'check_plugin_name': 'if64', 'item': 'Fa1/0/1', 'parameters': {'discovered_oper_status': ['1'], ...}},\n + {'check_plugin_name': 'if64', 'item': 'Fa1/0/10', 'parameters': {'discovered_oper_status': ['2'], ...}},\n + {'check_plugin_name': 'if64', 'item': 'Fa1/0/11', 'parameters': {'discovered_oper_status': ['2'], ...}},\n + {'check_plugin_name': 'if64', 'item': 'Fa1/0/12', 'parameters': {'discovered_oper_status': ['1'], ...}}\n ] Args: - host: - debug: + host: name of the host object in cmk to fetch the data for + debug: output debug information Returns: + List of interface service items """ __autochecks_path = 'var/check_mk/autochecks' @@ -348,12 +374,14 @@ class HostCacheMultiSite(HostCache): super().__init__(debug=debug) self.__sites = {} self.get_sites(debug=debug) + if debug: + print('Create livestatus connection(s)') self.__c = livestatus.MultiSiteConnection(self.__sites) # self.__c.set_prepend_site(False) # is default # self.__c.parallelize = True # is default self.__dead_sites = [site['site']['alias'] for site in self.__c.dead_sites().values()] if self.__dead_sites: - self.__dead_sites = ', '.join( self.__dead_sites) + self.__dead_sites = ', '.join(self.__dead_sites) print(f'WARNING: use of dead site(s) {self.__dead_sites} is disabled') self.__c.set_only_sites(self.__c.alive_sites()) @@ -416,7 +444,74 @@ class HostCacheMultiSite(HostCache): items = [] for host, description, check_command in data: items.append(description[10:]) # remove 'Interface ' from description - if debug: - print(f'Interfaces items found: {len(items)} an host {host}') + if items: + print(f'Interfaces items found: {len(items)} an host {host}') + else: + print(f'No Interfaces items found for host {host}') return items + + +class HostCacheRestApi(HostCache): + def __init__(self, debug: bool = False): + super().__init__(debug=debug) + try: + self.__secret = Path(f'{OMD_ROOT}/var/check_mk/web/automation/automation.secret').read_text().strip('\n)') + except FileNotFoundError as e: + print(f'automation.secret not found, {e}') + exit() + self.__hostname = 'localhost' + self.__site = OMD_ROOT.split('/')[-1] + self.__api_url = f"http://{self.__hostname}/{self.__site}/check_mk/api/1.0" + self.__user = 'automation' + if debug: + print('Create REST API session') + self.__session = session() + self.__session.headers['Authorization'] = f"Bearer {self.__user} {self.__secret}" + self.__session.headers['Accept'] = 'application/json' + + def get_inventory_data(self, host: str, debug: bool = False) -> Dict[str, str] | None: + # self._count += 1 + + __query = '{"op": "=", "left": "name", "right": "' + host + '"}' + resp = self.__session.get( + f"{self.__api_url}/domain-types/host/collections/all", + params={ + "query": __query, + "columns": ['mk_inventory'], + }, + ) + if resp.status_code == 200: + # print(f'{resp.elapsed}|{self._count:0>4}|inventory|{host}') + try: + return resp.json()['value'][0]['extensions']['mk_inventory'] + except IndexError: + return None + else: + if debug: + print(f'Device: {host}: no inventory data found!') + return None + + def get_interface_items(self, host: str, debug: bool = False) -> List: + # self._count += 1 + + __query = '{"op": "~", "left": "description", "right": "Interface "}' + resp = self.__session.get( + f"{self.__api_url}/objects/host/NX01/collections/services", + params={ + "query": __query, + "columns": ['host_name', 'description', 'check_command'], + }, + ) + + if resp.status_code == 200: + # print(f'{resp.elapsed}|{self._count:0>4}|items|{host}') + + items = [service['extensions']['description'][10:] for service in resp.json()['value']] + if debug: + print(f'Interfaces items found: {len(items)} an host {host}') + return items + else: + if debug: + print(f'No Interfaces items found for host {host}') + return [] diff --git a/bin/topology_data/create_topology_data.py b/bin/topology_data/create_topology_data.py index 96198b1..445249d 100755 --- a/bin/topology_data/create_topology_data.py +++ b/bin/topology_data/create_topology_data.py @@ -39,6 +39,7 @@ # LIVESTATUS is the default for performance reasons -> for now only local site # FILESYSTEM fetches the data directly form the inventory files -> use in distributed environments # 2023-11-28: implemented MULTISITE as backend, via livestatus -> use in distributed environments, my become the default +# 2023-11-30: added RESTAPI backend --> this is kind of slow (20 seconds compared to 0.5 for MULTISITE) # # creating topology_data.json from inventory data # @@ -77,6 +78,7 @@ from create_topology_classes import ( HostCacheLiveStatus, HostCacheFileSystem, HostCacheMultiSite, + HostCacheRestApi, CacheItems, ) @@ -279,16 +281,18 @@ if __name__ == '__main__': print() print(f'Start time: {strftime(SETTINGS.time_format)}') - match SETTINGS.backend: - case 'LIVESTATUS': - HOST_CACHE = HostCacheLiveStatus() - case 'FILESYSTEM': - HOST_CACHE = HostCacheFileSystem() - case 'MULTISITE': - HOST_CACHE = HostCacheMultiSite(debug=SETTINGS.debug) - case _: - print(f'Backend {SETTINGS.backend} not (yet) implemented') - exit() + _backends = { + 'LIVESTATUS': HostCacheLiveStatus, + 'FILESYSTEM': HostCacheFileSystem, + 'MULTISITE': HostCacheMultiSite, + 'RESTAPI': HostCacheRestApi, + } + HOST_CACHE = _backends.get(SETTINGS.backend, None) + if not HOST_CACHE: + print(f'Backend {SETTINGS.backend} not (yet) implemented') + exit() + else: + HOST_CACHE = HOST_CACHE(debug=SETTINGS.debug) user_data = get_data_from_toml(file=SETTINGS.user_data_file) HOST_MAP = user_data.get('HOST_MAP', {}) diff --git a/mkp/create_topology_data-0.4.0-20231130.mkp b/mkp/create_topology_data-0.4.0-20231130.mkp new file mode 100644 index 0000000000000000000000000000000000000000..47c80f9322aeac5082214ab6b980a24bd42fafff GIT binary patch literal 13382 zcmb`NV|OJCkc4AnVq@ZoZQGjIwr!ge+sWKmH@0otwkA&QzPrC+`%C|TKBrDqKSdG? z2gYq`YYqXr_P=)7lx(yLc>G1>)-LQI{U^B>edl<EwlTNdM3+%U(SvDPRD^=USOP*1 zqN&(CQ<DeP)$q#W#Saovwdv|A-*rru9zFB?|K{fWJUV6asfy|IeBktL5%7c~INUp1 zaC;rt{PMJ(;{W>m1T7f&{W%<4r}a`Lv<Dn}a6(ZC_}BXgd>W7l$eZaVew?ZMS{1ZC zT1|Pj@?nE}H0~Iqw)SDe1-v<>D&`nejdVGu#-H5NCoV$ScE>+#x&;gdBmgD2qd`~) zh@2>of8tNjKxu&UF#VauU$H5PD#?IHKHB@A_cqmc_qnLEb%$}GKMT=5+Y<Kr>GMi8 z32{5)sL|w#tq9}S&3OUnD9Vwm^Jf-5gBOZZmv)@mdc*Lp#UlQ~ZRk<kB!RIgU$0!k z1@R?*=>8NCh7ylCa25H(^UKHl2_v~Ts|+5#d5xRQri*+YXd+5PcR>{1?3N(Fc9#%J zT~KnYcj~`f$IUBtgi&XgVM*&`adLs$7zA>WaFW3D+^=Hmea*fP7WokjDDDI%rV{Ve zI3vO$u~wB-+*JAE*Nb-u7-o2YG8k<;Fa2hZA}}|j+UD7a--0~zG^puDMv~s-d!}wM zH{8{zh#l6ha!qPqdwv6cD$e?osXvt2B$8>jVT8K_2`8mXrrY}qpEzFhEZC^UZW>HE z-kQBP1}?dh$xA4ql9nU_9+6rb0n;KD>8?Hg(N+dd;rbsgp&}U=Ftfppb!VwGXY>!; zFJ+F1Gz-Dw2huLEaZxD6A;M298B`W%qP<3%i*^8&C}IkQKA5ILkz=kLaGrzxp6MjS zJ?|7)y?PF25L?0YIu6UTI?e}C*AlLIdcO1M*g3P}tmVRyPk(&n`gD25D_>jeUL#c; z7v47&-^cmDN;^ii_YXSmCo^P5VF(F#s7F-UigV`?Mr$?Lk8bxoOxu{iyqSk3rBltk zD(-?X9HRMilqOci(`#-#DYjPxdk?k6R|ed8OK=4|%a5pU97!Wq?N|}YM{*K*r{Rh9 zQLB8k4@h9#;ylE9DjR=eGsb|}-RU9Wm;~#Z!?6ETyP)FP=%P07>>$OheFbFK+c{CF zkT$Ur6}=e+ztO<-$vIZFS=Q`M*f`M-qVy~zZbAfgrG8A9u;8Wy9WWDi?KzXk!(qxa zjOeFi*{MO@o&%s^t?_+_98O#Ww-lk}{uxylMEN5$l09$ynf{4r@QD#8GTJPAdt4p% zqcXkHJ^y5@GEGk+qskN+6|;hC*jQ4L_hv}1N+$S0Td5koJ+{(2l35g(v|DNO5i54c zOmyh<sV*r<D0EtePth?&20tNm*e~S5*?hK~y|Nk)Q`ZNF#byz5_Cm74u|y`*yzqSL zrrzfbSj6jmMZKcm7w4-rSm-G)SmD9lTbv$ASBiJxBdx7TAihMXMb!bY7R7?<HA$-l zE#0=fbbcs3Kj*tmw|=uLhIl{CO7JI+Xu0j~zH|Zws^T)=PCft{!4lX8^2J6;U>x2> z)KF&XA1`Zx<!NuAv4x{uOOTNN^Obi0ci{c!dP?3GC~^(tp3U&jAe_f;Hr*$7+4<er zdG%@VCBk<vUwO3+R*<5==2Z^zE0iD<H6pKfy&RUE*rYb6<t=ikzJtWfL})1A1Ai8^ zF%=p3nqt5ALD03S+iEMXjhhUMTQNYkZ4?mDSkvBL|H)2=4p^pMi)1wYL*L6}a^jqG zGl}cuH`dBLE!Z3K{_`!a5@4+epY=8#u^?G>_LFMP!uM3Hv4#?ux31c#@_h)4qp2Ru zSX<r_W7DO!L7kqw0eIV4GKy!lK>ICpERjD(=2rqn;;=(QD$!RTR<A*s*1(J>s%)bT zD+c$Ca`D0uR{z{xoj(m-7VWC^$7<G&kv1;l-KqH<bqN%-AxPT?YWeEz#(C;41l{(o z?Vf`IhfaHdS4D>)2s?iKYXH;ht|b!`3BUh>KkW$U{2OSgzlZV1-gg!>c5iuA6)VAy zSC>OWumj@y;(zY|j-3$wfXt(L8?*2vJxrl)ma6^&a8}Q?J-@7x_1Xo-D%m9NO<#Ip zQsU8c3w8O=21F%W5i72B4i8fhk9%Gn1|Z84&&AA}E+ax?`UJjbv$ROghq5vS(={M5 z`doKA_&Smh_lQ=Z5^J#8*JzM4@K0;WBcO*0jy$sOCtCh&!ZIB5yxpS?44z2z%RYXT zt3}%H6-kaJ^JG(t6)(!Tr*RZHWqW-gZMSV`HBybA#VM?~J5XuNoEVV0>mJ6I`J4^+ zA~1+Mn!JO+N0NGDhggUowpG>~x|GQ!Zx1~)<=$s<7NDM%JwWUrqCetL-zV>a4rNOj za$@vp^a$;8jRZ)+o=Ww<jd!N3QVV?wyuO`1UA-PLcy4#|af4X61AGJA-Y@p<&f<jp z0zKUDyZE_<;0%a*1rKIXP*DQ;S!H2HPo*4RtAK-xhn}aOAL^+ocks&HNI!Oi$g2%c zOH)XngH_MpHcJc-KbeW7w9&BBhyP^>33)%VatrVTW9JA*^df9vlmz{R-`I90Rsg~z z$OEM~nkp8#IQ#kUQDF92#Z7YzCJc%DPM8zT!w20OGpV%29z%ne>>i+}P;i}m*kz=c z8@Z9*vuG{&GvafL2Kac@JiOb{nr>Pn4Z{t*&=e4G#k~_wARUpXe6jn$hP{8V(=$0@ zD=^yk-ddrjm@Ji4uz?k_J4bY-3L9$t3nk4<2=&fB5q4q;hs`HcbJU`*@!~@a&T+~< z6!u3tCEC>1(?)Hc2iqpoQd*nSI~CKkt{`p!OACEX23D#djxDnClq$1fV20sO>KM}< zqo}!Tq2q3p4l>+1Gbad+kZ<sM<xr^<LCMfM)D$JY-S2KD0<K37A4i3riF?ll1RqJ1 z5Cgm%lO$YO+)Z5a4Sgmdt~H4cNOkD!nvJcMK<a4Ra9`^X7F26Q<k^yn)#1Ky20ait zHLuT9F&6#Yc@a%Y2ed3QjwNq!^n)=kvFmo%AI@$KZIxaCPq<u;MG90Sj-2pE+C4K7 znCuB_)^^>+-(2By0gxXvUk?y*LiIAveTF0;KR1s6r|<76+~~CT=cxUqlmxybjFzWH zY%z}}tw}z99)XJ+Z-$fUJC1vvy~5+7zOc8RSq%%sR<zch(|PtUTo#m`q3<xM0*-X9 z0M5|nn*HXklfrJUClRtCV8m$G-%ot6Y2puQl;Ip~Kk%nA%zIPxT?K5z#>6U%Wr{}} zwnG0UX8g!wVeE*GCs8E#+2R{Jl$17bSBSJQ7{i`yLD{IZCyJnJod$>6uD{UqAre*K z9qLP(lNo^zDX4PqWG<vfCPV~$U+7WV29N~YWgk$J`6mjNB&g%4Q9EfU%<MF-W0YF+ zck)a`bHr;3jCWTF_m*=N@Z}oJ4&H+_8%;To+$l8HQJz<qf?w>DFS5Fcjg!|hfNkvf zGPaw<>=1L|8FN)^zh_p30MOpCG<-xp8|_T@$eVjq{<_K$j<m8w#4V};4E60yFjvUx zsO2fP!Z&XYfVBL9uem%XOTpi|@!8M`dBs0V>dhn{)7!uBsJ|YedQTVmnM{E?XJ1xD z3qE5}B~+prSMzn>V$Ec}3M#%>1u~++v^NRtBCTyaOcui3DzT@}2>*MdoMzW#djbtD zWKGmu07cc!F*u@9G?T_71bPs@L^r%zF2IiOZ05MiCDV#>y9K`IQ)7^QWvxae=kgK1 zS_ZU6>{#1UZLU+)Zil$3=0m$r*KMJDrt`Bhe^M2&f!(4pLTgN*bCF&=z2r#OFW3Cf zpx%667GIZcJN*pX?b0SBbt>JLL#?)@0o;7oZZ)V<V>zfos8%0Lk8D9zaNEU2EN@!W zMR}SLnxHI%HHh26C*#MjEbbeWLUl?)S;}BTuC)$9yV|bl?RHrF2`v!Iw9A7#ceOj~ zT>IM1JQQqNtb9r%<xJ~Uzn(tUE=?6_=5!H>ieLwdvg4&SOpp&`6^WuHdO!^47mYrJ z)t4vYEWD+cV&JsOL7p>B8}W12mzRqN@t??vlq$dee^k+7?@c%Oil!or$NJ8{QM9G_ zxXNE7%*zbws%~K)t3n<o_e&mXirJZ=B#`_KcKTpf8o=PHi^KfTb5(Oinx6e-=MWh9 zj3V&}Mvp8IPCaK(rUr4@4_mDR`12HKpvdq^rcckioS7gHab)@w+I@fHYEM#~Nya#k zRtSfokrhPdb0cQ1an#uu(WyR+BOZ7SoSG&Xj5PT}*^wBBEtN8tnrmNWvYs4${GENa z17e)=tVteGVC~#TSg+#=szDrO%0{`6fSk3$M2-nzST>ZA8(V^9Qry=Jp0+Pq%YuFC zJ~9ARyK0lY3=REhmRuDEoiu&p{Cz%y!OyrKyu`8cZAA>Yt#wTLn3IioSw}%SWqe)x z2vy7e7dW3Mlw!C|5rP~lKHC%-h~1{njWTPx%bJ%{={4}NjX;mxjfF#-_{j{*H->q9 z?icBZXTJu@LlSn)`kDd$q{fYID3kd7hO7m$d|Fv@;S4zgVs5MC$=>K2ut5cR)h(9t zT;~Wk_OLjegP6yymGUX@q5Sdt0YN;Qle!YCbl`~6JW=#1HeYX!#8Y!LoyFeDMpP1( z%;5c6zJed?uYbD=W5|p3N36QPp9n@em+jKshga`M&*D4|@h`N2FH&ccF3I{nLWFr3 zT~}gcEsH*OJ{Y**0O}^VUb5+S>X&*Uin;^%sXSu!^iH1Kvgs63%2sP*f<}?dr?<EU zc6l28xtthz^FA(52ZC3-Iii8UZ*I|DqrnmZk2-C)BJ|L_ZA$M~x_hifbv1{fnmcT0 zaO0@}-Gkj_M-;;{ZEjeNn+D47pME)2$XMKsTNuRc2(Hhb(^18Ojgpz&6sC35FpaRu zrPVM@$@i-g760%!<lnyN#s5v+#VCD`ueXG6lALuKpHUy$kg&&~&Czn^ll^4rYYU)f zsRI733nK7X=;QmmSzmPckjQB<QS*L&q>$g(^?xA}JSOfqSK#aM`(9#uLE=&eJsU3@ z@>jn<Myi3-mS#)76kCCy!VNFpay9)`VuwaC!oT5zaZarHsqrlfQX#CdCr3!X^4%X( z&z2VEjwE*yRe19v#@p1iWmSPAxAH2bTHgzu1!t6NSOdBlHiBwuhkjDCp~UD*R%kit z>m}(5P)kY^WcfjF`$&U<)93tam(LLj?5#%$fD@cVIoJ}n>->QmEvf7~gFkkV`;of3 zPo?c9#sluOWi?n~bggW1a(od=dE4f94~YgF4EjTGGk;twen+)7(Brv?iXv}BL2qkF z23PY=lIeM=QAAsxqEQIFV;%X*+`2JrgvYdr;2+J`d6(2%b*?luzWwet(X4ahAqhO1 z8pw{^{@Sp0SmErwQpII_NR?zEZ=zihS;cR~Oimqrg?)&lmQnu!)ItvIMUc6e8Q{Sj z^A4I?AQ)YTQ(_Q51nce)#h-KD@ZS=A^bc&d@s}1XY8UwP<h#ky{=$>}>FMTryE}LC z4Om#Qa%o#Xm6H2&*Oyc;&-9HJKL$gj;5Y{>4TC16*f^3gvF$(bVo8X-xd-OQKp3>~ zM!jPFD_7r@7VgQ-Wm4wL08zGNhF3-#j6tGIsGFr!n$%P_t_<TyqbQn#Os(zv`k#>& zX0?P@S~6CKy#)8_MKwQ}n#JDU&`%9QCkMig8p<8p_7OU*p|-)342*MW7^>1!d~ZSK zHGTdA+>DG@u2>o30`P+wtboq#0_dqB$wkeya<77K23Wmep5YPfmnf(EBF~!*TTgqG zUv}$JmYj;EoQ;1qMNP!*sN_CkuqFGdqa7CSGUea`VBls?+*V>`IfiuL%n#&YklJXA z#dG9~Wj>j5gHInNhG=P{X-$v3Wm>@l@Vs8@xCp)WDNxw<^h^T1EPQ7C^wA_EyVUN? z&(^G#q4O9y5y3GhzJ(UFlB9dxN8`R+oZg>buTceJ5@ekT2U+$J#0JwTN$HBk<t?X2 zpgCKk5`99?@_dM0;tM?6jgBvi3GndcLVma-NNXWBrRI^H8aW4U73sA6nOc}fZiL5~ z%h(}{4!p*ze>ksZGo&CI+5nD77%Bf0B5APp!eB}Mqo6TcJa0n0j3{wb1fNNW;lO(z z+WXWT@4lH48`jqSXoc|G6CXsUSi4a_(+qEtyW4z74_Rvs$WZHl_AGHl=m_s;!+a%m z<}`DQT?WOmWREW!-mw!7ly8b5{QlV;zMNYi1p7_3GV^EhH6YTO8<9tej+2?!7%cA~ zE$<*d1|hL=IaRkOcAJBy?`4Ux`z6;pgFhdmkX~KrjU0-R^y8!@$%29(N%ui~ajjjZ zr5YBT_}9cC1tJ9hE1V4`f7!y_ZxMa0>=e<$;eVKz(2eCtp$wjzm~Iq$5oJNHdE;@V z3W<Gm>xVlW#lnQj(Q4Ljot1v*2E*1=zYpDBXA{V&rzXz#OF3WtAf4B4fe}(a&-<jU zj@h>l^>4c1hjM$56<QbY>!1x0WaW6Rqu+5ujEYXHu@XkTo;!xm>f`MQ%BKo6mp10W z|LCFTs5|}=?&q>IJ>DiT5gbSIiP|kVvkD-Wut?lbo;+QQsp4fg8IkEom?{6t*Wr+- zxFl@<FY3Whyj`+U!!3#6yPpxnRJP`Y`nVov=nsR)J=m8*WRP!LSiTGG<Cyks-VAT< zx^bvMus+_aK#q5^hCCbBd*hqk3uyU)z#LXjj2O2~-ZZ8DE#A!Gz|`(6P4UYh8$JL> zd-aAcO6ulWNG{D*w5i`e9;iBN8~X=}g~%axu5c$Y&-YEtHT*G6$^(jRxoaya;mMzF zXHYWZJ;T9CQkXJ`1F-p%n33p>podKLND?4z-e~r4dG0=$#5r$YqZW3yuKSd_yE%7> zg|p@C?Mf{3`O%NPa=vSO^ZNqgygXp2=jekO@RWO3f=OC=u2oVU4TLYrmTd`{NTPrP zpVWr04iZE-4Z2(q_A7IT!!K9&XFEcBfDC>T4v{}Vvt$f#c}i4fCr&fXM9Rq5lj|}U z$TK!b7pxRbIb$6qN!>;cm_4kr_1SB1Y@>LM@OBr=gQ9f(D*-Hpu+e;;P&VA?YQDI8 zM6%aQ0-}W61fB4rwGZ<eSuET826YW;_uADiRnP^Mo~f<Jl_$QEMZuf%{~n~(p{YtY z{Re(CWbnQfN+G{5xOwXlo8A%ARQxbck!wExn$HqNz`^v>8J?Y0rG)2hZ2qw5gaa&` zG7-HrAYz8BGlP-i>c8KU9%7Z<Btr8Cf5OZJO}voHq|Id3eJ2mwR9D0a)I5IQX%?}c zrRY>#YvARL_M%v&uc1`}%|B#+wi5|d+9i=Q1zYMloIH_)v7uy?^4#a=EoMG;3jNx) zvb}7xK?ZNozD`b=sel=NKun0GBZpt}$0ySah=LX1AMt6p{8o^ECFD)}0J`Z8s=FiH z@i4r45W0R4Qa=v72<W5&r5Ndm3m?Z`JmYR8IY;6BqVX?%d-g^Z0M2XWeOX>}M$|L` z_UqzQLs~X}D{BwZRc*xPJ3;n+Xva27-GMSWu*u8^C=_Zj#pTW5u|-iyivaD&b5BV~ zUQyDmKsOc=iPzuVwz13Y@W{IGZZw*Jp|v3{rQteJWMHInh{hx526vbJmbL2&Hy^JL zUKjuS+w0vKQgD_04+d!Z%cjkGz1Cq7`K>Lv1>JkzT+F^1a;=0y$37h@j^68r$=B5= zE2157`vw)+zYSxhheN3TyM;s&7K*zFCc*FOs$%n&V!`M?*X*@N5<^vF8J<~CtYNdF zku6mV5ej*vn};6!^m!3vTac9X|EQhzLUN8g4aH`eRPgGE+9fHwn6^h})DSH}Hzp9P zUSKyNVZP&ro?tyROj^v-^n{I9x>yoqUJc6sdRS@tjf-y9z`~!}OK|-wzI~=|`qBu+ zl6v61*=lGA?5{e%L~hX@`;=5>5q9gT)od6)vAB6!T4qNQK+*}>T3Z3@iTtS(f~pd^ z64q`4Tz@imBLa1D=tej9X)ICscbbCv3L;FpOeq`y7{k7rK#+>DoH19mvi|{mOyOd< z(nDN)B_yd`D}0E*{Sc#ml0U*YO$%9t4HV*SwyY=g^Yw6f2Evd?$yz{C#qe~rfh!U$ zG6`WuP*SQ4&A~ZYX>f6Y(~hr32D?T*DS=7xU<fP9ALgFwow{_HfWHTVmUlpk6I0em zFjUNB5<l<Q1Nkv{XH_u}ZN>5#RfV5rV@Yfc`<$YVSnLXa%QWMjJoA2h;N>gZVs#Y8 z7LgXx@ctyICt=IXIJ;4jOl{^FghsP--8dO_hX@rL50~*>ho0+DCoZ9O03-cAKHVzf zq-<zRWcV#<YzMPy`t&e**3+%EC;QW7Y=3-Xst{W7MvJ7~+;OK@P)LwZP)O9UmQ+TL z0Z}yF(V=LV2?AFI+fD($WS?4}2kz`z+!8<-#2ExQTD{vvrfQJ%hKU=%|Ip9fw`QHm zl(rY0lwS>$3{!98lCeJYWH2Iglv5D>z3To5v0TJ{ux0W+G>LP;>bWCziWQ5~2186? zdIbOMk8hrmAf0*3W+@x+qqbS9AX{#?yj`Kik8F4|<*_7x`Azs}ipwRFO<piu$|p$S zi;B_^)!87l8`<^F)Av$j04?>Ke*G1E0Y`2$F$R<&H>RZK73gs{h~{zNnPlyRZs>Mn zUO2$so<LQ2zjBk5_fL?=;5LsoZSsaRFAhHApS83#5pq-ZM1krC_WH=kQpHRho}YIA z)nCOf?+&2^&N8<_YDC7bTr6<}{m!c4(!XSBRbv8V0az}{4}gTj(r}!2X4(C4giU&W z9D?`|z4s*+DzCCFVn>vne=!7<n1%lyo{sc`BxVqzWg<9gRiPuP9e0U|OgviHP(*Wy z>tr0+iF1$_<kHR~4iE^l97+XBJ<6_Q)sA+62%Ad@{3^d4L@s$lYvDw&8F}3&G?xm+ zx@hr}>3aHy^E?W<rr0(g_F<?yCW0Q}JZWfj40Zs*r}z!J#>M^T42BE4kg~)-3c<)< z{K<ceBVFyYbj-qsxE)H2JI^qE+q7i9O}U+88{E$||0O~d{*!dq6`d@m-d(qrjbjEd zvG^P7{6yFB@#sA$9-N;u&zuMcZ$Ky`BkB($C#2I#mCxc`;v8>t)x7p7YDEAJXQ5|a z2*>QRaQSVHSBJo2=yo(t)6?K+aUPP9Z%9>KveJNPQr)xvPqrb(7M}4A^7H&UhGBX{ za|hq-x>WY&qD<I(x+o;C<ea{je7mhgl^A%Hy|P0w5UX5kjlkOVyVQars4>w=BUtLT zuhi{X?Bm~YMEH#4Q56y*tu^>;&79{+(%v}u`^>-MUk!cU2>2^RgMM}#!x#v8=<#yf zzix%NDT3jZ^MEV`vhKqZ7$#KF>)A*X8)USY=_4lsjh&>CFtn{Q5<hW<VQxyu&8?do zavAzLv2j$V_3LH7?O^yidnO_9A}(lz^!OlGC*Dq|^RF^CUGz+3H<*@J)3C))B#pco z7kp^c2z%r!k9d5zG?-$kY+;rGnO834c=o3&$yZPpH9GrRS?vcQ2&SW6%IO$vBVyvi z3<8IWoc!FaHVqxv$ZZ9Dm^_YSjF88SbD{}AU!&2U^VKg=?*sIbk{+K$`je5x_QUJc zTWg4z;%1wN2TQ5b(^1Dj?#Fn$3=ee-hWI2n^M`IK8VM?8Xl2T_E>v#XEh}E|3cK_d zaPe@0lWd`U09#4@uY{@5HaRdvZT=P-vxLl|&JOFU&0MKg*SN2ZHM{@32n$?suCD<d zS`uvwn{}cLwzg9tG5+YPRj~MF@MfG@<FD`KN6Bs7`Ybwk?L*8GXkm~OSG|U#1D<(! zhn{;>{F#mLcZVa{L3)rLjPu)S<BhG>1<4&%y#Zctbrjr(LnMQRTT%d}@N?(|44rj_ zYzso#jo6YXoAlZM2e#q?zNQZS@{2ke_+ehLM}zpj2uo6~Kr<3H4Y{Ep3txR$#|*4x z!mAPTn^h8C?L@^q2mV1RrsH&5j>#Fyd+Vfh>{71PUxAJze)*!e=ZTyoTT<o@%gp91 zEz>k`0X>p0E2gk2gu{(6LpHNA`UR%lq6g})_a2|vTJYSmK+FZjGn}3*@c661j31N> zIJC%g8vd?fua&nBTCOK8HMZyej+j%IRChYs3LY*zXlQ)_%71KaeGNF6;#^iRe=#PO zB{bzK4b!t09fU1Zz-UY>cy(CBVoaV;p`Y(V@cw+-IN7f#UX=8a9>C%?TO82(jG<V- zU&BYw@^UClvE`th6%&LV2WeFCpt7&8NQ|^<i!&1op@vDlI!Prt!ym6hDCAaiL1jH9 zbH*t<<%MrJ(b{nIx;e)HmvDgO^o|q;YHn#8@DLpD3Xg7+Uj2is;jEGk$$Bw}oB3%1 ze<__!II0?Ht(d3(jcGH5QBUuLsvri@BOurf`PV+ZKJ)%{>xRiC^GFrm@JfUUXZSJ? zI@1|m^>>=N5CT-6r9Gx@&7MtE!p-J~YqL-`2pgWSe@2sAsGCnSm#aAx*_8<#_>?61 zP71dCkpCXfV<)V?8&2i!5e2dtYjy-u94Vj_s+O!ERA<v7WjAL~9GPb^5^TW^r?R1+ zRyK7l=@=HN=_|s8_1tgkXAX(}7cX&W2**<SNmau;p4sUerE6)c8K*gV*n#i>J_XFQ z>@l6%>QwTD!J~A}oFi!!t22zi=c}$&%$`KoSe2Kx<W+S#R7NRohlymZPAfb(f>=%P z5P55d1TuP4Y0sEjR0yP@mk_XS29Z7IY8GrxR4dyhc}ICZ-vOKuS>W1(tsl~<c1eBF zniayqgCKj&ckWL=h`31Lu>{=2w25BOoSFc<I8B(pRKEA9cAZaX5{ZR68imFDK{`2> zx(bHH_y92l7N#@41&gg*a-FnJG5A9zxtVvBVpSWutg+Dz%h`sx`-v|aNhAHhzQO#6 zek#|r7WQRdF|VT4#}k9yKUC)@RSpUYZuQRDVbcww(>6c(T3uzs?h|6`w-MX^_ZdXn z7tQvz09Y&-QTFvB$1>g(+ma2T1w_|HPDSBMbrB}s6mA55EW<MY@NnNL6Ep(p$M{RR zd<OD*o?kv@@NBcGAz;%^dU+d>@DVr!%FQ8}oEq!y6##3I<~}5TKMO!#Bk$<=d77uz zQn3+Q7yZdJF=h)&%TT_JZP>Dt2x1g0=Ta3G2N+*4q8qZDW<s{gr2%n5K=9i{CEIDA zJKjhb1egMZO&ji_QhmYGbu@t!JpmIV`QN&9h2fPG+h+x5sw|O%VuA#P(&T^@fWw^% zEe_Xm!9sk8B9RDQyuS)6<{N^&Ojcv%nSq4LCc66Y%jCEUq5g>uo_k79$NPD5okvA7 zajckDrlrI$btF!*c$B{~Tbd1$dXzK8D!a7|RB>3DOZ9Q@qR{cz|D7$UBcMF+{2W?# zm%OSV?Y5ScDUo5vqSRW=Z;@8P!e3jxcPrb+QfqF6$azEySIJ!)DFRg-6Tsu}B&=s# zy@j^&oZdd_0G+V@87J|fA(2Bev>enC<)zGzi9q&gOiucnXdXPa7|Pev#|5Pr(W0mA z{z*&md%;+_q9wP)CJXXyPSZwTu1H<Un?Zv5(eC;ljvV=>iZ7e?>?U)UIv-RLGzv=s zrWg3_7zlbxp?3Rrygg4j-NteIz8y{hbUp@xx@STD{fB|RAGQe~nS3mku8J5Fte<b6 zEYLl%IETY->C0yH%;va<;i7IX52F@4RW4vG{;20m1kubY5b}jGenrBiKXDt3C1)Yu zkLh9YA1*Ue6=S=Cx~BA+B?unjEQs&x2@Jp}Tfw0)cH2yfyfN7mqcj@k{udxZ{09&z z83k&WZ33}&z4A%iL!BXG;Xe+KsNI<i_YF*`>XZgf*J}2b$Io*h{k}^hPT52RBF-a? z$FMXlO*tcKdruGho4%-hx4XX_yYYJi2n@d*^kkSqUnt4dhGWFOLb+}YNTADs#VTh+ ze;>;`N6%(^G)tHp2Q#daeIRIl)R&3wOTfqUx5l0Yd8kOjmSD-^=a4k7!tRoErz027 zAQ~S3UWXGLxiz@HJNoy*<v6>P6Q_r(mo+bIA5<c3c+gNwzVSnG%F9m8DmwCYyQ*7= zj~Cg1IF|(KjIW<p(S+;}!cFt;r__e*MzBA+2lvaN*P4p_s{)L0qRfD4ExhsiQ)Vz9 z|0%Cq;;-!JE(~Hc09@?MczaMZ4kx=G+@1zU7CrnAELKzHjM)n@@*baQO3V{~1+wY{ z*o#^@Bwswm!|}X{{xgy;h(_YPl!e5%ODIxaon$8FeZlM~$@3S-M36>`j5P=0H=w_N zGA`|VeSBZ3f1O9Zi%UU$90$9~My_|fj~<m8V@F6xmS*2@k>06$Z1miI;SciX>o@=F zacTyxK2tKmVAX)9`CQTXF8ln#4eQn*U^g=#!Iba9-Sjelr(A%-4&w=w7!TOK&F3;l z5XC5&r<g8|aH+(r$%=W6voffAj2kTul73#MPXn@&E)0r}kkZ3Mp~B$gbGaEp%7x3u znYB;fBtcAIL5bMLroHO7Z;ZKNIuh|j%H?Q6)Em^`cXY5xbguQ$RvV^y&)FPT=Scwo z)Bu4)y>(TN_5;9#E1E2EVg=C%$9J8!DPB!BL%^p-1-kNT;%{X;UIk5A4)KP%boSnQ z>HFV!j6&AG&j0zk7kZ4Vs0UZ{v<95w)0165Kf5cwfpbGa>Nn#QCRCaB;+~@ALoV8C zTxo@xO`NJM376DKea@xA@-*JDqOX!n^2I;$V2Q85>e3H#@3`FX+d|)Em!T|UsWT3+ z2qnr&UCeLTC+&|AXgALteedm?Bc#BkWS<6fXIdz~U0b*jLR=Up9P(s-A%Cd$XOUQn z?6U-;L1orFR!)CPutuj4u&h^Py`>h)@M6cgvmUc(G*Buy-^=MQuP#^s+gE$3E{mi| zzZH26k9)Xzu&nmOTVF$lFui$}w;zkE?;?iCqWS;m@JPQh<X=!Hr}+6nDbK4Brh5-D z+0T!DStcvN{S8)4DezUl#wWOZFWO?EQ(p-cy>6b~<g^|+Yvk@(-%dqc^!M22;!+Zg zgR~3SQ0$VSx5P&n@4?>YFs)ANc6hqpUk%m89>DjE{M=6ErnS-Nnq$zwY@<$gC~xrY z3^L6|5P7YYKIcmfZN&RS*ZS~hL4qPIc4mXI2@ELnY$+HLXyH3d&dh=35OQC|JdM~X zw9>{40B?Gkp%M!(WyQzCv@JA|IhP7W)5*XM1?=F{xO`ga5X=o3s4UXL((dzT;H=uH zPnEBj1sb=EqIZym5q)5MJijeE;KWGBX83ubzZdekp>DUkDXnHKNdXrnkB$Emz`8}6 zkfr!DE$@MJh+r&w7f)p6h4LVl5!(m$s;hawcJOj4keabcSr1n1xX%(|1B1yAi}Pw@ zOghYD$_|(QpO*C2;oh<rN}(j;`w^WD6S$Sd=V>Q&aYiiO6?1rC%k3D#^mmJ(^4aBy z4C;Frxzn$jOwR9ojoM8s#c>PP5;Xtfr1~KMziv>QXWqnwFH-K#KVlamgMtP00AdI- zY>26G>9UIlLUY9gv^fLeijyw=js%&q7B$b|#oAQmES3zxUX<u6V;_=Ir>x{k3OsSF zflk9>iWdME_oU1$%0iCdEmUB}e4U8^Z(V<y&4cv)op_m$jv0$2I5^DT2hxu{4rtoS z`hXhR8eZARKRSQLD9Mr>c3L}dvG&$rNP>G-%ymqZAs9c0JWyaAow|I1@JJA5A29-d zMJddjEz5CftnJS3WELuo_&@Z$FQvg+nz;%S;)$G8(Mr;ipff%6r^*WX@8vkPdkLBU z7VqkW3-rq@8MdCQZ%nZUrgAzf+cS>?l%nnoxaBF0b^6MsKzc#*@|KX$pAnC3BD+K= zQu@xyntd1aliTbwA~-8L7`yHrlJJP#K}n|^6Y@mbR~5;>!o7uhBs>2sZ`xDn=>Djw zJu-;HV=<BUDZ!H5rL+5ZXa_&ai6Hmp2O9hB?osx^KI1!$Jtimdt0>Pkp@ge{o$M*| zR{K38+Nh@HQ<qk+oalN_WvJdRp`!Tg2JZ6lUv>Mf^YLDE`wZvU-sMVqxh3Fdnf;P; z%&z@6a)AZGxtYvWf(4mx9>Y^W^ynU9BZ@_c;k;Wgl{fNFbPnFy$-5<&o@DT;T8kEL zr<<m%^2d3s``L40jI4{^SeB4zR?PU=obLv%+vYA&6X@2dz4!gDXjM;87f-1x`+@6T zKPUpZ$lQc<mUDyiG22R_9FaE(+<5tb(69#%Ecz(_Rd+yXp^_@9+=q5dT7A!tQ62x) zY?c*xKi+d+YQuRa1OMrsQyKdH$w;^NeOn?YFI)<wGgg(N7mq7l7%8Gf-T;J_@ffVk ze(k_mbV?il>`&I-hx=>7AbeQsCjI-FreG#Y*mnSZ?NRFdN^y+$&Bd^5%d>kcr6pK> z(2vGc&i%K)zH;1e^QY(~WAih3kp7+Z#7<WcW^miPsk*!>v}<~Rr~+)iJ9(+vfrMKz zBMDoVdMZh*x(zd)1B@_qgwvp=5lNB*oo#Aih;-3Sv3Lcp{zY~OV*Nm_==C7?xY=>T z{$7p>3qNzzZV_cHz16(R<KJf3-n0s8LX3=iLR9VPL5fbOUxW~~V^db3An6RALvbCe zjxKSP4r$9K4{+XOxfTZn4?pTIU}MG&Ir_mdm;uUhmKvC3?<)P6Mt6*uA~rp7u}OLf z_reKp`AHH5n@3ke?H1S6e65P8_#lUzyeu|}nl(0LOZ3^n7+Qt^cmgyo)ZJ}<uJEhr zZl-`vhrod?<p4z0w<&wk&#LwJOx>6VDhd*39@*LFW8mK`Nb#+uA_7r$0L6&Ahv$5- zd3(k3*Tik-#O7pJe+@&Pe*~9C2PspZKZlvvmH`{#_d^^+;5Oibk_2CMftWOeG$Nv{ zNIT0s($rRBp>k!yl4>P>(;t7C0?W2+Ec38!XM(XqsfPo;{hhbE`Ag-k1Yvu>gnVpL zw_Gw0aOml(h384@Hyfau^rduVsiK^gHLsSMihjr(DUX`ya*6r3<;hU<<}M4GFvstq z`H_<gSm_$7^i)ckN`ut|za}q|eu!0f(S?b+Z;v}ldd*I~&}@Eb@8;W*1tx*JiG0i8 zu+@AQgI`D4^QfTrAaYrFMwP}brKbZpw~yFC;4{g-gUUqU_{|~e%u2Z`V4`{5oFRO6 zYs5>>dmI>ZL%0baQJ~5Qq1F3L@&g1Y=I=53H1q`hr*TA}R#eo2>jRL-8;A}Pte_4N zY^s=t#XjD$C(VYF_D=G`Hjcfbjb{&yjA>dVE-bn|BN)Bl(d>nH(?3N~af~g5gHAud zkb=x>=pvFDAhj*vY>)aG;P2uU6zno%`Eh3#CB2pv%6fB(tsOEJpTAr!rc%lCz!TTM zHGW8sUqz)QFqHO^je=oK)5v3A*UmY=HcZq3bkZz0aD@)cfjiShpLxlD?$~|qnEmXo zy}2YJF5hE`G)}H!DR&hwH5`>S&AZ<mmeU>_ZXnBt%wLFoOdTf!c-{ZV6nFl$n$*D- zR=2~Nu(`w)xxOs%fJHyu@HeWlqYJ;HxId}0TY<$VVFg9PR9txGY~DQAh{}sM)9kO< z{-fx(ss;V*kMo(?{vrD;SNT}nd1!pMNc&MEB^P*;ox`!`Hs?05>0F`&x2lmdqzh6g z8j(KY?SXJLE7RF?fX(84REE^SQe=(7-1FqzmDC3|d+{}UE)SdaxP6TnTMo8hHS&?M z<kV8#Y_)Z&H7-`fjp&C((N~P9(v+yuMRQ~gD3kkDZWjc6InvhL0X_QFeO&QugMh7_ zAWIw&sP7ddW%IKr%`~pN!|1{V@-rI#*`4?MIV_q3I&e>b*YCErri5Y7d&CYJtbjol zr*3aC@eVW0e1wU+qRSOlrA<rk-f_eLO`y=%-%C~eUrtYXZrGFDF3P-vDZgY6tmJ}Y z8jK2Ibda_}<n#5@j-yIWrg6+P^B`HU&s=i82Jb!yr7DJKdGYR<&~AvNWIS&7H1{y) zq#Fp(6#t6VJHR|)>LK{~@fl52tmxf4enwwLE{wQvk<j$>^wUJA(-4{5fTH`tS3MGL zYKf0F(02F3M3V~BabANj`GOYugA7`yhRfiuGCURw!hdaQ3UXP}K6eQ4x|a$rW;ITn z7{Y;KwqLv2{OGOhPlD$=FejRvnr{cWoT#AosMA!M&7MC$<@HusQXP%U!@h@aEXr-} z5DnJq8LXN1r)TvrpVt~qcIws~uQI%mRH5*67zme{ndn}96aevAzP}`}dv6PG(=8?< z*<z__&~_|wZ!Y_y>-)($kq?tI{#H@sk5lfq3@n>jBXD`KrgIGS!w2l!IB_W9upxj; z@<^<s{*5Jk^>@MP>&m2O<qs7tGczhsQ9}@C*zJ0T-npnu7Yk)h6t;z)uDkU&<tNk! zq~aSH{WY-GURT$#fJjSMOlGyiBKqTx7^OIXRC;@PMCJAmPa^U3ksICFJe`IC?PcNP z=_3l^<%fFz_W};OdUKT{dG-)j(z-;@&jA%maYWJ<9%OyZAL<;Gjz4wGid`ce15|Mu zKJwj%{2^1~c~ZuiS&o0*JW{yR6W*s`$gg>0&IJqC4Wq(5Wb)+jbOC9o<4OrzG*^@* zj_ktCCX(oFeD5$@ncp>_@T3mQVm>LWl<d$5_7kxUj?2dQp2>S*5u;QY{GL_o%lEVj zr$TmRaom;5=mx>4Ds?Xm^vBqxeBWWn$*u50wHt@S@1u6r)j-{TD<!`*DY{`L*c4TM zD{)qwqcfX4^kN5aW!Z)myV2OEN-T2|>^poDSyT0dCMESjuXiZGQguOYMVcA4Yp@;! zkEF|nEuxR2>(YkCiT$XQJtj*g-5QrbfHlz14t490y@MJ3fytI15b5#mzwxU-_NY7} zlq%c_x~m)=XBv%>3>&oEk2z2lN8Y?V#>BmEXKOVrpNx@xGJl}jX+7MWW?I9R(Y_)4 z=IM|5nxBIY<`5g;fAod^yfp~D&bQ@n20@spzSsguJLDy4PXLv7avXZ!^`z*eJ$0^z zRx`ZcHaI;RB1XgzR%V}Gqb9`~SXKK+<g?vs>USgo?SV%*F813Xi%(XzG34LeRvgys z>%Ti>h8d?|_iP{EirDr%MI$2|!4iAsnLaAKJxd$$a6UD5!r?u<D+R!&@Q+2$^N?wr z0fk)CE?RmMBfa3-5T2T#LL=N=n{rW;wEgja1*x3GSg=oB61(ATeb_);dFF5QO+9W= zXMaaTPUcVMf_OynZ2e*;5zg9H%A~RGjxAsp)*@Mp>d8U>&_QlMXQn~lkOI>*14-t# z)-2!>?tV!jJ*RFEQ5;*2TLZhxTc?)sfj^-HcU5#|(yyi^*QwRWg<!{gZb6=o6-Bqa z<7I;5`7Vi<LW7q3Em{p%0Z@|Dichp(t;;1WO-6&Qg!4PSydR%jpV3Rp#!5k86nk6a z%AO|4cE!d4I~Gp62cP1=3Hw<1URx}$!0Yq%_IflCa0hz*ygwxIO(f|J2F?0;tu&Rp y&x)uUOfd<RFDZK~OV~w_mb$Q6bbhLK&fWk&t_1(TRtD%ZQ1gw18Vna4?0*2)RzI8o literal 0 HcmV?d00001 diff --git a/packages/create_topology_data b/packages/create_topology_data index 9c199bf..d37aa91 100644 --- a/packages/create_topology_data +++ b/packages/create_topology_data @@ -59,7 +59,7 @@ 'topology_data/create_topology_args.py']}, 'name': 'create_topology_data', 'title': 'Network Visualization data creation', - 'version': '0.3.0-20231128', + 'version': '0.4.0-20231130', 'version.min_required': '2.2.0p1', 'version.packaged': '2.2.0p14', 'version.usable_until': '2.3.0p1'} -- GitLab