From 5a3467538e70921f7b4eadcf4c17e71993acedf1 Mon Sep 17 00:00:00 2001 From: Wouter de Bruijn Date: Fri, 28 Feb 2025 15:25:18 +0100 Subject: [PATCH 01/12] =?UTF-8?q?=F0=9F=94=A7=20Changed=20user=20for=20doc?= =?UTF-8?q?ker=20container?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dockerfile | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 0551bd1..de18f3f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -6,9 +6,11 @@ LABEL org.opencontainers.image.description="Python script to synchronise NetBox LABEL org.opencontainers.image.documentation=https://github.com/TheNetworkGuy/netbox-zabbix-sync/ LABEL org.opencontainers.image.licenses=MIT LABEL org.opencontainers.image.authors="Twan Kamans" + +USER 1000:1000 RUN mkdir -p /opt/netbox-zabbix -COPY . /opt/netbox-zabbix +COPY --chown=1000:1000 . /opt/netbox-zabbix WORKDIR /opt/netbox-zabbix RUN if ! [ -f ./config.py ]; then cp ./config.py.example ./config.py; fi RUN pip install -r ./requirements.txt From 6bdaf4e5b7068e5faebf6eb7a2eb662fd963a534 Mon Sep 17 00:00:00 2001 From: Wouter de Bruijn Date: Fri, 28 Feb 2025 15:30:06 +0100 Subject: [PATCH 02/12] =?UTF-8?q?=F0=9F=90=9B=20Permission=20fixes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dockerfile | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/Dockerfile b/Dockerfile index de18f3f..c3bb81e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -6,12 +6,17 @@ LABEL org.opencontainers.image.description="Python script to synchronise NetBox LABEL org.opencontainers.image.documentation=https://github.com/TheNetworkGuy/netbox-zabbix-sync/ LABEL org.opencontainers.image.licenses=MIT LABEL org.opencontainers.image.authors="Twan Kamans" - -USER 1000:1000 RUN mkdir -p /opt/netbox-zabbix -COPY --chown=1000:1000 . /opt/netbox-zabbix +RUN addgroup -g 1000 -S netbox-zabbix && adduser -u 1000 -S netbox-zabbix -G netbox-zabbix +RUN chown -R 1000:1000 /opt/netbox-zabbix + WORKDIR /opt/netbox-zabbix + +COPY --chown=1000:1000 . /opt/netbox-zabbix + +USER 1000:1000 + RUN if ! [ -f ./config.py ]; then cp ./config.py.example ./config.py; fi RUN pip install -r ./requirements.txt ENTRYPOINT ["python"] From bc53737e02017a972cdbe01085128ce0a5ee7a29 Mon Sep 17 00:00:00 2001 From: Raymond Kuiper Date: Wed, 4 Jun 2025 14:23:01 +0200 Subject: [PATCH 03/12] first supoport of multiple hostgroups --- modules/.device.py.swp | Bin 0 -> 57344 bytes modules/device.py | 92 ++++++++++++++++++++++--------------- modules/hostgroups.py | 1 - modules/virtual_machine.py | 5 +- netbox_zabbix_sync.py | 14 ++++-- 5 files changed, 68 insertions(+), 44 deletions(-) create mode 100644 modules/.device.py.swp diff --git a/modules/.device.py.swp b/modules/.device.py.swp new file mode 100644 index 0000000000000000000000000000000000000000..62a997b799a5c93df7d70bce7a66d87b8f631d7d GIT binary patch literal 57344 zcmeI53zTJ7S?4bb1SAmy>ZrVqw-~!R>8|c1kU&cTNq5p|$jhXgSJRfa>Yl2)>8g8+ zdvA4jbx#AM2o8ge3KGf4Gf`9^0wbcl9UoByWo1CnF)kS(EMd$5iit2d^ZS2$@3Z%L z-0n`0y3kLL%+wjb}z z&-adPA8qs(cB~G%>nr(aM>jvxTgYeDHcp@XW{6D+Y*Jtn1=h!l+n;pKfr~D@z=rx% z`vX_O$uyMV3Puy6xgJ|CIvPrut|ac2PrUKJG=EF>h*2DZb9RJ zfd4<-ALjqx;_t^R@6Yl7U*zw%R^FfO|Nk+6|MSZG2m1dH`TNB_|FQBOlZjnd`ups+yB{Ha|8FbA z|H;aGA8qrtNr6oYY*Jv80-F@rq`)QxHYu=4flUf*Qecw;n-ut#P@vOowWM<&kUq=$ zf6)IIAKq%sgNwip@IBz3?`*X`3;q<`2Hpl<0)7I_gDb(s;BnwA@DCUTz5@Oj{AX|! z%z|^k+2Aa2H->@#4&D!b8N3V(z_Y;~a3RRRIpAK55PuCm1b!8~3A_}X0GEKzpoHHE zJ_EPU>;lzo(R4l+>0^b4)88;6F32`2iw6p;7b@2 zJ_g_-^o3>__*@n~WQ`FYIsy4kFY(x0DMSRaiCt8(AXslLP->yq9nOr_R(j*<_Kx<}gF7RER0GRH zVM2H)>oc0`t<7}0-C;f&71==?SE@8rO2g~(@%pfz<*I%{%I+W^QHL=!J(8z2DN=5? zW2J7$$D?e%GwLnSZV-L7Gwu!g*-GzlPVF;asF_;aZoZh!jq>qjiaMHBNw@BbO*M37 z^POVcS(;nztfh(IXXLv~wyYK)NJfxRD4V74>~Iysxe#+vGL-_u{Uni z&`JFdI->uj5s)qA#1DHBc*clo`B#nN!FzNShYjI6{mJ3fDG zZk(^Ktx(R>y|-3s3IwG(YrVN6c>HP18|nN%<)T=b=vM649@6WGfcQ_qyfh{dtRF58+#xzeL+@ z6la?+uYQNV<$9An@kH7d`p~Qjwat$b`^QprgRs}{jTyF%HwN~$@0#ll?BA75Ujqjn z91EP#`s=prdFE<)>2>=u{~&bQ#g)#|w(OeALcZ?38-CZrFY*yH;wU50ql4jLpQ_{C zA>*lRZDXa^AMeV#y-^3c&W;Cz?W>*s#`a#HM(8i(+dJd&us6R>&)Sw1Z#%=K^;IRG zJi|S-r|F`zvb}iSvNC7aEpIR}bXNA5izqXLQDn3+n#qqbKkjsej+~=?-AdkpyDSWI z4ZZPljv%m-XH0EF1_~{#M{Ydfa7rp2dcO}e9wcw#+}7T0LvnrS<}^#MNWrGa#ld>N zD}iOT!=G+%m@i18@RL51bALSD%8xDNYvb%{b9%jIFup>OOz3bOCsv|hadUE$-DXu* z&l(Sziie|o4szvlZcsP89p+0AbCkUCBOF;PdB|jXq_eV~mmgJfB#@|94zn^inh)cn z$XLsf<8v|G7~aq3v6f8OwYPrGu02l@ZHN^rxHufF`nJq^t80VdIGb*bhZ}Std*wZk zXNoe6LNGUI4g^_XR1Qx!W^(W;nuMttd<|;Qy;cWD^11Fhg4=@FX(gs+Gau3Av7aa9 zFm#iAxY$|9%K`U=$K}uoWB2Jw2DM(%a(!vPZ+Gitj*@kWH$K1o7}+xjli<&A^I`I& zD2y8v{!NLfuA{39hI+uYEQI6UbL-+F=ZZ6zK4)NDLIv zr8F#PG|D@}g=P12P~-@QaE~N%Ahd^c3aP`rT^Ouwgjd}>U(^3B@i@nCve8<;&|5^P zUGDU|ISg;fGz!md;TNtcElQmw(pNhxz3z4!LB`feU^_DP_La`@jf4TN3}A6^L|@qL zi->3nY8_&2!0Ze))9}51iK(uw&(rC)ujb?BL6>xjPPl!6k=K&`|3vh|9y+A-|Gi!z zyc>Q0jbJ}`JosMlRdo7~f{%b-0RI783ciXS|F_^{;1=*a@I>%MboRdh?*q4jRq#~s zXmAhu`rm?2fj5I|!A0OZz~|7@-vW+;99#u10T+XRMnC@wcsr2pz6_oMq_>{~zKDMQ zyWrJe8Ege#M=yUj=zw#;U!!~fGI$}_3mypWM$i5@kS_jG@H}uW_%`q%X!C0DLLfRV zgUxG`0-F@L4-}A)qRXr8SHRSwAf3F}KU^XbVVHM6LsT89M zWqq8Z6s{qD3cb#u*XZ7{PoSer)A zXb$c=6-Fw03zbJRD$36sug+%ui~gDPgCb0{wWU>@%JvtX%Jt*ydJ_>{9z_Nw7+HTd zb?p?X((a+D+97(c+dic3Ws1BSvGgWtq@iSq$uuN^ZIrR1k=6>KbeH86f}z5y7m~F4 zLf%JtLomx5dU-jsscf$(hv8vqIm%f25K^(!({M!sIkqHdRn3D*;?jNc;Brk;bU zp4dNvCabz$dtkrm(U&3K!!$DSryXVP#S+KdJy>1qpp9F2UsKDq(o?ddy|E|qfb@rv zV}?q~gpk^LFfUosLo)*+S=M+~-3VKzhpIV|&Ff|JNQ2#21B=yQBZrJG5xDG${A4q; zXdmdy!hj7SsP(~+fgJ(sx;jujX3AoM<-VnYh0E*XsfvSXU(CL;;-Lt>sfe!cjT>^}9Obxy9MpVFy5Ew&yOZ~h%H<-T>Lk9*u3(vApuX_f;#~W)o z5!&5_-YU#rTYuZ-XrXyA{C?0a+gc21Z_C<#-q24USwFj-J_}3jLy^O&nkkQF>ItRRv}|Q=1zK2+ zkloWOrV@s|0v0{YxWr)H7IdV@GMrWh16I$%6fYy0CZf^8u!o_F={Y0#+Ike9ZeZet znf3FdnIAUH2pWQ<<+|}qhUr8yjS==pFF&fRI>zbZJQ>O-w&Xf>VI8&fJXZ=e-xj)S zx-_J^-JeywylI)QJeHJn`|3`Ay`xDFn}b#e(ZJyXFIdO8*KOz?i|x#$Agw@-2V-I) zEu4@jCp*o-EtZaKJEJX!wjyE$o6cyafp^vUhplX`_ebk%GG@Uoiv+xx%UdTaC6hsW z+VuY)MvT8*j_T8v*9oPjvkAD9F za10y(PXiACzk_ZsUH`>kKez#$4;}~p4&7es^|ygHfgcCY2G0T)fk%KZpx?g{JPT|G zj{sjozyBQgEchsp?*DV32M&SDzy;u|==~oDC&6KG06Yx*6}tb2!P~%b@KkUQI=^fI zZwGVWV(7;q2k>>md211|%YgY&_8 z;4GlM3ZlQY!F^DvlA!9Ax@^%KOIPSZ8A1ZnDgAVmnKT(ig_cF&PdegQvD+8o?#ag; zjB%4z1LqNltOLgZqV7zuGIr?nn;AWt!Ll}uX`E8bO44vYw|NdSd+<<9L1l_TNb5vf z;y{>0(uJBhiPcxlD@mvjAqkT%TFOV4($yYX5*}>2_*s$5al$`$Iv_ky0b$v|=qQ|~HW#@`GwZQ+6 z=fi=U#%;Yy|MrgOEIHIFqEhRM+`D9Xp7yfIR@a%!h62U28hho@aq|$PrPt}@UqFM! zw$o#yys26(4pvyqQ2i~TUC0Py`GB0P@=>>TQ?XS~kQJ?bjni5>_wUJ97_wdGwqV27 z6>yRu=qoINqUwR78e67w3DwKvEf7T$LVb4Gl}##5g$^MZ%Pk%K7{{rNH8svb-u0)s z2va^YSM!lmm3rgMjg?l&BHS6h8tLkPaj=G$%{jEarj0$O*Ea#j3nz6gK?ya{<0#X3 zX9bbHt5M=wDruw!vbd=>6fE4E3s@sx!aPs3Rn;g{7t}mCk(ny9KqK1Mn070aAlxfb zrR!|24XRwK6t|>U1c|dKRBV!zaa_*|GHfsP#4;x6o1sYG8AL{5-P#C@(}S%-F;GXW zJ|QqQGtC8Q>g#JY?lMt9Xj0)qeG1QnSf4;Q-L1V!=BkRDSO`36c$x0cV7Gj-6Lu@? zZOy&X@+`^~tX?PD_Q<-2OQm>l=w!XKQ9mC_(~&b3EiaWTDfQU`X_~ShqZmLhmsvgY zb{Tce5Tq3GyH~C=`}SBj+vbZ~RYOFGIGODWK4P|>IgwZU?i5tX zh7J|UrVPTh*tRyIAySKLB4T7W9PX{HX-*SWW@Z|AmC{;F%qolM{DomIGlQ_VU7l>v zPU@VFZYBvUSQJ?k$C?;Bk+PeDCntV}p6zQ64zVl=jAey0g=>J8Vpp$vmusM(B25~Q zE|Vhb6Jnp?ztarN=4Mf5QCii1)hh_OrL5e;LuLC%EoLa04f}HZzoBWmgZO3Tl@bpqM~Xmx4p5g>oKyyG;Mzh9db{um3N5rSe1Q`@ad^3|^Rq$Hy5^xQ8B={3-1#baIz>~mP z;BT-Mybbih2@Pq_+Bu+UpFjFa+dSiUBh+l+cfoM{d%MLBt25>gF_;eSOD%r{k)|M9E5dH*>`% z(R#hG=G(O6JYi4R%{pwuDnQEa!-UxiHLl*9KRXEOL19J%vgaLFRf6UaCeCqb&?G%CEx6W?c=8DwhtY2p;g}KV6Buw1I_7G!J}~` zrY4D(cTA~V;)>38IN$JEvXb&vzTLZ9q09vpitJ*!Dw9(3Vg`vgAL2{Pf`of){Gnop zn98p0V=;1Jy&SAkptIq9j;-5PMpXjGH!dkk1r20WaYN1sHX|=q?CD$Ou00%&w0t?; zzNDQvR3zm?&^YJ{){exb7xx!d)-hoDc6TEFV((bS(8Gh$l_Gn}Y<6)LYz(d=X^Fom zoJlGeALern*efr!ZiZAk%&CgjZuNLoKZm&gmDGi)>^gJecR7zchpjBVfmY2jTtSCf z$Cs^J899xNG^xDWQ{BC=TArfA)Jdb*o?d6t<5~)@i*_n|VKf%gkM_{@C+84*@>I4E zwrNS;S1wd^S{^{DaCSqL4pgl)-q#Ev%JBu zWN{CyN5pWN84D9gBrHm3HZzBfm=JZwg2sTb3DvQ@8Poq!eOoW^`hPe-?{;+kU;|j? zcLz*^ucPn(5qLFtF?c$7D7XiGU;h8H0jz`PfN5|iy8Y|HVK4`N7@Q04Mu-0(;4=f} z4{(6{H27z9_P+w}1TO{JyWa-)P{@x;hZC z7tzb#4SohlPd^vjiEjQYU;$hS9tu8>ZvGB%3wR08`hFYyAovnG`e(tL!4NEhYrq4* zC(+Aa2fE;~;D15OJHT&%n}KM%2J8ymKfU92tetpf04ny4qyhB1)b!ycz18hzli!UA z8?Epvy{q3&RMMtwR&$a{Sl61o;O4pC;PCpI>qA!tyT3UDSTgbqbqjsVOO?T>QWftQ zFokt~W>{=%-zMkM+TM2cXr41PVB7dcO$P@W%QD#%zctBHk%}x7)rwy{Ni?5i+)LOj zf`~T}Po7O9t;srONniSbV*=2uo=8aS&%Z`9cG(||R+Lj%~O2HErx7Z7zw{c2PS*S>< zk;+1`&%yN`Us|Z{|2HFn=k-;iL@QKO_midn^rS7FH2A;icT|A&T36dCVIfddn;L=^ z+pI(A;=!o`+sPWW81}Rw8pb>GG)CwrPV!$v*kTofkl{B6Ni>9X!U@5=l+v=gCx7tC z_R5s#75`~S%bg}csDyvGLp>S-%cv28rY?pJK{Yl)(7G)o8B7eG^3tuF7~DZSCVB+n z$8iZrS*ov<8@};>B5K)GRu_`a|8|J+5XdE9%-2LcS70_hm!0I z#1EdQO{FOs#d}=X@D0uGH9GI`T*@)y3c)B@Sj|3>Zi=57 zoZOPt<4<%T3EkE8#=YM4#uAd4D!PQrbz7IIY~_KJ>}K178?;vmS|NCtUbL;U+1)lt zKT#7@5sgr>|B6tnUf~K_zy*1kZkJ{|?Q~dvf{{Jp?{XVz?d>{BSI{ur$E5IE12D<$n z;AZe*@FMVZ@C5KMAie*c;AZeMKzskS4`2#>C(wC*pF!XMB)Apa2(;G!Bj9ZCd363y zfY*Y9;A*f9d>x(t9pE|O*;aDgUqSc(FW}{1 z3((&G51{*Ny?#H?e*fFh_kR(rf>|K_|9_(Ae-X&f|CL}2r2lUPTDyM>_(^aOYz3c3 z-@gSM_qzYU>;IwCm025|8^>$em1p5Roh5AmG8w*IaolkMuQo$5L2cHKfr(`@pSBMr z6KG=^1?!mAVuRB-=8BozSZpcpWSW&Zk*@!%vEFQD}-zXo*`B zXr2zUp*`hHB6omLN%fox!fO$!%ydc9uKO}MDS~BLv8GRO*ej12m!13Dj*cTvLNeN0 zNsFy*et8TrC}VzQ)2)`7E1G+nW|R8g?Xg2bmYyVixPRFDm2sj~!+_nPrc)0B zP~+TI=qFzGQ?Ig`Riq51Hnhj^pMHbsPM_;LY{1`xWe6aVrNwlSy;Yv$1 zp`|NpwmN*Qhce}#|_AntjXUb`thP8iQb7eH8t#nbQYz4TvZCyP>@3jA#13Cp< z2Gqh3s=aom_nG$rtHCa@R;X#XOCE4#XVNCNKRm16yW_hcv$_^>t#)N-mT8wyy_|?8 zZug|g3sNKzO^SWXqRP?OtxCnDQBKkLX_HOrM7!u3)U!A~B6gzGaGE_eyCBxv^ifY4 ziA_4kj5NruW*~L8(W1;jFcFF4&L>BwAm~5@i~uwka|{+6+otY~T2cI|%Q{N3ewtEo zgxox)VfqsW=;DzSwcPeLw-h-Mmc)93-!>8a9gXN(duRxfoma2be> z-s7Yw`k5T-pjK#dM$vN^gK>-0<50WE*Vi0Njhv;#axIQ|yu<*Z*h)6+_3#I9wDe4u zr{i8tV$Q=%lZ@k6F58xc<4@eggaOa{1|@{HIMG&7Orc`V!8@tbz?wrTMO>F-B(A0D zDQv&3hC0SA5(HLM6wwnkE*uqaLM3#cG)!fZpJc#Sy!vQuG~z30YRGrQ+;ZZ0Sj;=Q zWmi@ewa(a}uhh?Y{3)=mIH27B@o#OrQ?FYn70%+1`0ovGu>!4_K>QV{9z>1CU{1(z zM4@k3EaS_Kp;MYq+L@^$#BC^zLg!ZqX~YhWe4kReyXUNutLGp0!=>D#uca6-`-plW{dJ_ zkZfi)?TL-+hj(AK?Y&ajwMEDE?PQIsen84)IQT#&PLj5RjK;nB9>?XoqktTLHqYNy zs7<*43)Cu+EkmkBWojiaR_7XP)m%KR0Y+91kMXN}foi4j&^G$csU(Tj(lrZmy!+vJ z4#IfVs!tlFBFD)&Nh~ax)fS{m#j$ki(uW<*JzH7Tb4w^&%WOYwnp2Gcw`W)%pv}p=RGS#a=nn_}fF>=DC{g!R!{*gih z$R&5??|5*S1jtXO&;E}@xb2cE%vI{-Lf_H+EM@Y@r@C< z_}$)u+1n4DDitw_mKA_MNl#5mY@0}mFbZ2vAMM^5Cr5Fn)o3__jSAE4i)CqiVmqx( zJ@r_am_t%l&!xg5GlTPDDc;MyB^}_F?PGO0&!(Qtr*>8NaGhCa4qEQ?Xe)iU#+&P7 zq}=_KSx92UarGRqJZnwVmi4he-XD}`48SqqY=}}>5}Ys$TA@n1Pi0Q+2@|C=6vR^) zouSi8f~Abz75H`TK+Hye3b{tnJ|rC8B&N@Q^@YG_!!3wt8lhYRA+G$wTA}>9r|o?z zrWop~7N5Ww>D|SClF}y8;wZogZJbPaN&l63DCA1>V3qbgt=wUYO4OzX6H={y>0)`k zC)S@RO?J{9u$#kA5Eu|!kleid~7CiCvn0V;X<;U^`KGEQn;BKz7Rkh~{3;I!x z6UTI`mR5E$>)LtfI@^rR$Ae@aO;O8QGpo39Q%Sio%l|2j#rA|&RWjN_*;1(VYHCph zwi2hUnU&+FZ4yRSDKR+@#Bp^tpdBDY^Q+PEF?1i%LTGnZ+lG~t#kQTbOyAJAp4cI%m>k`L!>jGi zLgsX*=lBWFDp+m`QN5`duL)juPGvCT^r&^R~@yF-^FVU@|ZoPr{mv&nkxyVpIL zZMWiEe@yJPjilVmqDpaQAg2U6Iy!fTgnpw+UoM+JOGGeZzt;bsj&eGdj+&hR|3P&9 z5qLJZ6lnclXaBzp$mjn&@MZM+&wzJ>*MKL1zelIne*a$qC&3Ni0&q6?U3B|Ccs_Up z_!K(*yTLlx2_6dcO};hoo#1cK<9{1SzyDG2DDXw}c@&9RX1!x1E>HqWKCE!Bv zdGz>S2QLE81TAnk`ul6aA@F?g6ri>J7Whr{_!oj_fX4#u#eX4qHqd&${P|xBM5mEx z^-WyrZ{e!56TL~t>137?z4XEaTh;gr#^T-k;;fLwm1rrR5_|wHkc;4t>dU+3!4e8z z2^5THt99mG#IVD*y>G+hU~<@mZP@Y0Eb0x8;$Nw?vVry)yLm(&m+qTvyur~j>ki~( zY^L5;$gX1bNf$4zkC=miktROZCNTBAgY4YDpmK)rXCS!C`^XMx&)-cJRGb{HgEu<7#I+=yhS1`+c zMS0c7hn}_<@JV0dyn*<&iq%dxZ;&Z0bhdph(>LEToodJ#;Mium_I=X29H#J#ZPUL) z$%9fZzY>);N`ri6BcGJSl@eqVc}HA{9cmnUvCd|QiU6ZrQ*t`7wWY`D1t09Ofvm$R z81}JJzxTG4hJmC7ISa2E_r#X?%Ov*gjmruL33zL1)y}C3d*Mrl-Xa4_&etg(AwVmZ z$`i`gJRn`cynSqzahz@!ncZ1$eGZ{0Z0bH*TLm<>tl||X_6Ey(2z4mw=Alr<%ETq9 zrTD&B?8ERx}i{-ee+nTZ5-O0*YQW zD*#SknH zH<2P-MG!=g(wPY%fbJx19lEfDt79vU83Q3kXw}o9Y#qw3k%c{P@Aqm~B z3)WY?YUznBls%3^DoGvaP30XkAxt#Anv)b_`1%3|QtG_Pp;I?B0{w|G92FE)jq8m<7c@(5kY=2%;S`4|N&iQEZaqypE&BhJ zUa!3koqr7sz_nl(xEr1SSAq8aKLdO>cqF(Bz5iX{6<`F;2cJjZe=E>gf3raP{~w|A zzZyIXJQCcFzW?ul*7nZ=A4cc@32+H$fjiLmZw8Cthrt8D?G*Oqpa(7l^5_3Ua2r?x z-wVEkuKy6Xn(jNgjJQ1!B z>MJ$A1Bv0#RaJ=Osq?f-9w=DLGEe@v9a%+EvL3@geXBc}rWQxhT^HOJim*E#jZ?L3 zhs@IUX|!;MfOXy(-sn42GQ^lW=_nl0s1LI^*Y@H=D-)Vb=`;;!Q_M7x6m+ojZe)z9 z%U(*PY`fKya7p4o%WFu?xPSN(4B}&_hugm!9@_5E3Mqs;6Na;8QL4a3$|90Nrc@1* zU{e)~mYSsEhir{;MLKv|CpxQfv0d5P`e<1Ma$ThrqU%n{eYARcL%7g}5)J9Qay)2- zcj=w9n)|gy#Pp^Pq1YA4FQno6x64fhbNi;eNwV&55TfZ$@1J}$OBztW)A}4dtSVW- zCimr#fXTU>m#J1(J!bTWja`#TrCr&GbJKcTq7cr|bRU!G66ttLJp)~4LW?9JPcg@TV zG(xG|jy$b2&ZOTqUaYbyt=zXr&E8Yqjy)-vv;pr^{|O1E3DEmaOjZCw4BdT?@D`%e z_KGyBRa~AwW=2s8uF~#{dG5^b35NPK8A>pDfQu4gjGtyiD9vwTuq(RtCa;Vp^QNCj z#c(EfulqKP$*G6i2>xfBdTQ*8&woQoK4gdnXgQVkP_5i#c*>*9f8 zh&H=noGWIp;`{;D*_%0K>0=dk0+h(P!zC%|78?gRK-MMzEV2z{@nfMplpsV{sxEgB zTevRm&|2HzGd7%f+U<>W0PSqC>&X#%`#hhIKOFs^tZ{4+?Se!KDXf}CwW!pD1~U4% zlDaBfi3~WcgG8KCs$caf=uBk>#Xgm|(5#@OsZU-Ru(S*Hsa!^=Jha8Yx$gQJ5;%LX zM&6ZE(d57;A4J-9*0y9vzfQf5y^(ZyU@I~4Vd$D-I&t0XjF-gRN}Qu^Tu>{QU#*i; z;Z$;2OruI^>Hqqc-lwH=qyKL)wH6&1egBoObFJQm!GEkJw!^*z8>ffaBb zcr;Ks-v@pU%!4a|%Ku>S2iOO00Y49(2_6r=fsNpE;LYGhptJjD!9QXr_&E3=cqRA| z@MQ4q;E%BxybJs$cokRz7lC`Q8TPG9xW-doLEe$}iTZ?)53=Kxq)EQd34CaUt zadX~f>qMw8AK-JJyNzv8ZU?wn8d1`h6=hp+kuVLrppvNNeJ2h&id((NW^^#g^HAA7 z;KV66bED~nfJLg}uf7M8lIH75+OZ!5uB#}ty1u}@KXl&RS4e;NhYl8rSCW!KC9gOQYkO1a z?~BPnz|^M$+2YwoOv~3?^;}m*(K9V)QKO8jsT_K)>;9KVv1v)OXsA)@m5ME|u)5Yb zdaY%@{2y-_4Bf!^Up?A=E2L)<#ol^Puj)aLecMIl^WWpU~*~m*hvqe2ql7?wyjQm7Dp338;+?K^}hu~W%zRbhS+rS zg^ZN6r|H2}0(*X65NL9dOB>)Bq&G0%gifW(p8m45OFL`qi6lf@Nvep@_a}HvE4bqE zeZJ(XWEca*ZwD+d%`wlQu_T^8bqTzZ5X|M^@={D>krK01S8jHbR>Fd5rECn z?z0D>#A2}SlLn@|8*kr_M{n zHwSL@c#*@KBK&!7QM6K7bd{v#a#6&NI*_{9OMx+Th+sz_t2(!*?7`NdT9&x~V{cLV zN?SW<@RFpUuof^aM357ZfK;oNPtb0zY>e zu?0TkbMX1=JD;#~=XTu|7Z2fBQ6w4L4er1`E>8CzQ}4n&Q;;}5A9TBO1%ccrQ?x8! z{>1GhN|fCw>Pb<>gYDnDRlXm;_gnV&efIYy_V?BH_m}MN>y!1aa6`|0u)0wDD zh?Y*FxPeXD`%d{uP%HjvW@Kv(4b`9>NCVha7Tr&{5m`w5UJh>C{{Jbo&i6|HMF0OW zuYul;zJDvw+5dCkDd6GYcJzJi0k|4y@BhCA4+Po+@H1clt^)U>GyD;egA(6bmpJV{eL|8EA;w51@8gB1Kt2$1(v`;FaxCjp93BM z{u-TMegp3SuLlQ!zT@{8pl<`*0d4_52QCBmqVIn}IzMt?yyp zQnA@4cHM#fyULui@`d4;Qqw7a*W0&kgY~z_mi<6ctPeTrBpKRdzgJpysGrV)744Gk zwCR-S#rZNjOKCmHIOH!DxZDz~08aI(2bx%cg_XEDOQ zG{Nh9SAm)M&{3WY8z5Th z)&OA*ytlAyyN@bet!#~>aSL0>M3O~iScJGg1Pkj36`IybwrDhCIndY6_lah)x%jXv zorBI>8ls|U=za^Gm4$Vz%_wZU&X^j#(Qem!Q-_AoBSnWWf&#^T3-8+#dwK}24qO|T z6MCB&u=V|RAt4Dy%#ggc*z<NJaHY1Yw5ABx>8&wfo<@j-z@n6LQ`5xbCF$Zpw~uX8xTaP0bES8RHqz@7m2^l}PhBzFklWUS^#-T6al(6Uz3f@{`fii1KmWtyoWkNFI z6nS~6G_Z_Gle=q>u(B3cDdQIwSZZ&H9^vv`;-Hc5jH1W*B3yKNIr9`?c;jJWwMaAu zW+%pgXz7lQ94aUbE7DB`cQb7e+-;fR41^A9XUnX`N~wt#Vae(~ueTP8$eAy>YW;r; zdho;0kEQ=}sE}R%2VMWY;0<5{JPCX+co6si`u=YYRO@7K8iKLw71XM*nle}R7gXW;EX>;FFs z4uhRQ`vvZ%4sQk<;4runSiO9mHgB60*rdQF1vV+LNr6oY)F=?-FJ!1qmosc!4bo$j z&=HIGZn~Upx|}77f~=^UE@$!-os*Gi)8)*$&6+M48Asr<#$D3&w^I^r)8(x2wlaGD Z|L$^DwaNs|-y3Bb{p7Y)eG+A|`M literal 0 HcmV?d00001 diff --git a/modules/device.py b/modules/device.py index b8d038d..0d9bca9 100644 --- a/modules/device.py +++ b/modules/device.py @@ -6,6 +6,7 @@ from copy import deepcopy from logging import getLogger from os import sys from re import search +from operator import itemgetter from zabbix_utils import APIRequestError @@ -64,11 +65,11 @@ class PhysicalDevice: self.status = nb.status.label self.zabbix = zabbix self.zabbix_id = None - self.group_id = None + self.group_ids = [] self.nb_api_version = nb_version self.zbx_template_names = [] self.zbx_templates = [] - self.hostgroup = None + self.hostgroups = [] self.tenant = nb.tenant self.config_context = nb.config_context self.zbxproxy = None @@ -152,7 +153,10 @@ class PhysicalDevice: nb_regions=nb_regions, ) # Generate hostgroup based on hostgroup format - self.hostgroup = hg.generate(hg_format) + if isinstance(hg_format, list): + self.hostgroups = [hg.generate(f) for f in hg_format] + else: + self.hostgroups.append(hg.generate(hg_format)) def set_template(self, prefer_config_context, overrule_custom): """Set Template""" @@ -333,12 +337,16 @@ class PhysicalDevice: OUTPUT: True / False """ # Go through all groups - for group in groups: - if group["name"] == self.hostgroup: - self.group_id = group["groupid"] - e = f"Host {self.name}: matched group {group['name']}" - self.logger.debug(e) - return True + self.logger.debug(self.hostgroups) + + for hg in self.hostgroups: + for group in groups: + if group["name"] == hg: + self.group_ids.append({"groupid": group["groupid"]}) + e = f"Host {self.name}: matched group {group['name']}" + self.logger.debug(e) + if self.group_ids: + return True return False def cleanup(self): @@ -514,7 +522,8 @@ class PhysicalDevice: templateids.append({"templateid": template["templateid"]}) # Set interface, group and template configuration interfaces = self.setInterfaceDetails() - groups = [{"groupid": self.group_id}] + + groups = self.group_ids # Set Zabbix proxy if defined self.setProxy(proxies) # Set basic data for host creation @@ -567,25 +576,26 @@ class PhysicalDevice: """ final_data = [] # Check if the hostgroup is in a nested format and check each parent - for pos in range(len(self.hostgroup.split("/"))): - zabbix_hg = self.hostgroup.rsplit("/", pos)[0] - if self.lookupZabbixHostgroup(hostgroups, zabbix_hg): - # Hostgroup already exists - continue - # Create new group - try: - # API call to Zabbix - groupid = self.zabbix.hostgroup.create(name=zabbix_hg) - e = f"Hostgroup '{zabbix_hg}': created in Zabbix." - self.logger.info(e) - # Add group to final data - final_data.append( - {"groupid": groupid["groupids"][0], "name": zabbix_hg} - ) - except APIRequestError as e: - msg = f"Hostgroup '{zabbix_hg}': unable to create. Zabbix returned {str(e)}." - self.logger.error(msg) - raise SyncExternalError(msg) from e + for hostgroup in self.hostgroups: + for pos in range(len(hostgroup.split("/"))): + zabbix_hg = hostgroup.rsplit("/", pos)[0] + if self.lookupZabbixHostgroup(hostgroups, zabbix_hg): + # Hostgroup already exists + continue + # Create new group + try: + # API call to Zabbix + groupid = self.zabbix.hostgroup.create(name=zabbix_hg) + e = f"Hostgroup '{zabbix_hg}': created in Zabbix." + self.logger.info(e) + # Add group to final data + final_data.append( + {"groupid": groupid["groupids"][0], "name": zabbix_hg} + ) + except APIRequestError as e: + msg = f"Hostgroup '{zabbix_hg}': unable to create. Zabbix returned {str(e)}." + self.logger.error(msg) + raise SyncExternalError(msg) from e return final_data def lookupZabbixHostgroup(self, group_list, lookup_group): @@ -625,7 +635,7 @@ class PhysicalDevice: Checks if Zabbix object is still valid with NetBox parameters. """ # If group is found or if the hostgroup is nested - if not self.setZabbixGroupID(groups) or len(self.hostgroup.split("/")) > 1: + if not self.setZabbixGroupID(groups): # or len(self.hostgroups.split("/")) > 1: if create_hostgroups: # Script is allowed to create a new hostgroup new_groups = self.createZabbixHostgroup(groups) @@ -633,7 +643,7 @@ class PhysicalDevice: # Add all new groups to the list of groups groups.append(group) # check if the initial group was not already found (and this is a nested folder check) - if not self.group_id: + if not self.group_ids: # Function returns true / false but also sets GroupID if not self.setZabbixGroupID(groups) and not create_hostgroups: e = ( @@ -642,6 +652,9 @@ class PhysicalDevice: ) self.logger.warning(e) raise SyncInventoryError(e) + #if self.group_ids: + # self.group_ids.append(self.pri_group_id) + # Prepare templates and proxy config self.zbxTemplatePrepper(templates) self.setProxy(proxies) @@ -680,6 +693,7 @@ class PhysicalDevice: f"Received value: {host['host']}" ) self.updateZabbixHost(host=self.name) + # Execute check depending on wether the name is special or not if self.use_visible_name: if host["name"] == self.visible_name: @@ -709,18 +723,20 @@ class PhysicalDevice: group_dictname = "hostgroups" if str(self.zabbix.version).startswith(("6", "5")): group_dictname = "groups" - for group in host[group_dictname]: - if group["groupid"] == self.group_id: - self.logger.debug(f"Host {self.name}: hostgroup in-sync.") - break - self.logger.warning(f"Host {self.name}: hostgroup OUT of sync.") - self.updateZabbixHost(groups={"groupid": self.group_id}) + # Check if hostgroups match + if (sorted(host[group_dictname], key=itemgetter('groupid')) == + sorted(self.group_ids, key=itemgetter('groupid'))): + self.logger.debug(f"Host {self.name}: hostgroups in-sync.") + else: + self.logger.warning(f"Host {self.name}: hostgroups OUT of sync.") + self.updateZabbixHost(groups=self.group_ids) if int(host["status"]) == self.zabbix_state: self.logger.debug(f"Host {self.name}: status in-sync.") else: self.logger.warning(f"Host {self.name}: status OUT of sync.") self.updateZabbixHost(status=str(self.zabbix_state)) + # Check if a proxy has been defined if self.zbxproxy: # Check if proxy or proxy group is defined @@ -882,7 +898,7 @@ class PhysicalDevice: e = ( f"Host {self.name} has unsupported interface configuration." f" Host has total of {len(host['interfaces'])} interfaces. " - "Manual interfention required." + "Manual intervention required." ) self.logger.error(e) raise SyncInventoryError(e) diff --git a/modules/hostgroups.py b/modules/hostgroups.py index d1350bd..68b0bb1 100644 --- a/modules/hostgroups.py +++ b/modules/hostgroups.py @@ -91,7 +91,6 @@ class Hostgroup: if self.nb.cluster: format_options["cluster"] = self.nb.cluster.name format_options["cluster_type"] = self.nb.cluster.type.name - self.format_options = format_options def set_nesting( diff --git a/modules/virtual_machine.py b/modules/virtual_machine.py index 34e3394..8915832 100644 --- a/modules/virtual_machine.py +++ b/modules/virtual_machine.py @@ -58,7 +58,10 @@ class VirtualMachine(PhysicalDevice): nb_regions=nb_regions, ) # Generate hostgroup based on hostgroup format - self.hostgroup = hg.generate(hg_format) + if isinstance(hg_format, list): + self.hostgroups = [hg.generate(f) for f in hg_format] + else: + self.hostgroups.append(hg.generate(hg_format)) def set_vm_template(self): """Set Template for VMs. Overwrites default class diff --git a/netbox_zabbix_sync.py b/netbox_zabbix_sync.py index 79d8a20..08e3036 100755 --- a/netbox_zabbix_sync.py +++ b/netbox_zabbix_sync.py @@ -85,7 +85,13 @@ def main(arguments): # Set NetBox API netbox = api(netbox_host, token=netbox_token, threading=True) # Check if the provided Hostgroup layout is valid - hg_objects = hostgroup_format.split("/") + hg_objects = [] + if isinstance(hostgroup_format,list): + for l in hostgroup_format: + hg_objects = hg_objects + l.split("/") + else: + hg_objects = hostgroup_format.split("/") + hg_objects = sorted(set(hg_objects)) allowed_objects = [ "location", "role", @@ -116,7 +122,7 @@ def main(arguments): if hg_object not in allowed_objects: e = ( f"Hostgroup item {hg_object} is not valid. Make sure you" - " use valid items and seperate them with '/'." + " use valid items and separate them with '/'." ) logger.error(e) raise HostgroupError(e) @@ -215,7 +221,7 @@ def main(arguments): create_hostgroups, ) continue - # Add hostgroup is config is set + # Add hostgroup if config is set if create_hostgroups: # Create new hostgroup. Potentially multiple groups if nested hostgroups = vm.createZabbixHostgroup(zabbix_groups) @@ -243,7 +249,7 @@ def main(arguments): continue device.set_hostgroup(hostgroup_format, netbox_site_groups, netbox_regions) # Check if a valid hostgroup has been found for this VM. - if not device.hostgroup: + if not device.hostgroups: continue device.set_inventory(nb_device) device.set_usermacros() From f7eb47a8a83a450d918a544218992b32eb13ced3 Mon Sep 17 00:00:00 2001 From: Raymond Kuiper Date: Wed, 4 Jun 2025 14:23:46 +0200 Subject: [PATCH 04/12] removed scratch file --- modules/.device.py.swp | Bin 57344 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 modules/.device.py.swp diff --git a/modules/.device.py.swp b/modules/.device.py.swp deleted file mode 100644 index 62a997b799a5c93df7d70bce7a66d87b8f631d7d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 57344 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 Z|L$^DwaNs|-y3Bb{p7Y)eG+A|`M From 27ee4c341fee51acd44aec5377652cc0e388d526 Mon Sep 17 00:00:00 2001 From: Raymond Kuiper Date: Wed, 4 Jun 2025 15:18:27 +0200 Subject: [PATCH 05/12] Fixed multiple hostgroups for devices --- modules/device.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/modules/device.py b/modules/device.py index 0d9bca9..d5bb635 100644 --- a/modules/device.py +++ b/modules/device.py @@ -337,15 +337,13 @@ class PhysicalDevice: OUTPUT: True / False """ # Go through all groups - self.logger.debug(self.hostgroups) - for hg in self.hostgroups: for group in groups: if group["name"] == hg: self.group_ids.append({"groupid": group["groupid"]}) - e = f"Host {self.name}: matched group {group['name']}" + e = f"Host {self.name}: matched group \"{group['name']}\" (ID:{group['groupid']})" self.logger.debug(e) - if self.group_ids: + if len(self.group_ids) == len(self.hostgroups): return True return False From 77b0798b65c86ec2026b936c88734c7464f79e1c Mon Sep 17 00:00:00 2001 From: Raymond Kuiper Date: Thu, 5 Jun 2025 11:39:42 +0200 Subject: [PATCH 06/12] Added verify of vm_hostgroup_format (moved fucntion to tools.py) --- modules/tools.py | 46 ++++++++++++++++++++++++++++++++++++++++++- netbox_zabbix_sync.py | 34 ++++++-------------------------- 2 files changed, 51 insertions(+), 29 deletions(-) diff --git a/modules/tools.py b/modules/tools.py index 791025d..e49cf13 100644 --- a/modules/tools.py +++ b/modules/tools.py @@ -1,5 +1,5 @@ """A collection of tools used by several classes""" - +from modules.exceptions import HostgroupError def convert_recordset(recordset): """Converts netbox RedcordSet to list of dicts.""" @@ -99,3 +99,47 @@ def remove_duplicates(input_list, sortkey=None): if sortkey and isinstance(sortkey, str): output_list.sort(key=lambda x: x[sortkey]) return output_list + +def verify_hg_format(hg_format, hg_type="dev", logger=None): + """ + Verifies hostgroup field format + """ + allowed_objects = {"dev": ["location", + "rack", + "role", + "manufacturer", + "region", + "site", + "site_group", + "tenant", + "tenant_group", + "platform", + "cluster"] + ,"vm": ["location", + "role", + "manufacturer", + "region", + "site", + "site_group", + "tenant", + "tenant_group", + "cluster", + "device", + "platform"] + } + hg_objects = [] + if isinstance(hg_format,list): + for f in hg_format: + hg_objects = hg_objects + f.split("/") + else: + hg_objects = hg_format.split("/") + hg_objects = sorted(set(hg_objects)) + for hg_object in hg_objects: + if hg_object not in allowed_objects[hg_type]: + e = ( + f"Hostgroup item {hg_object} is not valid. Make sure you" + " use valid items and separate them with '/'." + ) + logger.error(e) + raise HostgroupError(e) + diff --git a/netbox_zabbix_sync.py b/netbox_zabbix_sync.py index 08e3036..340fabc 100755 --- a/netbox_zabbix_sync.py +++ b/netbox_zabbix_sync.py @@ -13,9 +13,9 @@ from requests.exceptions import ConnectionError as RequestsConnectionError from zabbix_utils import APIRequestError, ProcessingError, ZabbixAPI from modules.device import PhysicalDevice -from modules.exceptions import EnvironmentVarError, HostgroupError, SyncError +from modules.exceptions import EnvironmentVarError, SyncError from modules.logging import get_logger, set_log_levels, setup_logger -from modules.tools import convert_recordset, proxy_prepper +from modules.tools import convert_recordset, proxy_prepper, verify_hg_format from modules.virtual_machine import VirtualMachine try: @@ -84,24 +84,6 @@ def main(arguments): netbox_token = environ.get("NETBOX_TOKEN") # Set NetBox API netbox = api(netbox_host, token=netbox_token, threading=True) - # Check if the provided Hostgroup layout is valid - hg_objects = [] - if isinstance(hostgroup_format,list): - for l in hostgroup_format: - hg_objects = hg_objects + l.split("/") - else: - hg_objects = hostgroup_format.split("/") - hg_objects = sorted(set(hg_objects)) - allowed_objects = [ - "location", - "role", - "manufacturer", - "region", - "site", - "site_group", - "tenant", - "tenant_group", - ] # Create API call to get all custom fields which are on the device objects try: device_cfs = list( @@ -118,14 +100,10 @@ def main(arguments): sys.exit(1) for cf in device_cfs: allowed_objects.append(cf.name) - for hg_object in hg_objects: - if hg_object not in allowed_objects: - e = ( - f"Hostgroup item {hg_object} is not valid. Make sure you" - " use valid items and separate them with '/'." - ) - logger.error(e) - raise HostgroupError(e) + # Check if the provided Hostgroup layout is valid + verify_hg_format(hostgroup_format, hg_type="dev", logger=logger) + verify_hg_format(vm_hostgroup_format, hg_type="vm", logger=logger) + # Set Zabbix API try: ssl_ctx = ssl.create_default_context() From 298e6c4370433971c572e1f471ffa7e0fb9191f3 Mon Sep 17 00:00:00 2001 From: Raymond Kuiper Date: Thu, 5 Jun 2025 11:53:17 +0200 Subject: [PATCH 07/12] support multiple hostgroups for vm --- netbox_zabbix_sync.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/netbox_zabbix_sync.py b/netbox_zabbix_sync.py index 340fabc..9cd0eda 100755 --- a/netbox_zabbix_sync.py +++ b/netbox_zabbix_sync.py @@ -166,7 +166,7 @@ def main(arguments): continue vm.set_hostgroup(vm_hostgroup_format, netbox_site_groups, netbox_regions) # Check if a valid hostgroup has been found for this VM. - if not vm.hostgroup: + if not vm.hostgroups: continue vm.set_inventory(nb_vm) vm.set_usermacros() From bd4d21c5d8205e4753249370003528ba8572075c Mon Sep 17 00:00:00 2001 From: Raymond Kuiper Date: Fri, 13 Jun 2025 10:24:26 +0200 Subject: [PATCH 08/12] Hostgroup CF checks for VMs --- modules/device.py | 2 +- modules/tools.py | 11 +++++++++-- netbox_zabbix_sync.py | 14 ++++++++------ 3 files changed, 18 insertions(+), 9 deletions(-) diff --git a/modules/device.py b/modules/device.py index d5bb635..0293778 100644 --- a/modules/device.py +++ b/modules/device.py @@ -724,7 +724,7 @@ class PhysicalDevice: # Check if hostgroups match if (sorted(host[group_dictname], key=itemgetter('groupid')) == sorted(self.group_ids, key=itemgetter('groupid'))): - self.logger.debug(f"Host {self.name}: hostgroups in-sync.") + self.logger.debug(f"Host {self.name}: hostgroups in-sync.") else: self.logger.warning(f"Host {self.name}: hostgroups OUT of sync.") self.updateZabbixHost(groups=self.group_ids) diff --git a/modules/tools.py b/modules/tools.py index e49cf13..9cdb56f 100644 --- a/modules/tools.py +++ b/modules/tools.py @@ -100,7 +100,7 @@ def remove_duplicates(input_list, sortkey=None): output_list.sort(key=lambda x: x[sortkey]) return output_list -def verify_hg_format(hg_format, hg_type="dev", logger=None): +def verify_hg_format(hg_format, device_cfs=[], vm_cfs=[], hg_type="dev", logger=None): """ Verifies hostgroup field format """ @@ -126,7 +126,13 @@ def verify_hg_format(hg_format, hg_type="dev", logger=None): "cluster", "device", "platform"] + ,"cfs": {"dev": [], "vm": []} } + for cf in device_cfs: + allowed_objects['cfs']['dev'].append(cf.name) + for cf in vm_cfs: + allowed_objects['cfs']['vm'].append(cf.name) + logger.debug(allowed_objects) hg_objects = [] if isinstance(hg_format,list): for f in hg_format: @@ -135,7 +141,8 @@ def verify_hg_format(hg_format, hg_type="dev", logger=None): hg_objects = hg_format.split("/") hg_objects = sorted(set(hg_objects)) for hg_object in hg_objects: - if hg_object not in allowed_objects[hg_type]: + if (hg_object not in allowed_objects[hg_type] and + hg_object not in allowed_objects['cfs'][hg_type]): e = ( f"Hostgroup item {hg_object} is not valid. Make sure you" " use valid items and separate them with '/'." diff --git a/netbox_zabbix_sync.py b/netbox_zabbix_sync.py index 9cd0eda..5418b50 100755 --- a/netbox_zabbix_sync.py +++ b/netbox_zabbix_sync.py @@ -85,9 +85,13 @@ def main(arguments): # Set NetBox API netbox = api(netbox_host, token=netbox_token, threading=True) # Create API call to get all custom fields which are on the device objects + device_cfs = [] try: device_cfs = list( - netbox.extras.custom_fields.filter(type="text", content_type_id=23) + netbox.extras.custom_fields.filter(type="text", content_types="dcim.device") + ) + vm_cfs = list( + netbox.extras.custom_fields.filter(type="text", content_types="virtualization.virtualmachine") ) except RequestsConnectionError: logger.error( @@ -98,12 +102,10 @@ def main(arguments): except NBRequestError as e: logger.error(f"NetBox error: {e}") sys.exit(1) - for cf in device_cfs: - allowed_objects.append(cf.name) + logger.debug(device_cfs) # Check if the provided Hostgroup layout is valid - verify_hg_format(hostgroup_format, hg_type="dev", logger=logger) - verify_hg_format(vm_hostgroup_format, hg_type="vm", logger=logger) - + verify_hg_format(hostgroup_format, device_cfs=device_cfs, hg_type="dev", logger=logger) + verify_hg_format(vm_hostgroup_format, vm_cfs=vm_cfs, hg_type="vm", logger=logger) # Set Zabbix API try: ssl_ctx = ssl.create_default_context() From bfadd88542ea0b285e8c216a9b1bdba4d140c9fa Mon Sep 17 00:00:00 2001 From: Raymond Kuiper Date: Fri, 13 Jun 2025 10:49:40 +0200 Subject: [PATCH 09/12] perform hostgroup creation before consistency check --- netbox_zabbix_sync.py | 57 ++++++++++++++++++++++--------------------- 1 file changed, 29 insertions(+), 28 deletions(-) diff --git a/netbox_zabbix_sync.py b/netbox_zabbix_sync.py index 5418b50..3e50aff 100755 --- a/netbox_zabbix_sync.py +++ b/netbox_zabbix_sync.py @@ -85,14 +85,10 @@ def main(arguments): # Set NetBox API netbox = api(netbox_host, token=netbox_token, threading=True) # Create API call to get all custom fields which are on the device objects - device_cfs = [] try: - device_cfs = list( - netbox.extras.custom_fields.filter(type="text", content_types="dcim.device") - ) - vm_cfs = list( - netbox.extras.custom_fields.filter(type="text", content_types="virtualization.virtualmachine") - ) + # Get NetBox version + nb_version = netbox.version + logger.debug(f"NetBox version is {nb_version}.") except RequestsConnectionError: logger.error( f"Unable to connect to NetBox with URL {netbox_host}." @@ -102,10 +98,18 @@ def main(arguments): except NBRequestError as e: logger.error(f"NetBox error: {e}") sys.exit(1) - logger.debug(device_cfs) # Check if the provided Hostgroup layout is valid + device_cfs = [] + vm_cfs = [] + device_cfs = list( + netbox.extras.custom_fields.filter(type="text", content_types="dcim.device") + ) verify_hg_format(hostgroup_format, device_cfs=device_cfs, hg_type="dev", logger=logger) - verify_hg_format(vm_hostgroup_format, vm_cfs=vm_cfs, hg_type="vm", logger=logger) + if sync_vms: + vm_cfs = list( + netbox.extras.custom_fields.filter(type="text", content_types="virtualization.virtualmachine") + ) + verify_hg_format(vm_hostgroup_format, vm_cfs=vm_cfs, hg_type="vm", logger=logger) # Set Zabbix API try: ssl_ctx = ssl.create_default_context() @@ -152,9 +156,6 @@ def main(arguments): # Prepare list of all proxy and proxy_groups zabbix_proxy_list = proxy_prepper(zabbix_proxies, zabbix_proxygroups) - # Get NetBox API version - nb_version = netbox.version - # Go through all NetBox devices for nb_vm in netbox_vms: try: @@ -191,6 +192,14 @@ def main(arguments): # Check if the VM is in the disabled state if vm.status in zabbix_device_disable: vm.zabbix_state = 1 + # Add hostgroup if config is set + if create_hostgroups: + # Create new hostgroup. Potentially multiple groups if nested + hostgroups = vm.createZabbixHostgroup(zabbix_groups) + # go through all newly created hostgroups + for group in hostgroups: + # Add new hostgroups to zabbix group list + zabbix_groups.append(group) # Check if VM is already in Zabbix if vm.zabbix_id: vm.ConsistencyCheck( @@ -201,14 +210,6 @@ def main(arguments): create_hostgroups, ) continue - # Add hostgroup if config is set - if create_hostgroups: - # Create new hostgroup. Potentially multiple groups if nested - hostgroups = vm.createZabbixHostgroup(zabbix_groups) - # go through all newly created hostgroups - for group in hostgroups: - # Add new hostgroups to zabbix group list - zabbix_groups.append(group) # Add VM to Zabbix vm.createInZabbix(zabbix_groups, zabbix_templates, zabbix_proxy_list) except SyncError: @@ -268,6 +269,14 @@ def main(arguments): # Check if the device is in the disabled state if device.status in zabbix_device_disable: device.zabbix_state = 1 + # Add hostgroup is config is set + if create_hostgroups: + # Create new hostgroup. Potentially multiple groups if nested + hostgroups = device.createZabbixHostgroup(zabbix_groups) + # go through all newly created hostgroups + for group in hostgroups: + # Add new hostgroups to zabbix group list + zabbix_groups.append(group) # Check if device is already in Zabbix if device.zabbix_id: device.ConsistencyCheck( @@ -278,14 +287,6 @@ def main(arguments): create_hostgroups, ) continue - # Add hostgroup is config is set - if create_hostgroups: - # Create new hostgroup. Potentially multiple groups if nested - hostgroups = device.createZabbixHostgroup(zabbix_groups) - # go through all newly created hostgroups - for group in hostgroups: - # Add new hostgroups to zabbix group list - zabbix_groups.append(group) # Add device to Zabbix device.createInZabbix(zabbix_groups, zabbix_templates, zabbix_proxy_list) except SyncError: From b62e8203b698697a9b3ea955abaab571abb9330b Mon Sep 17 00:00:00 2001 From: Raymond Kuiper Date: Fri, 13 Jun 2025 15:47:31 +0200 Subject: [PATCH 10/12] removed debug line --- modules/tools.py | 1 - 1 file changed, 1 deletion(-) diff --git a/modules/tools.py b/modules/tools.py index 9cdb56f..5a7d8d3 100644 --- a/modules/tools.py +++ b/modules/tools.py @@ -132,7 +132,6 @@ def verify_hg_format(hg_format, device_cfs=[], vm_cfs=[], hg_type="dev", logger= allowed_objects['cfs']['dev'].append(cf.name) for cf in vm_cfs: allowed_objects['cfs']['vm'].append(cf.name) - logger.debug(allowed_objects) hg_objects = [] if isinstance(hg_format,list): for f in hg_format: From a7a79ea81ea55b01044673b9a53305f6340fa8a2 Mon Sep 17 00:00:00 2001 From: Raymond Kuiper Date: Fri, 13 Jun 2025 15:56:21 +0200 Subject: [PATCH 11/12] updated README for multiple hostgoups --- README.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 0bd6bfc..a938572 100644 --- a/README.md +++ b/README.md @@ -175,13 +175,14 @@ used: | ------------ | ------------------------ | | location | The device location name | | manufacturer | Device manufacturer name | +| rack | Rack | **Only for VMs** | name | description | | ------------ | --------------- | | cluster | VM cluster name | -| cluster_type | VM cluster type | +| device | parent device | You can specify the value seperated by a "/" like so: @@ -189,6 +190,13 @@ You can specify the value seperated by a "/" like so: hostgroup_format = "tenant/site/dev_location/role" ``` +You can also provice a list of groups like so: + +```python +hostgroup_format = ["region/site_group/site", "role", "tenant_group/tenant"] +``` + + **Group traversal** The default behaviour for `region` is to only use the directly assigned region From ba530ecd58402f1599d2bec4bdc2d939cad8a680 Mon Sep 17 00:00:00 2001 From: Raymond Kuiper Date: Mon, 16 Jun 2025 10:28:17 +0200 Subject: [PATCH 12/12] corrected linting errors --- modules/device.py | 12 +++++++----- modules/tools.py | 12 ++++++++---- netbox_zabbix_sync.py | 14 ++++++++------ 3 files changed, 23 insertions(+), 15 deletions(-) diff --git a/modules/device.py b/modules/device.py index f02f35b..e61cede 100644 --- a/modules/device.py +++ b/modules/device.py @@ -320,7 +320,10 @@ class PhysicalDevice: for group in groups: if group["name"] == hg: self.group_ids.append({"groupid": group["groupid"]}) - e = f"Host {self.name}: matched group \"{group['name']}\" (ID:{group['groupid']})" + e = ( + f"Host {self.name}: matched group " + f"\"{group['name']}\" (ID:{group['groupid']})" + ) self.logger.debug(e) if len(self.group_ids) == len(self.hostgroups): return True @@ -500,7 +503,6 @@ class PhysicalDevice: templateids.append({"templateid": template["templateid"]}) # Set interface, group and template configuration interfaces = self.setInterfaceDetails() - groups = self.group_ids # Set Zabbix proxy if defined self.setProxy(proxies) @@ -702,9 +704,9 @@ class PhysicalDevice: if str(self.zabbix.version).startswith(("6", "5")): group_dictname = "groups" # Check if hostgroups match - if (sorted(host[group_dictname], key=itemgetter('groupid')) == - sorted(self.group_ids, key=itemgetter('groupid'))): - self.logger.debug(f"Host {self.name}: hostgroups in-sync.") + if (sorted(host[group_dictname], key=itemgetter('groupid')) == + sorted(self.group_ids, key=itemgetter('groupid'))): + self.logger.debug(f"Host {self.name}: hostgroups in-sync.") else: self.logger.warning(f"Host {self.name}: hostgroups OUT of sync.") self.updateZabbixHost(groups=self.group_ids) diff --git a/modules/tools.py b/modules/tools.py index 3f7bf81..0b600c2 100644 --- a/modules/tools.py +++ b/modules/tools.py @@ -101,10 +101,14 @@ def remove_duplicates(input_list, sortkey=None): return output_list -def verify_hg_format(hg_format, device_cfs=[], vm_cfs=[], hg_type="dev", logger=None): +def verify_hg_format(hg_format, device_cfs=None, vm_cfs=None, hg_type="dev", logger=None): """ Verifies hostgroup field format """ + if not device_cfs: + device_cfs = [] + if not vm_cfs: + vm_cfs = [] allowed_objects = {"dev": ["location", "rack", "role", @@ -130,9 +134,9 @@ def verify_hg_format(hg_format, device_cfs=[], vm_cfs=[], hg_type="dev", logger= ,"cfs": {"dev": [], "vm": []} } for cf in device_cfs: - allowed_objects['cfs']['dev'].append(cf.name) + allowed_objects['cfs']['dev'].append(cf.name) for cf in vm_cfs: - allowed_objects['cfs']['vm'].append(cf.name) + allowed_objects['cfs']['vm'].append(cf.name) hg_objects = [] if isinstance(hg_format,list): for f in hg_format: @@ -185,4 +189,4 @@ def sanatize_log_output(data): continue # A macro is not used, so we sanitize the value. sanitized_data["details"][key] = "********" - return sanitized_data \ No newline at end of file + return sanitized_data diff --git a/netbox_zabbix_sync.py b/netbox_zabbix_sync.py index afab914..d9ff71b 100755 --- a/netbox_zabbix_sync.py +++ b/netbox_zabbix_sync.py @@ -83,12 +83,14 @@ def main(arguments): device_cfs = list( netbox.extras.custom_fields.filter(type="text", content_types="dcim.device") ) - verify_hg_format(hostgroup_format, device_cfs=device_cfs, hg_type="dev", logger=logger) - if sync_vms: + verify_hg_format(config["hostgroup_format"], + device_cfs=device_cfs, hg_type="dev", logger=logger) + if config["sync_vms"]: vm_cfs = list( - netbox.extras.custom_fields.filter(type="text", content_types="virtualization.virtualmachine") + netbox.extras.custom_fields.filter(type="text", + content_types="virtualization.virtualmachine") ) - verify_hg_format(vm_hostgroup_format, vm_cfs=vm_cfs, hg_type="vm", logger=logger) + verify_hg_format(config["vm_hostgroup_format"], vm_cfs=vm_cfs, hg_type="vm", logger=logger) # Set Zabbix API try: ssl_ctx = ssl.create_default_context() @@ -173,7 +175,7 @@ def main(arguments): if vm.status in config["zabbix_device_disable"]: vm.zabbix_state = 1 # Add hostgroup if config is set - if create_hostgroups: + if config["create_hostgroups"]: # Create new hostgroup. Potentially multiple groups if nested hostgroups = vm.createZabbixHostgroup(zabbix_groups) # go through all newly created hostgroups @@ -249,7 +251,7 @@ def main(arguments): if device.status in config["zabbix_device_disable"]: device.zabbix_state = 1 # Add hostgroup is config is set - if create_hostgroups: + if config["create_hostgroups"]: # Create new hostgroup. Potentially multiple groups if nested hostgroups = device.createZabbixHostgroup(zabbix_groups) # go through all newly created hostgroups