From a468278b4d5431dac99a9294c22ba09cefd85678 Mon Sep 17 00:00:00 2001 From: cloudymax Date: Sun, 24 Mar 2024 10:19:32 +0000 Subject: [PATCH 01/40] fix extra whitespace in confgmap, fix indentation for voluems in postgres --- charts/netmaker/templates/confgmap.yaml | 2 +- charts/netmaker/values.yaml | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/charts/netmaker/templates/confgmap.yaml b/charts/netmaker/templates/confgmap.yaml index 53d0d8d..ef94db1 100644 --- a/charts/netmaker/templates/confgmap.yaml +++ b/charts/netmaker/templates/confgmap.yaml @@ -21,7 +21,7 @@ data: CLIENT_MODE: "on" CORS_ALLOWED_ORIGIN: '*' DATABASE: postgres - SQL_HOST: "{{ include "netmaker.database.host" . }}" + SQL_HOST: "{{ include "netmaker.database.host" . | trim }}" SQL_PORT: "{{ include "netmaker.database.port" . }}" SQL_USER: "{{ include "netmaker.database.username" . }}" SQL_DB: "{{ include "netmaker.database.database" . }}" diff --git a/charts/netmaker/values.yaml b/charts/netmaker/values.yaml index c8ad0f1..3eac2d5 100644 --- a/charts/netmaker/values.yaml +++ b/charts/netmaker/values.yaml @@ -154,8 +154,10 @@ postgresql: secretKeys: userPasswordKey: "" adminPasswordKey: "" - persistence: - existingClaim: '' + primary: + persistence: + enabled: true + # existingClaim: '' # if postgresql.enabled is false, these values are used instead externalDatabase: From 568cb14987a805f7fef2f114f6510363b620962f Mon Sep 17 00:00:00 2001 From: cloudymax Date: Sun, 24 Mar 2024 10:20:23 +0000 Subject: [PATCH 02/40] bump chart and update docs --- charts/netmaker/Chart.yaml | 2 +- charts/netmaker/README.md | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/charts/netmaker/Chart.yaml b/charts/netmaker/Chart.yaml index d20c3e5..f6363c8 100644 --- a/charts/netmaker/Chart.yaml +++ b/charts/netmaker/Chart.yaml @@ -15,7 +15,7 @@ type: application # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: 0.9.0 +version: 0.9.1 # This is the version number of the application being deployed. This version number should be # incremented each time you make changes to the application. Versions are not expected to diff --git a/charts/netmaker/README.md b/charts/netmaker/README.md index 062311c..5e11412 100644 --- a/charts/netmaker/README.md +++ b/charts/netmaker/README.md @@ -1,6 +1,6 @@ # netmaker -![Version: 0.9.0](https://img.shields.io/badge/Version-0.9.0-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: v0.20.3](https://img.shields.io/badge/AppVersion-v0.20.3-informational?style=flat-square) +![Version: 0.9.1](https://img.shields.io/badge/Version-0.9.1-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: v0.20.3](https://img.shields.io/badge/AppVersion-v0.20.3-informational?style=flat-square) A Helm chart to run HA Netmaker on Kubernetes @@ -70,11 +70,11 @@ A Helm chart to run HA Netmaker on Kubernetes | postgresql.auth.database | string | `"netmaker"` | | | postgresql.auth.existingSecret | string | `""` | | | postgresql.auth.password | string | `""` | | +| postgresql.auth.primary.persistence.enabled | bool | `true` | | | postgresql.auth.secretKeys.adminPasswordKey | string | `""` | | | postgresql.auth.secretKeys.userPasswordKey | string | `""` | | | postgresql.auth.username | string | `"netmaker"` | | | postgresql.enabled | bool | `true` | | -| postgresql.persistence.existingClaim | string | `""` | | | replicas | int | `1` | number of netmaker server replicas to create | | service.mqPort | int | `443` | port for MQTT service | | service.restPort | int | `8081` | port for API service | @@ -92,4 +92,4 @@ A Helm chart to run HA Netmaker on Kubernetes | wireguard.service.serviceType | string | `"NodePort"` | | ---------------------------------------------- -Autogenerated from chart metadata using [helm-docs v1.11.0](https://github.com/norwoodj/helm-docs/releases/v1.11.0) +Autogenerated from chart metadata using [helm-docs v1.13.1](https://github.com/norwoodj/helm-docs/releases/v1.13.1) From bbca1d4d047732a9db0c81e9188706d79e780a93 Mon Sep 17 00:00:00 2001 From: jessebot Date: Sun, 24 Mar 2024 11:32:18 +0100 Subject: [PATCH 03/40] update postgres version --- charts/netmaker/Chart.lock | 6 +++--- charts/netmaker/Chart.yaml | 2 +- charts/netmaker/README.md | 2 +- charts/netmaker/charts/postgresql-12.6.0.tgz | Bin 56646 -> 0 bytes charts/netmaker/charts/postgresql-15.1.2.tgz | Bin 0 -> 73720 bytes 5 files changed, 5 insertions(+), 5 deletions(-) delete mode 100644 charts/netmaker/charts/postgresql-12.6.0.tgz create mode 100644 charts/netmaker/charts/postgresql-15.1.2.tgz diff --git a/charts/netmaker/Chart.lock b/charts/netmaker/Chart.lock index 726d482..eb38a21 100644 --- a/charts/netmaker/Chart.lock +++ b/charts/netmaker/Chart.lock @@ -1,6 +1,6 @@ dependencies: - name: postgresql repository: oci://registry-1.docker.io/bitnamicharts - version: 12.8.0 -digest: sha256:d30a9afa794d29fe3b3f564c1fafd89030705ac4d5a77c99843235449525a152 -generated: "2023-08-07T23:04:08.264324788Z" + version: 15.1.2 +digest: sha256:0766b5084b914ac9e7edb772078003b4727ed87c986b9265172777a14ff0b5be +generated: "2024-03-24T11:32:00.096443+01:00" diff --git a/charts/netmaker/Chart.yaml b/charts/netmaker/Chart.yaml index f6363c8..0094fbe 100644 --- a/charts/netmaker/Chart.yaml +++ b/charts/netmaker/Chart.yaml @@ -25,6 +25,6 @@ appVersion: "v0.20.3" dependencies: - name: postgresql - version: 12.9.0 + version: 15.1.2 repository: oci://registry-1.docker.io/bitnamicharts condition: postgresql.enabled diff --git a/charts/netmaker/README.md b/charts/netmaker/README.md index 5e11412..2244834 100644 --- a/charts/netmaker/README.md +++ b/charts/netmaker/README.md @@ -8,7 +8,7 @@ A Helm chart to run HA Netmaker on Kubernetes | Repository | Name | Version | |------------|------|---------| -| oci://registry-1.docker.io/bitnamicharts | postgresql | 12.9.0 | +| oci://registry-1.docker.io/bitnamicharts | postgresql | 15.1.2 | ## Values diff --git a/charts/netmaker/charts/postgresql-12.6.0.tgz b/charts/netmaker/charts/postgresql-12.6.0.tgz deleted file mode 100644 index c782571a80414080945cfc0a8ab3510955b58910..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 56646 zcmV)RK(oIeiwG0|00000|0w_~VMtOiV@ORlOnEsqVl!4SWK%V1T2nbTPgYhoO;>Dc zVQyr3R8em|NM&qo0POwycH=hED30cDKLw6Dv)i6+O7f-M8LcnhIg0FbyyA{oQ5UC?XtkOd{6X0ln%1AjCe37-Bup zkD)(9-eJ4D17rLPq70L$2ObW0U>v7^9=vHEwYxh3icu7x$j6BFc03TlIqCtQ%;zN9 z0f3^IFw7+~;^SVYL(vp7P8Z%mJ0Si&qHRn%W6UEs$G#9wwgUi%Q%R`@^cydQ_+SV) zY=3Nj+zAl#DUP{(G*ogv`ac(7SO3oT0A>J2fW#;Qj3m@YfQlp5|eKOy{ zzUXMln+{|Qan@lR`uD73O?f<@T7P=-1Ml##`|rlgM;8W`0n`N z_^^BYm*esAU;g5c-*(>~b>H~kjsG(F?(O9G4T1;oyYb}c;O(J5KH8b02vNvU&;zZ* z?%|Qwed8S-+#dX;cl@^Z=A`|X?$OcVU%op&{=cn1`~=?c$D02SFia480t2vi{vW(O zesge?pZ_Os4_@Z~bNsvl0}?MNp3bn**d%K((dC!`cAWB|3N zZS`9op6-jVdglv6%+F|{USGjEa>%t?g1`NvjACI~!YLYrkfqd)>}SHt9SSwhIEpy< zg@nl*?Xo?C$OfR8!B~l3dgmE%gh&Ta0MUbN+O4GC{s<^U`4H3pG_ATAF6#JIG zszGPIX^IYg#Ej!;s^m@~B@o|Xu;Z2 zbPV5rMvIiXd$a&J+6Iv|*govF!F3ca1k3OU z>Rmaf(ogCJnj!jtebknY6YGUNVEP*%zHvsZs&T5(c2rm8R~efrw>oVtX@XwI+!@GC zl56g$FI0E#lQGghF>q}xou892q7mYVnN5;tWqm5~WI)8Yrj19O3i;(kAQd7SE;1WR z?b+JWwrEe1Fq8`$NAoz83SL@V#t9=A9*U%GCUNWuGHhfyIt;)PTEwfybOIG151SIFvo@v<*i4Q{yBKpWFJVkLx7N9TeEwwUn3bntFiGBp?gGbC~ z4tsWyuq?%PQ<`jPS0u|U(9+bhx*_>+0mmq0pdSPvRKIcpU>Hga3L^>tMcRWUJ7qQO zf!{tAVD>YgtpsfOaGOH6Ko}Hf5gdmI1Q<>u!Z`N9oCFAHSz#PIgEx z2Krx4GT{}(_{8`r%Q~;oX&_2dcz}fn#dSy_oiflD|5*#2Xl`)fXo4fm7p^w4m#`|u z^n-HK*x#y6Po|0hMU%`0J4?pK%j1)Ihn9FuI~-0~$C6PAv7K&t3b@h@R1Csy^a`5oaOwig`3g%s>+-J6gpU_4*V|U=k|5{kk7M!UcORI*^S%X5h8Z(u}{}2PkB+7xMG-$ra&4 ziWrLcYX!~``ulXnX=Bm}h|fC0yz!<94p2v8V4YVnMV??Tj;5Y^X69-(h&^EAQmC$D zb*>aH05g5mUI4Kw<@fK}Az%h~o-i&(WDcW5!cg0{zzC;Ybz|tGcC(Ig$nIGub*p$W z3A|Jxbwp28=e(HW2gs3UhQ6cVpM8Y$I5Z1DypG2rE^}6l)L3zvATg{9l7L4kjUm35 z+g650-@ah%lCwwMsdu5E~990gh%1)0`JzaZ(^ok)a zY-*3skVgsOp0UL}5_uC$8TV*XRJ9vmlrkVtLeCia&x%XVjvT@rvvEB2PjBqLHYJBM^a;dl>wXE8s>YrnonxYp4Ikf zB`;MvuidR|4y3i;eDCm!Otf8}zIP;}q+L%wyi32T!c#3Gd1UMHumn|k-?!z3Ygai| z_w(M!eQBhBsN%?nbXH>BZ}EAIZXBM{`B?NY zE%7H+)3$9t;TB1IBA?zG6z&xLVMSkpze zh&%pE>8_Z_mpEb^!9cJsVS-^W0w{XGltgnBaqs{s7PH1$I2`Df6=jH^{tR>E^MoRs z+0OIb4~)YofFX&{2S6zJV0G?;^q|<^_dxc!Ky_njPh+I2xT06DTt#XDk)vnh6<92^ zu2o{aXtPD+&nSsV!UkkM$6VT>)>FB)#$0+X+MpkXRvD~Q%=~I_B;jlofbmpvPjo~;a$Aa4 zN>9qr1of%daH5F0qM^ z6lLCNX_J;P=>kV+NYMm;1h>ZbFT`5H!VvgsXQlkJkDF9GnV;wa#?p9Hf+8M@e}(*%HHaUJd*Q0Kd2T4O?jYxMMP8_ueaj@Bdq19S@mNqm`Vq zz5yeYI)XXTTYGSOF#;&^=_1zs*b(h|WygR@x1|wk)NI&edofUYNdKW`jQK+fzx7A_ zXxg~L>pWG%V;)Ji>OXj>w~#gGm1ZSS%K81xg_^RF~B5M zy?=BR{U6%ke3Ckn_R~{g!e@vo$eI5;?-bGY{>>`(zK|_Xk}k~rbRS7^?sz|gaSAy+ z#m-J$=FtS0f(Z!&Y2fS>zVCsuV@T1m6w651O~H_&kig&qk13=WF`ym`F%Zu|n7-JR zN4p+F0uV>y6_cjX&N7%i(2A$h29o_9aY&uh#FzxTY)_hSB;g>$b8L?7#TH{W2Xizh zbRie-9~Qt^XzGwnZWvP=J^;JYaYY!z<8YyeHQPt%-51+aXuyX~fF3$*HkT9n!^!dG zhq_|{h0;@8S)_dKV8#-}I>!emb*(ch?O%1V15&;3z2`BMxzToU<`~GfTDjTNO76Z&>TRUe=GQ^{?a~C z>((6c83~xPbh*7L?=TJ1-ilw$I7kvR&ejBpvt=YA!( zA^=DKM81x#8_4&UFkZvm$i!0W=e&;Yc-q(t!)baFPk$Q2!LsgXzmscE=hX1peUK6v zPrca~%6??MkgLNh9c)n`v!4%-JO-?h8#Zs+hA%!4)kYFesl_*&r0zeRX|%)&n0( z#ZtT6s|UJpHkd#0O9`&&8kfOkt^l3sr%O8lc9jD}F*>QgPCXsoNPLsJ1;)=*Kd zLaS_$SyBR>vXbEa9Ndi5UtuV$_AH=Jhj)Z#Jv8niiwaI*#GqOQLo&6|=K(J#MOA@d z;1o-MLN>_6bX31I5Db-p^R0oPRxFxC0CFIsoxqq7ZW?RTT^aAHN%}Vc^TnOOd8gjA z*{lbC&)$lE|6iRkjyi1SJVpMDfYvDij0htIN7Hu8$zRZjz;B-#u_2o{xhd4JlWOYR z7IQd1l;itQLTrk;@!)AQTmRF9Ua%_+4zYG&%lu|2VwmFxWQPszmGsSr5qKo@9>9=9 z)5^}tBkd)OF5^Hh4W_Q~+g9MU!}OZr zErx&@O^blGeN67gYnIxn*{Q@BnYj2U7^g49;;6hHI_*P&&O}qLX~3t1G4qXcs4E>P zY7dh8YKEsX03RR@b&(eh`>!NXpd+M~=rnS+#QsSGS7F30XM*%COc0du5mJ(styV^% ziDW8WFf}{UZN0T8)~Kp^D;>5DH|6^3@KYWy5nhX1|NWCN7jI_>$X~L^1QlkhacbA4 zgQE#>d1`elWst58T&5~ZcolwE0IW`=v%|t&XY5gU7Crm|DbsmHay-@ISr%QR@^b5eDAz7m^T}?af=T2Vj5_+) zCXdP!*vsb-RSk=@&1w+J_l>bByUn1=6tK>Ni2GY73z=o2# z8qkJPS^%o7?etlt$-TLLJ*be;gEuP)sj`(WoRxo|9aPRkd5o7;mrx1z$|56Cc1l(R zr#&-OsBc^yu5sry`zpv?fJ?BC?0KkhJ9Bdt%r>w}ydj<-f8mGdCqnL7ne(rp0OY1g zoh_rBzkG})gd(w1g%h!5)wE}8y^SoRsRWxTl3>mjl}#%T<{R3wR+6w!=5ZpkWp(9+ zPQ}uoU$+$=xywx-YUGVrRJFDn_@+~B9dMu!$!91qQ3zW_vFoRhP=mNV|?u-V$XA`24l!X!5w44tI1v8HMV@kqt#I6iyu|tov1oHvU~@x;2MDXD-g@i-2CozH1tPlVremVkdX;j z$4N6tgAdL0YBs0+8440vj08QXv&#;Ybrs}Xj!1wyuW-Z>m46$0JQxKYj5to8*lcq8 zUmY+b7|L-&ZrQm0fGjI8q6Y+(l~$*~k{6+;=!f`kfnf14M{UK)VDZZ~U&N?vl*@Vq z#K&74=Ja4uz^ysZEFt?IEh?#&I!p#XrU@F2NH*BdRQ+E0`@O7i=#CpYL>~ON7r{Al zj_~hGt0C5OLKkSN?}z@#bfqEt%le{6A`46Kfa=OY@zJ5(F*Af!=Nm>!O!w+mC!l0{ zo)%JSJVBftV{vLMnbcP)^hv}84j~v8-p(t9(9^xcSrmiYo1SGD-Pj#d zo(KC$rG}Ov0V*--Qspj{R4r6-10hxcKQIWezA)>-ZrH@(XQyiAG1tees4v$cua>f0 z`j~~Ok{pHuy;H5n3w{*3&#A6xP!xiI#Ok5TG-mlV@{~mu?6ltJ zUJb2Em8ytl_CZ<*+XSMf=)@Zrk94)#1xtG1;PB`;)oX_ED&`5>W{@!`$3pRTb^htf zl!k^^uYilCz{P-gX^3rsh2w-R#^hu6Jd53V9gx@i;C0&e^*;>G9Ku>j9+F@bBfnab z%?GQAt!`C&@?i?77o*j8a^=xW#9r4}mwMSM)jiTMwF=H{*mND9^WhSxP0PN!@!?hh z_d?Aa6X+`}WOW;#3}TH5Lv9eTKO=@b*vgr7z{R0eP@HwnY&!pk;=SXS!fB#_ei@u|OXc$Xk_B ztgBd?GZ~dGr%zbVhsrNRAB?IoU29-m%VT}dsym|$ydYevl4;*yc>VMtN;c=CWIc?QS zxkTE;;wBXMnOq)o%G|_*H`4+cv<9ddVhocg^;wP7->wtOS_*Z$eVI}+MxZ4$UQ6iA z)RmGUzurXZi1__ z6Ri)bY(?vn{F!|TMp+5QMyq1mhrbM*nOitpUxL?U@`e!4OA||nzYMhd$<+{6yPDO| z#x)lk*dgRI83z+XUARw|FER61euV-6vkW`waIOsN&tDAT$XJTAv`9_^qFFLdNoL7d zX&0i>AT8=SQK$eX2B;EV#AD|gLosyv z4ntsFVpt(tswbA_4bTKfDA;$dHdL;-|FR$bx79}9Dbey;WzAZxeegKL{>_cR!!)i`U?Pf$JDWBM$w0_ZF?VX>sgI_7;=sNP7C<9rk#}=9!wYV-vFIx7%ckghMe%5SK zq>18cv}QXJ8-YIQxyTNDO29hdB1;)#PLk3irL1C|tt#`^g9@R=Odb2lLhMx+XQivP zJ}Sy2O=8h8A@&*#5YqN9Mw5(%d;wwwj1iOmim+C-m1Z>#Z#}ax~X%=~o zBerNJFVU{6woCHW#`A@$^|UV5#kQ+4=Sx;wb(%}AH-JG_8QLlKqJqq2xfd3~2UJ$& zMKrI*W>)G;L z)k>1zj=kwzl^bnYXU@t65~4 zD`=^+t0q4+ue2Ix;nsGkIuAFGZ;fQ!Wti*IaSP}xvrHXU{^w@p7RReDH8;n!m*m_M z*ZUWop6eb2F3-`e@7Qx^=`N!_m#Uj1{>w_%UDlUk#%^hMzTBK$cXeBqxSKPeUQ%~U zQg;g?(wbWl{ z7Bx-&b@k6C8Nj9J|08mM9Ug9)2wcv_FFzHyDBhdo1J}xOT}H5_0@uh1c4xy@6N5`Z ze?{5BrSyOODZ-`Q`U3KVOX*tKCA)IEa4FiCoZ%%5`j1TAuh{iJHA*hd5O1DdT#f#T za*Qhxe({OMm6U(M*~Zm0uat6}lem|p~#jP-GD|JL0UsyEVC=trfZneCC^{m zW|%bk$_#{@Bc{MfYdT0WdSIT?>^PJP4XwFhW3c$p6t>+YH8am6VS+GllZ42+3ST~y z&+=xZJ1q8><^p21K3qrP;zvs6d4bg}08K)j05FI5HPzXrF`!lvEZR?~FuTQs<=9U` zNCBaEilg?Xbk<1!RfWha`Y}sg3!G?DtEy>n1+^HVS-a_`fwq4c!M>6=Yf z(Z5t9S+?b|u_lw@BUx2~r%+}!^ggLZ%XRG;m0C@Yx3Af@(9M;VTTKVo(s21c*h;P% z*NdjhVIF)fnyzw7V#!#5qQ!tjk=hNhTXLq)VvKe}dF2+MNbE02!ly*~D%7Ekc))Og zR4S4>0@(-XqmN>>H9x%?fn9k-`{rn`&cwILTQ287Y8OP+ioM z7&8zN4tz3?A(!e{*7}i~WX%gx4oXd#N}lC*0!245%8OV2;5IK>TCg~A_R%#$RWDM@ z1nx8=XAiyxBvRp%Lh%U-i|8GJEW9qR`F8Kr#PDb8 z$Z?epG}!(ZX_0Q_Hbqm+I9=p8t-tqTYjk7-U>Y=p9utP8Q@J^O)1(mw4yTPli*I-F ze4cR0eGaEg9_3Qxllfd$_u6ZOG{95Dcw@Z!bA6=w2p53O;NhE-9)#_7yA6gRf(!}u zA>=?`neI09xz8A`d^RDP^eBnIGpyt!GRZ`F@}+MaNyAcUBN*OM(VMStq8pH z$ra%;y&y`DX-Y!CU<9Bn=0+x26rb*XYL^*y%oE1t0T7H52}Q*_1V$PhcU7a4-ulF2 z(H|X(7*qTJWkSDVWyfGR%c2^U5tPyItQYgejc)ogTBr}Mpz92`o2KSMP6VE_N7N?;0uW?y}k}kKM{z@49x%HJ>30h6U3^Q>+g* zf51_IqiMFp59B=88cr3Y(Gdk>^(ojr`xqmN=P2SZ+}p{gp6Ji@*J3VM(Hb%Ys3S

!Z%B9Z3+dEsVx@U;h7;Ren>XB`3%pYwl1K;P zIQ`Yjl&+PbLZ0+_wB)BSOk_@(=@@2t5CM@~ijeYWBn*L{zhj_3JWtc}YC4dHeN=aU zrC6Hcm{w){iw?`)GeAkGPNj1#t)s;;^o_9UDSsmx3Aqrz{9q=#P~DiCDhs2bV2Rj) zs&d0)==W+xR^p-d4)*4U{y^;k1*kMPIx7oH%?3~}=P<@{ACpfiFhZsl7;E{lzDZM! zFnr;E}#cP!9)kN{65pK#bmHBpQnHBxa8km^HW?X)qA*sW~ z(x#rM2!7pII}E_D{hO=vt9QNPJGnTbm?GbpZdPF6!$JG-07ze;wAhmfK0qAm4W4}c zCVediJ7#flteqw2QlGfy79H!@FzaXjBv)3p0?;}Wl_8asXDeaNHqk)KZ4R9>{wdru z4YzJ*mU!0Vt_am^T+)&; z+up62KsC=2OTA<3whxZl&aUlc&-P4vwwyeuMbzk_QyHOVXM^cQR)1cyi?gb?KT?t) z7NIa5`a3i9uLQ_w>P6_$)3Ce;3_a*6Udcl3JC;-P(oul`#M3+DRkhZme%p!~eFL?SZq4(HZE1v#V1;q0Q7e;Xj?QylBM z_x7y^hUS)wBkO1-rg#1K@6MKB8sUEzFbPgb$Y~jSS3dIDw&^Nqm*hWNwGRKVVoe1M zp_N77!t14Yt5Q!(Yy6vx$>F=Zv;P@h-`t+v+?}3}`afKp-JPEO*nfXNs<_bguyVxUUHX&+p^YQvQ5JB1oz5(oxRl0<<= z$rz`Zw7=+zuBIG>;ZswNSaZzqq+|E7NtuRxU+tsOG_Ji+l_!%YeVK_DVCD`DS9AVK*7i{)VT2}Oi; zGSTsfW@2X?l5uAaWf7fI;wLiBMwK#-aeE%TDwAA8Wb{#AGTsNP8}A-|G| z&{2TOtGYa4w;Upy_f6-xTEP|OjsqE~tfE3>z6v9G$;t}bD5Ze{FkV>FZj)D;mR`kk zlYV1O9vm?aqhcQMGFGQ|3(p|srS}RK2jP|VEU!yIgM9RgZc!vjmbGzi)bGm8sZ?+r zhqB%}lLrIL+$3f6ikpX%LMq8ut3aGwA>e}>3;rK*Og4+UMzN-gXIM)p++Xmzx-ud2MrW@0V}C`Ph+3uqhV zPbg>)#KfpS;jLCzuhdq_Sjv)UsY}u*g4pR?)w1h;m0xyYo<%tWX6=PFhqi8E%~9}_ z3u}(5m9%t@%<a99=3lEr-yR014$1-Xp-I3rEHt>ze24K7AA4CL<>kN`)D2RqQmj zV5;Q{Nl!3D!jSwanWCHQmj4SlwNshU$C&m&JiVjYCwC#7{-)u7FYM5m;)qW`>rc$Y zDzv~Zj{GnQ5NMT#b+$lzPgtg(KnEP1bi3fc!GVo;B9@1Px&0_COSEF>0a0n*`xJBR z!_aHz~Eh{RWm&4?cZz0-hKM;dCnO*6~6B5PbPosgO^fTH@;&NvNHm zsymR(=a4RXZX#($&+35n>7a=#{N`)?$ppEhZvK#U^yjesPe!6G;pld|-IL>E`ER$| z&Ha1OeS38D&B5{U@!P|bwoF%RVnYlb>7(k~~3&Rgm%CVgfp8UdqtL6NL67Lo|5Yl^J7|3NKt zpY}D~37?hGYxUQqx1aDCin!4vjy+yabP3z!b7dN6KFELs=e`42?^fJ_!1I z%gpn?w}8CGLafHrZIoxAyxv3>b71U|(6laS1+cbCQ+dP;?E{MDgd2@%V{0{oLWW|1QbRAnS+C_Cw6hrmsh`p z@eD@E98v58MG(Mg-o|SEPUG9w>Tfombkt-DuC9l6d|j@c-T|1X)F7cIx8y*^AelCZk}rZi`WncEEPJy>w7Zx-8nP-(8)WFM-I46*I_yorXsWRfcPaXzBde>b%IY zK=uC%niZA~f4?P3u}#9p;vyMjs;10gq!R}Wpmt@ioaZwf1SraN0DzWU!c-s366OGu z!!DOBcgeLr`Lv9E+ta7(^76?f=QHql$Ntxt6;{>9Ce}rjX~6Km*A((Xfy|Kr)rf5r zMS-+IS}eptek9eiODy<$Q1%9XY{`Jil6UVT%mv(cxV40T=~?GWw%LtIdb>%RSDB1i zKxg4#dOa&HZCx>3Iil-e$#?2n_QLae0jL|-!(sRM zyNVBtNL=EU^TDUwg!JR&GxB0Ee7_||bgMovjF{*6u!7RDRB)4NQzqTkRLa!(DdG@^ zY`MD9PR@DuOJ8CNwF@>`Xx16IDxTy=RMw2h zF2t%e3<~9x+!{otNPJd4Nmvj!`O-eP)Jb>Lgu!(y#^{L(@Ry-5%|T|9@)c zkF@-M=9GqYuC8y-Ms5C)Z^y^9#Q%46__kZ{{~f-0@&7%?&)}#2%`LdpI`A%mG7XE@Y# z_Cy~pguRCUc@Zs{bm7Wj4#{0<@<1%i9WqR#+8I%Qi$D*)Ew+F2_|ia#D}52$D<=J@~}#6 zrWy$SE1fd7f#w?7l2tWiS|;hnq^39OI+UVFYD4+^x5|Sjb3A+BmlBk^SS1mruR`za zMudZ1G>`eh2DG;ab|d6~PT{roG8QF#)-Il7NYZYJ)CzLO+PgP!KN*RHasGYlH+f@6 z`?uf9n#!XC;|=>6En1-c>C>kc_#KQPLnp_;3-)Sy|J!d4S4GQeHhhr`5W=*y|BhCv zV}{9lu*Bjk;t!Cubm_u!%3Hx2^-{A3!0y^pa1T^5U2BG0Vw8qheWZ#_4|ZshSIq80 zV|XdPYZ$(vugnCVN9VxvI9~t{i@%Uu;J$x-g&q)n-EbvVWlE~XM!qz|;-To_`_}MX zCJF`VXo0`$&6DRDd&~1c%>ESu&zliexu%t`H^6%jnn*720-#lG8SwP*M**!M!Trjb zR%v`vHtN8Oo3GAlsQ+8vc6>gOHkDORJ~L5N1i3J2_T_B>I*Murxol%Bme!hfQcD=_ zshtEL2v$ZpqEs#y@?K)KB+4@wS@8P&6j(9)W}F&SLHP9P69@=WWwhnRQmy*QA}y7Y zdf}F(Rx*=VFEuGNQ4F-M^v%ny0^!b8+GcFDw!6AMJ-a&}=5(C8o1xisa#a`RXwLrD z{YRT|$P?Cv=GIy9O#ktzwO1fvcztu5BVrx&EJnUcsu1|hWz?TqX%^3FlTp!W&Hq-J z0B-K)94-JCa~XmOdIWPECEUtUb1tAh zAL_I5wBlSlbxZv!?`V?&(Ndb!ZW+9Gmg9$}JFv&q#{RW4Pex}qznl+Brb=V{E3N{b z@uo>bM*N>jFXpb;NPh%oe%Y58n>#Z=cTWzw9(z5^Vq?)-L!h&s8<}ptNBi1Deqgfg z8Y)pE2%^j0gwRJwgJ#me9{kQ~6499Jz2@Wdzg8B9gTuG&ZoAt)_+rO)MLWe33R^zX zLYlX;EZG$W*%rfuq~U2q#0wIE2tBG4o-ouA!4dGqDlf0bjVXG-B#{ZPI`W(9F#1Qx zK!8Fd@6Mi4l1!D>o1t)S*8Da&k4!W?L;HY)fxPHZ(jmQ%Fp>`qh5M=qkh-WEV4gyj zP#;P3nkyxZCKKfIHaHa&$QuLmcQY`sJN$+1DO`+%56UjVj3ub8O_36IKfn+NkRvsd z1j9Y!AIKT~kB~;P@-!u3h@{M)-64&HpGrrRP6)GZYFbYV$bU z0*?CgpQEG0Gw-n7-T8AzY2nVF!2_Q6WP$Dp4v|=Azn=@{`ZLr6*=IXx z!jxHi#6r0M|4Lw}t{Q`Jtn$>3VF)lX zM|PED1rwt%P&IdDu>_>UnlS^rV|8V!c0>zRD)u9LZPiJky;R2AlI7mZyr_*m#>!#r z*$F;>YM9%Tw{&DTe^;y#OcB50`6!u8@JG<{GWz5i>{-Z{`*J*Mna%&c;pk>}x@q39 zFKnR89ruO{8w{LtHFWdQD`UG|Eau1mb9z;{nm|vXe07YaEY=|xIgT}(8SKGt8-%3{ zn}rpQ(GpX7ua-HR@TNonpM_1fT&>TliesfU*mZ+=LL+mjb&P1S+_(T<+b8XXRUIhk z75SE#kMxQM!`lw}uox9Y4 z8JW|fIWtler6yeq?D9Bl4CC89mq8YK_d~AGA8CeZ_dgkcq#!kFbtcIjJ1vM(owoci)9u^A?-(=I$Og<=!$%<8-xpb+>ua(5BiA7;7XPQW+LlY;bd~jA-L~|=+-6U1tsEOpv(5$=LoU4K- zOQKHQ*&%z`m?xG4!Kr3jZw1>&m5uCRx2C97V5-r^swhk-AM7>WEttu9mLB@35^G9)25LqE8zat*Az%r>xkR^MV6b)G9BW)ePEQQHDUFmCInx&G&A&wZSt zS!uzn0@84~6wN)|czcUvQRr?jMyl$)%Ig+-?BcXPwDTtG_!DDLLX6H z1&fDlfYOL=#=?8FSn{oM_?bv*ogLuK?BMR$&Vb5Y0OjtIf3uP?H|JeFD+FhskR7ex ztSItL*pbDPJLv@`eJvNE($21!S4{jH@Fs4Gy&Yvck2~#D-J<~f1N{EGZc_5O`{}aa zJ2H=PhyV;jU;?WFj$}bShNEeSz#K6Kr$|MY`{Kh1#9{r)k`Dw=C$k2|#8j5C52bHl zEQ|VQ&r0X9i$tn1XZgKEd`1Fi0oWW%%;IW@!WSb2Uv$rO!DFimsq{pF z^9hhkLqbEK3X;l9N?Bn61Tvi{OD+o#T_*HC=ByC1j6x`y@ zR|yUGYL--Cry{zimeq{FUfTVAV0|ao#=nvT__Bn(j9(rTJ_8@j=N}}UAFMVX0OZ-l zTO7}I&YLc96I1el0|X#QBK)refk(K|_X_9=Ps03AO)k})B@-e*nrkf{A0KTmC81M& zHJQ46paT;=^CUepJ`XpSi{WT!8%Q@l^aqx8ZXR<&RuKR>wW{{QEH*nEXX8u9(AO^t zqnEYW2iA9zixm{(Lf6t{V!4?e$=t<9Lhow^qwb2hn1tA0bYc=@7sBgjuf!k`ivNwH zsd|PHYaYE6L$RUOcTOOxV(RO)bh>hunEhfMh6yL{P=u)5&6k+uRV$Xj&pHBxZ-XzG z_S96`YzaE>L{+pAgsWR;SoVd?sFHSh1X8owCRX75(X{-%A&GxtO%9&xxO|Tm%{iSv ze%>;(?``mo!=jwgKWp08WA@Kz@x1&V^j8t={!@y!50^-Gb9Us}I_RfdU7u4o7Y(^| zb5Y46pN0iW*6daZq!k=MLJ)n5WHu2D*%r>3Z$nmb)Lbo8y#sS*ZM3x;cF?iev2EM7 zZQHi(j@_~CbnGW)$F_~<%e(ifQ?>uXTC-~1_Z;K87RgMb$qyL-DSVB&&R0nze zZ)1rS{r=Yxs)EPY{LQP~4UX3jz&;1Vqd@+l#8lIzX`pxKKv?Fna-_W3r`+l2eRpGX zbSWG4rgUm;#E}cjHR6p5H}ElT#hPZHNGr`0T&~{qiy+U{ieMiC=ArHRco5@WH%q7gWv+7=d4zHDs`mxTXhk6>L z-S}hww7OPA7W7|i+`EiXW2_BarBM`2O%knA~G87(i;pF9qyb=84uj$G+7M^9b z-(*o6XL>qInPme{)amf_p%7KAZ`vDK)i)?{@Y0j8JRo#*2-4A57DO#~0Ok3M^If&? zEC2ksU7|IKrf{7YT}%?g2tWDXv43r}KCIt7kRB}o`8khAi98y8X}AMsLx=hzxP|f` zcM!#aB^q3S8+pdBp9&*(e=v`;q4;i&#$X%bsdk4bj1ap8@Yz2f+!+@sIU9D%gaszH zwJxDYV0)Su`=j@CsfnkNmNL%Vx$n(ZnM%+5_}#Bd{^DfYfv491pQy5-jAab9xdXivZpMQ`=Y zhE8tWf$EUZ>#(EHMxSJn6ynE~-OQ!4?{t^aT2L}4+fvNwrY)QtV(Ur}itn_~tr@Ks zgTS)F zp^3RH$MQm3^*1JYJ1=>AY*T}%8ym(hJ9x0p-9x1c=r-30KMgE>1HLc@e0ey29F>0E zH+}FHesPc?PLCUWb=+keoMq)?(|EgtT*t%%0du!Jf*KyI#SCnzc#`s8u*bV76;0t! zRjS*#K-PKS#oz4%2urJv(cO)YSAf#O*1(Fm;U4;5`4Z>$M4-ShSnRYgjkYcatYF( zNXxOAFFW*QKG(Eha$j=LOUH(GXXhItBFoQUUOE&OV$QDsvJ0I*b`!gI4AD-h1&^8VKQzk7rjf7?^zc=cpaZaH}fw&)X?obEV8uCrNyVESfw!OwBB|3stL4^gNQMD zTK}n=zM+uY)$3wyB-X^cPtSXux1T%?DzEjXP`xEQa~bYKib59cJv}~r z;qZJn40++xyztW{yWgvPuA=_meP_OBhjjSv4HWl|tmkMnSb%Izl*vR@te;$!_$h6> zsp3jVsCZ^jL{4i_VlKTqktA=4rJrDid92ts#^=I)I0y*}MzQv{cuAoaak-;%(3OEr zd)O7#&- z8eF+eVGVn>_9X(?M7@u>{gc@Lw9UJAuQ1xMxe^sH|Cu6vDurZGzqgfwu3ICexYsrQ z;!Mzsfc+ z*cM66|E@r2!m4~!m$425)+=v#Hi9-a@!3_Q0L2)6I+wJRbX9`EbSgTb-M_{&2Xs4H zC)dY!$owdk{*ZaE5_{%Kv;GJb5c%ShIl`w%(;a>?1oDUU(1`f;w_WP+rL7AHhKDBt z1gnaom2DKFKfVbnMIxU9vhN$thrR2>_5TzxP}8Tf$LGnpp2L1+Sj^RJJVt||Y#a-z zM6GR=ST`at@YS+%Kntsc9au8`m5Am!?z zpCBSjb7kVOK3$L-s--6fl^NfUX*X`Pa{NaA_fX;+A+!rh1#Hy9aa}L9C@ttmH~s*? zI=JGHVH8ckhDRqUn?b4X*7rnalRacvU*3MvHQ&^WZ&xW)d9SKoK<*gt#C*E)>|O7y zZB-wVin8{BXd$+VbjO)xH;tZLINEQ&LO{=@Vx#iG~^W zWr8L$(RdS9siC;5!xL}<2a&Tf{DOm&iY(vw=cOCHVR!OUq>f|tUpF1>;lFM=@-XRt zlui)ex?6tQ{0freenYa|A-)Va+t({DeHi+lZvjNolvBf&Br=X=DTkV-xh3i@icRkf zETviKWYb`9nVy^c_rwC#8Ni&D+uu zCvNk!+Q5pfR!}caVei0VU2=YQb0*mQfe`yo;4}5f?WcQQIf0`S^I(MRVBZJXPjf>v zkNM;5rvV7s*&S$vWu^U>Ith#;#0%Uvn;)M3Om5fanpMZXRJF6L@FHG4i@De7l=JH( z)@jkG>7NSXYX?OiPXPGNsGvS#<(1x>%OQHvnP2a%tvs0H{G~dK<5#O{RZsGyOap<* z>r6R}$ZoNP!Fq}a{q;764=`^jL|G79NN#tD>F=hlP8*p(L@JiGd-eBXcsQf5>K*yoNPfmZ%?jg7ou~;7{_L=IE0T zY_HgUC_E5`*Vz}o93{4Z18@pP>{&ZHxKQRFJEG|oP1 zl#dN?Z&C3es{+BdbWcKl1NuR4j&6!y^*qG%AG@qQc;_ZkR?wpH_e;#?&bw`!phs-Q zJtw{JP)2~yp`=NyNMB!G=%643-(HX%B|fH}>{9E(TU7?|FI&{UQ`1^Mql)_hZaucP zZrq!vOPyJmS&@k8Bby=@hxYA1LeE<1SwDIaHD@^F zW|@H3py}P)sS4pqQ)!3%c6)YOde48f6Kf(FvjY$8>ROvR(-Ov(89B6jj43^#0acxf z%Nnh+JV|vEYq52Jn?&+{I>B>vS|H#?YiE)}@TN*rqfOol+eU%6Q3;D($anrEa*P^ePRoEK6oXjO3eHAp>V zA9Otsx=p=sm}mGCFT=4_wi4V2RlNA5@ZbLH;OnIt@b2U4;UePs+Id|Yr8s(u7)PiR zgiapPs|E%R+nQ@etd#k`ut8?TrA{~7loh^CqV3v#MjUv&XaBvd_3?Q8x{GMamfmaZ zAFf>dJbLl(==BWna`*7=#nbB{QVE)0KgH!_++~^0DkRs^`6?ZHVxX@L=;7(Y+xmL6 zUnPk1@9E{DD^r7C#&I8%CfA-A@+kY<_;Ri-Eq4A7`fiR_AuH(x`wRsr4!OZc7cnDq zOXX!=7I{r!x_6Fj{aTGnOzBUmO{wWI+jDyMS0xE!pMCpl;IuQTO$G-~WRej2Ltu~b zx`CdE?_DfrYBS*&(;_XGsdTe4mi15Y0(U?q_3Mvs7ZF^Pb%f@Q!&06X*R7c=@C1#J zNg#SQe{S!GT(m=mXh79S*eu`s@i(=UopECrmMeOy-Qj!1U!};GKuZQUR}(d_K_)bu z=Rhp@n6zjNE)Wiv*3i~JvOqu`;lp*KhW;g|?o?jQ5z1bxt{O?V#|S$TW0rl;yuo2H z7;`TKeEN7hySjU8);?14i3+Uutk1lktR;A2hEJJ^NEoqhmWn8GpPF^ zu&HaH*GI4kU(3J%a5yhhWjT;L1}weHM1b*)6sOHfjQgf46)d6YDD|OB;_7x2Y<_oe z9wP?3s=f}b@q(P*x^OMEWCm#*>8}H<8>wbF#Lzvu$n?1Sxcm4%cWVRg?YT@Qcj`vQ zy@Q*qZfW3#dy9!~`<@uo*WlHSHq14Wvn~r;GSKm{;O%E3+N{s#C4b`-XeWGX1FI!H zJ`vi&fj>4YM@A&+#^k(PD<2)#GVb5X&gbm*udwjmuJfVyQ)tzs? zS2VipLlzpJ1oSgku=(lhia&?63(%9MykWty7+5l$LylOffQ!!N<8`PwM+7@R_Gq}> zp1+QY8cq0vh>X>(T1Hm0vS`Dj=hmhf;KdD!Od|PNm1$5Kk!sBy_i?Z*OMGq?w7~UB zPo6BweG9ABPshxc*ECb`6ACY$ij2u@@LBJg)$Z<(E=KQxR%1%hY&|-uak?b2Wzno%#DGCs=X#c>)7XyGgtU-PFPq^Co~A9Qg_FH(P7~p=F>^~BUy<1 zqdg;cm_S~WoybGstp#iSTeb^taD}6mR%W8)qV~ML*+d?x|Ycc%CXbCCk z{G0GI5dyiJ)pFId5s~o+Z*2XyaB~y6B9L4mP1zgXeWG$7D&wRTg7^1p1}h`VJq`f) z&;&|?hkzlXXleF9HMo;H3C+G>(CK{@Uz}R$^XXylp)Fn83qe1~H>to^Wj*Il`K;GC zcU?t6+i&Yg>^pacE@aW)$EBsUo$o=nekJD_W+PMz^-YuS!s%SNpVP)tV7kvBp=-x? zJO7DSSlH;3mE`5*@3y>HNj=y&=g$Y}8FPHHO=>-{v2j1y1oZK137#r}ZWVOkO&jnN z`Xh&MwEei9pSKO$h^uT9v^^zqWZZ}Q;P8H~$P|bdzBJSgwxUZ*HYtg%o0G4%xuy4= z3F_s=iGO-n$Weq57^B_F;OP;M?4Z>!qe=)oI?Tw21sdo+D_qmh1hk)S=Wqz-o(|2y zDWkzr!NS1N-~1MiuCgk>2^tHgwwN1aNW}OTv;pnww8H@xSdqX+Js#1N1V?XUP*q7A zwwhIhz7=Q?GPx)CeuQ(QJjhomom>ail{ILuwl3p zA_>M*@7#XZLx2Fdc*!LfvQbM)W;Qh)B07}fEiuhO#A@5JrLwGxboyE7z@Ce)l!yCl z+Qv$r%EiZoklS-q3FbF7p=744oE!;5ox-$)3t-^YeQwtSMN19x^p3z5W7Gt>?(tI* z!Dv%O_9AuB3U2s~wyl9Ri~�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 diff --git a/charts/netmaker/charts/postgresql-15.1.2.tgz b/charts/netmaker/charts/postgresql-15.1.2.tgz new file mode 100644 index 0000000000000000000000000000000000000000..519b11c031f55446c7964321be3a19670fc92bec GIT binary patch literal 73720 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 vGoiRG3A~oi#w^w}wX_`P-d Date: Sun, 24 Mar 2024 11:33:07 +0100 Subject: [PATCH 04/40] update docs and chart version for a major bump --- charts/netmaker/Chart.yaml | 2 +- charts/netmaker/README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/charts/netmaker/Chart.yaml b/charts/netmaker/Chart.yaml index 0094fbe..302e73d 100644 --- a/charts/netmaker/Chart.yaml +++ b/charts/netmaker/Chart.yaml @@ -15,7 +15,7 @@ type: application # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: 0.9.1 +version: 0.10.0 # This is the version number of the application being deployed. This version number should be # incremented each time you make changes to the application. Versions are not expected to diff --git a/charts/netmaker/README.md b/charts/netmaker/README.md index 2244834..65d644e 100644 --- a/charts/netmaker/README.md +++ b/charts/netmaker/README.md @@ -1,6 +1,6 @@ # netmaker -![Version: 0.9.1](https://img.shields.io/badge/Version-0.9.1-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: v0.20.3](https://img.shields.io/badge/AppVersion-v0.20.3-informational?style=flat-square) +![Version: 0.10.0](https://img.shields.io/badge/Version-0.10.0-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: v0.20.3](https://img.shields.io/badge/AppVersion-v0.20.3-informational?style=flat-square) A Helm chart to run HA Netmaker on Kubernetes From d856a74157d51279deb2f3e2a1ff9b5e760beaf5 Mon Sep 17 00:00:00 2001 From: jessebot Date: Sun, 24 Mar 2024 11:36:33 +0100 Subject: [PATCH 05/40] add pre-commit file --- .pre-commit-config.yaml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 .pre-commit-config.yaml diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..3a5a0ed --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,16 @@ +repos: + # update the chart README.md with the comments from values.yaml + - repo: https://github.com/norwoodj/helm-docs + rev: v1.13.1 + hooks: + - id: helm-docs + # helm lint and markdown link verifier + - repo: https://github.com/gruntwork-io/pre-commit + rev: v0.1.23 + hooks: + - id: helmlint + # detect any secrets that may be committed before they're committed + - repo: https://github.com/gitleaks/gitleaks + rev: v8.18.2 + hooks: + - id: gitleaks From b9ea46d9bc2a6e7acb1883c01d74b9c90fd47cda Mon Sep 17 00:00:00 2001 From: jessebot Date: Sun, 24 Mar 2024 11:40:28 +0100 Subject: [PATCH 06/40] adding testing and renovate udpates --- .github/workflows/ci-helm-lint-test.yml | 51 +++++++++++++++++++++++ .github/workflows/renovate.yml | 26 +++++++++--- renovate.json | 55 ++++++++++++++++++++++++- 3 files changed, 125 insertions(+), 7 deletions(-) create mode 100644 .github/workflows/ci-helm-lint-test.yml diff --git a/.github/workflows/ci-helm-lint-test.yml b/.github/workflows/ci-helm-lint-test.yml new file mode 100644 index 0000000..0c068b8 --- /dev/null +++ b/.github/workflows/ci-helm-lint-test.yml @@ -0,0 +1,51 @@ +name: Lint and Test Chart + +on: + pull_request: + paths: + - 'charts/netmaker/**' + +permissions: + contents: read + +jobs: + lint: + name: Lint + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: "0" + + - name: Install Helm + uses: azure/setup-helm@v4 + + - name: Add dependency chart repos + run: | + helm repo add bitnami https://charts.bitnami.com/bitnami + + - name: Set up chart-testing + uses: helm/chart-testing-action@v2.6.1 + + - name: Run chart-testing (list-changed) + id: list-changed + run: | + changed=$(ct list-changed --target-branch ${{ github.event.repository.default_branch }}) + if [[ -n "$changed" ]]; then + echo "changed=true" >> "$GITHUB_OUTPUT" + fi + + - name: Run chart-testing (lint) + id: lint + if: steps.list-changed.outputs.changed == 'true' + run: ct lint --target-branch ${{ github.event.repository.default_branch }} + + - name: Create kind cluster + uses: helm/kind-action@v1.9.0 + if: steps.list-changed.outputs.changed == 'true' + + - name: Run chart-testing (install) + id: install + if: steps.list-changed.outputs.changed == 'true' + run: ct install --target-branch ${{ github.event.repository.default_branch }} diff --git a/.github/workflows/renovate.yml b/.github/workflows/renovate.yml index 77ce572..4ca1c7e 100644 --- a/.github/workflows/renovate.yml +++ b/.github/workflows/renovate.yml @@ -1,17 +1,31 @@ -name: Renovate - check for dependency updates +name: Renovate on: schedule: # The "*" (#42, asterisk) character has special semantics in YAML, so this # string has to be quoted. - - cron: '1 * * * *' + - cron: '0/15 * * * *' + push: + branches: + - main + paths: + - ".github/workflows/renovate.yml" + - "renovate.json" jobs: renovate: runs-on: ubuntu-latest steps: + - name: Get token + id: get_token + uses: tibdex/github-app-token@v2 + with: + private_key: ${{ secrets.private_key }} + app_id: ${{ secrets.app_id }} + - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4.1.1 + - name: Self-hosted Renovate - uses: renovatebot/github-action@v39.0.1 + uses: renovatebot/github-action@v40.1.5 with: - token: ${{ secrets.RENOVATE_TOKEN }} - configurationFile: .github/config.js + configurationFile: renovate.json + token: '${{ steps.get_token.outputs.token }}' diff --git a/renovate.json b/renovate.json index 7190a60..813b3f5 100644 --- a/renovate.json +++ b/renovate.json @@ -1,3 +1,56 @@ { - "$schema": "https://docs.renovatebot.com/renovate-schema.json" + "extends": ["config:recommended"], + "allowPostUpgradeCommandTemplating": true, + "allowedPostUpgradeCommands": ["^.*"], + "repositories": ["small-hack/netmaker-helm"], + "platform": "github", + "forkProcessing": "enabled", + "configMigration": true, + "onboarding": false, + "requireConfig": "optional", + "customManagers": [ + { + "customType": "regex", + "fileMatch": ["(^|/)Chart\\.yaml$"], + "matchStrings": [ + "#\\s?renovate: image=(?.*?)\\s?appVersion:\\s?\\\"?(?[\\w+\\.\\-]*)" + ], + "datasourceTemplate": "docker" + } + ], + "packageRules": [ + { + "matchManagers": ["helm-requirements", "helm-values", "custom.regex"], + "matchUpdateTypes": ["patch"], + "postUpgradeTasks": { + "commands": [ + "version=$(grep '^version:' charts/netmaker/Chart.yaml | awk '{print $2}')\n major=$(echo $version | cut -d. -f1)\n minor=$(echo $version | cut -d. -f2)\n patch=$(echo $version | cut -d. -f3)\n patch=$(expr $patch + 1)\n echo \"Replacing $version with $major.$minor.$patch\"\n sed -i \"s/^version:.*/version: ${major}.${minor}.${patch}/g\" charts/netmaker/Chart.yaml\n cat charts/netmaker/Chart.yaml\n sed -i \"s/${version}/${major}.${minor}.${patch}/g\" charts/netmaker/README.md\n" + ], + "fileFilters": ["**/Chart.yaml"], + "executionMode": "branch" + } + }, + { + "matchManagers": ["helm-requirements", "helm-values", "custom.regex"], + "matchUpdateTypes": ["minor"], + "postUpgradeTasks": { + "commands": [ + "version=$(grep '^version:' charts/netmaker/Chart.yaml | awk '{print $2}')\n major=$(echo $version | cut -d. -f1)\n minor=$(echo $version | cut -d. -f2)\n patch=$(echo $version | cut -d. -f3)\n minor=$(expr $minor + 1)\n echo \"Replacing $version with $major.$minor.$patch\"\n sed -i \"s/^version:.*/version: ${major}.${minor}.${patch}/g\" charts/netmaker/Chart.yaml\n cat charts/netmaker/Chart.yaml\n sed -i \"s/${version}/${major}.${minor}.${patch}/g\" charts/netmaker/README.md\n" + ], + "fileFilters": ["**/Chart.yaml"], + "executionMode": "branch" + } + }, + { + "matchManagers": ["helm-requirements", "helm-values", "custom.regex"], + "matchUpdateTypes": ["major"], + "postUpgradeTasks": { + "commands": [ + "version=$(grep '^version:' charts/netmaker/Chart.yaml | awk '{print $2}')\n major=$(echo $version | cut -d. -f1)\n major=$(expr $major + 1)\n minor=$(echo $version | cut -d. -f2)\n patch=$(echo $version | cut -d. -f3)\n echo \"Replacing $version with $major.$minor.$patch\"\n sed -i \"s/^version:.*/version: ${major}.${minor}.${patch}/g\" charts/netmaker/Chart.yaml\n cat charts/netmaker/Chart.yaml\n sed -i \"s/${version}/${major}.${minor}.${patch}/g\" charts/netmaker/README.md\n" + ], + "fileFilters": ["**/Chart.yaml"], + "executionMode": "branch" + } + } + ] } From 84949cb17efaf28ec05d657e3a9b3a90769bcf6a Mon Sep 17 00:00:00 2001 From: jessebot Date: Sun, 24 Mar 2024 11:42:28 +0100 Subject: [PATCH 07/40] add maintainers to chart --- charts/netmaker/Chart.yaml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/charts/netmaker/Chart.yaml b/charts/netmaker/Chart.yaml index 302e73d..319d110 100644 --- a/charts/netmaker/Chart.yaml +++ b/charts/netmaker/Chart.yaml @@ -23,6 +23,14 @@ version: 0.10.0 # It is recommended to use it with quotes. appVersion: "v0.20.3" +maintainers: + - name: "jessebot" + email: "jessebot@linux.com" + url: "https://github.com/jessebot/" + - name: "cloudymax" + email: "emax@cloudydev.net" + url: "https://github.com/cloudymax/" + dependencies: - name: postgresql version: 15.1.2 From 0cd735d4a227deeb740d908f42ecf2b27b3869c2 Mon Sep 17 00:00:00 2001 From: jessebot Date: Sun, 24 Mar 2024 12:01:07 +0100 Subject: [PATCH 08/40] seperate out the ingresses --- charts/netmaker/templates/cert.yaml | 36 +++ charts/netmaker/templates/ingress-mqtt.yaml | 70 ++++++ charts/netmaker/templates/ingress-rest.yaml | 75 ++++++ charts/netmaker/templates/ingress-route.yaml | 37 +++ charts/netmaker/templates/ingress-ui.yaml | 75 ++++++ charts/netmaker/templates/ingress.yaml | 235 ------------------- 6 files changed, 293 insertions(+), 235 deletions(-) create mode 100644 charts/netmaker/templates/cert.yaml create mode 100644 charts/netmaker/templates/ingress-mqtt.yaml create mode 100644 charts/netmaker/templates/ingress-rest.yaml create mode 100644 charts/netmaker/templates/ingress-route.yaml create mode 100644 charts/netmaker/templates/ingress-ui.yaml delete mode 100644 charts/netmaker/templates/ingress.yaml diff --git a/charts/netmaker/templates/cert.yaml b/charts/netmaker/templates/cert.yaml new file mode 100644 index 0000000..c1141d2 --- /dev/null +++ b/charts/netmaker/templates/cert.yaml @@ -0,0 +1,36 @@ +{{- if .Values.ingress.enabled -}} +{{- $fullName := include "netmaker.fullname" . -}} +{{- $fullUIName := printf "%s-%s" $fullName "ui" -}} +{{- $fullRESTName := printf "%s-%s" $fullName "rest" -}} +{{- $fullMQName := printf "%s-%s" $fullName "mqtt" -}} +{{- $uiSvcPort := .Values.service.uiPort -}} +{{- $restSvcPort := .Values.service.restPort -}} +{{- $mqSvcPort := 8883 -}} +{{- $classname := required "A valid .Values.ingress.className entry required! Please set this to your ingress class (nginx, traefik)" .Values.ingress.className}} +{{- if and .Values.ingress.className (not (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion)) }} + {{- if not (hasKey .Values.ingress.annotations "kubernetes.io/ingress.class") }} + {{- $_ := set .Values.ingress.annotations "kubernetes.io/ingress.class" .Values.ingress.className}} + {{- end }} +{{- end }} +{{- if and .Values.ingress.tls.enabled (not (eq .Values.ingress.tls.issuerName "" ))}} +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + annotations: + acme.cert-manager.io/http01-override-ingress-name: {{ $fullMQName }} + labels: + {{- include "netmaker.labels" . | nindent 4 }} + name: {{ $fullMQName }}-tls-secret +spec: + dnsNames: + - {{ .Values.ingress.hostPrefix.mq }}{{ .Values.baseDomain }} + issuerRef: + group: cert-manager.io + kind: ClusterIssuer + name: {{ .Values.ingress.tls.issuerName }} + secretName: {{ $fullMQName }}-tls-secret + usages: + - digital signature + - key encipherment +{{- end }} +{{- end }} diff --git a/charts/netmaker/templates/ingress-mqtt.yaml b/charts/netmaker/templates/ingress-mqtt.yaml new file mode 100644 index 0000000..495b7bc --- /dev/null +++ b/charts/netmaker/templates/ingress-mqtt.yaml @@ -0,0 +1,70 @@ +{{- if .Values.ingress.enabled -}} +{{- $fullName := include "netmaker.fullname" . -}} +{{- $fullUIName := printf "%s-%s" $fullName "ui" -}} +{{- $fullRESTName := printf "%s-%s" $fullName "rest" -}} +{{- $fullMQName := printf "%s-%s" $fullName "mqtt" -}} +{{- $uiSvcPort := .Values.service.uiPort -}} +{{- $restSvcPort := .Values.service.restPort -}} +{{- $mqSvcPort := 8883 -}} +{{- $classname := required "A valid .Values.ingress.className entry required! Please set this to your ingress class (nginx, traefik)" .Values.ingress.className}} +{{- if and .Values.ingress.className (not (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion)) }} + {{- if not (hasKey .Values.ingress.annotations "kubernetes.io/ingress.class") }} + {{- $_ := set .Values.ingress.annotations "kubernetes.io/ingress.class" .Values.ingress.className}} + {{- end }} +{{- end }} +{{- if or (eq .Values.ingress.className "nginx") (eq .Values.ingress.className "public") }} +{{- if semverCompare ">=1.19-0" .Capabilities.KubeVersion.GitVersion -}} +apiVersion: networking.k8s.io/v1 +{{- else if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}} +apiVersion: networking.k8s.io/v1beta1 +{{- else -}} +apiVersion: extensions/v1beta1 +{{- end }} +kind: Ingress +metadata: + name: {{ $fullMQName }} + labels: + {{- include "netmaker.labels" . | nindent 4 }} + {{- with .Values.ingress }} + annotations: + {{- toYaml .annotations.nginx | nindent 4 }} + {{- if and .tls.enabled (eq .tls.issuerName "" )}} + {{- toYaml .annotations.tls | nindent 4 }} + {{- else if .tls.enabled}} + cert-manager.io/cluster-issuer: {{ .tls.issuerName }} + {{- end }} + {{- with .annotations.mq }} + {{- toYaml . | nindent 4 }} + {{- end }} + {{- end }} +spec: + {{- if and .Values.ingress.className (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion) }} + ingressClassName: {{ required "A valid .Values.ingress.className entry required!" .Values.ingress.className}} + {{- end }} + {{- if .Values.ingress.tls.enabled }} + tls: + - hosts: + - {{ .Values.ingress.hostPrefix.broker }}{{ .Values.baseDomain }} + secretName: {{ $fullMQName }}-tls-secret + {{- end }} + rules: + - host: {{ .Values.ingress.hostPrefix.broker }}{{ .Values.baseDomain }} + http: + paths: + - path: / + {{- if (semverCompare ">=1.18-0" $.Capabilities.KubeVersion.GitVersion) }} + pathType: Prefix + {{- end }} + backend: + {{- if semverCompare ">=1.19-0" $.Capabilities.KubeVersion.GitVersion }} + service: + name: {{ $fullMQName }} + port: + number: {{ $mqSvcPort }} + {{- else }} + serviceName: {{ $fullMQName }} + servicePort: {{ $mqSvcPort }} + {{- end }} +{{- end }} +{{- end }} +{{- end }} diff --git a/charts/netmaker/templates/ingress-rest.yaml b/charts/netmaker/templates/ingress-rest.yaml new file mode 100644 index 0000000..04dbb94 --- /dev/null +++ b/charts/netmaker/templates/ingress-rest.yaml @@ -0,0 +1,75 @@ +{{- if .Values.ingress.enabled -}} +{{- $fullName := include "netmaker.fullname" . -}} +{{- $fullUIName := printf "%s-%s" $fullName "ui" -}} +{{- $fullRESTName := printf "%s-%s" $fullName "rest" -}} +{{- $fullMQName := printf "%s-%s" $fullName "mqtt" -}} +{{- $uiSvcPort := .Values.service.uiPort -}} +{{- $restSvcPort := .Values.service.restPort -}} +{{- $mqSvcPort := 8883 -}} +{{- $classname := required "A valid .Values.ingress.className entry required! Please set this to your ingress class (nginx, traefik)" .Values.ingress.className}} +{{- if and .Values.ingress.className (not (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion)) }} + {{- if not (hasKey .Values.ingress.annotations "kubernetes.io/ingress.class") }} + {{- $_ := set .Values.ingress.annotations "kubernetes.io/ingress.class" .Values.ingress.className}} + {{- end }} +{{- end }} +{{- if semverCompare ">=1.19-0" .Capabilities.KubeVersion.GitVersion -}} +apiVersion: networking.k8s.io/v1 +{{- else if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}} +apiVersion: networking.k8s.io/v1beta1 +{{- else -}} +apiVersion: extensions/v1beta1 +{{- end }} +kind: Ingress +metadata: + name: {{ $fullRESTName }} + labels: + {{- include "netmaker.labels" . | nindent 4 }} + {{- with .Values.ingress }} + annotations: + {{- toYaml .annotations.base | nindent 4 }} + {{- if or (eq .className "nginx") (eq .className "public") }} + {{- toYaml .annotations.nginx | nindent 4 }} + {{- end }} + {{- if eq .className "traefik" }} + {{- toYaml .annotations.traefik | nindent 4 }} + {{- end }} + {{- if and .tls.enabled (eq .tls.issuerName "" )}} + {{- toYaml .annotations.tls | nindent 4 }} + {{- else if .tls.enabled}} + cert-manager.io/cluster-issuer: {{ .tls.issuerName }} + {{- end }} + {{- with .annotations.rest }} + {{- toYaml . | nindent 4 }} + {{- end }} + {{- end }} +spec: + {{- if (not (eq .Values.ingress.className "traefik")) }} + {{- if and .Values.ingress.className (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion) }} + ingressClassName: {{ required "A valid .Values.ingress.className entry required!" .Values.ingress.className}} + {{- end }} + {{- end }} + {{- if .Values.ingress.tls.enabled }} + tls: + - hosts: + - {{ .Values.ingress.hostPrefix.rest }}{{ .Values.baseDomain }} + secretName: {{ $fullRESTName }}-tls-secret + {{- end }} + rules: + - host: {{ .Values.ingress.hostPrefix.rest }}{{ .Values.baseDomain }} + http: + paths: + - path: / + {{- if (semverCompare ">=1.18-0" $.Capabilities.KubeVersion.GitVersion) }} + pathType: Prefix + {{- end }} + backend: + {{- if semverCompare ">=1.19-0" $.Capabilities.KubeVersion.GitVersion }} + service: + name: {{ $fullRESTName }} + port: + number: {{ $restSvcPort }} + {{- else }} + serviceName: {{ $fullRESTName }} + servicePort: {{ $restSvcPort }} + {{- end }} +{{- end }} diff --git a/charts/netmaker/templates/ingress-route.yaml b/charts/netmaker/templates/ingress-route.yaml new file mode 100644 index 0000000..61c2a39 --- /dev/null +++ b/charts/netmaker/templates/ingress-route.yaml @@ -0,0 +1,37 @@ +{{- if .Values.ingress.enabled -}} +{{- $fullName := include "netmaker.fullname" . -}} +{{- $fullUIName := printf "%s-%s" $fullName "ui" -}} +{{- $fullRESTName := printf "%s-%s" $fullName "rest" -}} +{{- $fullMQName := printf "%s-%s" $fullName "mqtt" -}} +{{- $uiSvcPort := .Values.service.uiPort -}} +{{- $restSvcPort := .Values.service.restPort -}} +{{- $mqSvcPort := 8883 -}} +{{- $classname := required "A valid .Values.ingress.className entry required! Please set this to your ingress class (nginx, traefik)" .Values.ingress.className}} +{{- if and .Values.ingress.className (not (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion)) }} + {{- if not (hasKey .Values.ingress.annotations "kubernetes.io/ingress.class") }} + {{- $_ := set .Values.ingress.annotations "kubernetes.io/ingress.class" .Values.ingress.className}} + {{- end }} +{{- end }} +{{- if eq .Values.ingress.className "traefik" }} +--- +apiVersion: traefik.containo.us/v1alpha1 +kind: IngressRouteTCP +metadata: + name: {{ $fullMQName }} + labels: + {{- include "netmaker.labels" . | nindent 4 }} +spec: + entryPoints: + - websecure + routes: + - match: HostSNI(`{{ .Values.ingress.hostPrefix.mq }}{{ .Values.baseDomain }}`) + services: + - name: {{ $fullMQName }} + port: {{ $mqSvcPort }} + tls: + passthrough: true + secretName: {{ $fullMQName }}-tls-secret + domains: + - main: {{ .Values.ingress.hostPrefix.mq }}{{ .Values.baseDomain }} +{{- end }} +{{- end }} diff --git a/charts/netmaker/templates/ingress-ui.yaml b/charts/netmaker/templates/ingress-ui.yaml new file mode 100644 index 0000000..f22d270 --- /dev/null +++ b/charts/netmaker/templates/ingress-ui.yaml @@ -0,0 +1,75 @@ +{{- if .Values.ingress.enabled -}} +{{- $fullName := include "netmaker.fullname" . -}} +{{- $fullUIName := printf "%s-%s" $fullName "ui" -}} +{{- $fullRESTName := printf "%s-%s" $fullName "rest" -}} +{{- $fullMQName := printf "%s-%s" $fullName "mqtt" -}} +{{- $uiSvcPort := .Values.service.uiPort -}} +{{- $restSvcPort := .Values.service.restPort -}} +{{- $mqSvcPort := 8883 -}} +{{- $classname := required "A valid .Values.ingress.className entry required! Please set this to your ingress class (nginx, traefik)" .Values.ingress.className}} +{{- if and .Values.ingress.className (not (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion)) }} + {{- if not (hasKey .Values.ingress.annotations "kubernetes.io/ingress.class") }} + {{- $_ := set .Values.ingress.annotations "kubernetes.io/ingress.class" .Values.ingress.className}} + {{- end }} +{{- end }} +{{- if semverCompare ">=1.19-0" .Capabilities.KubeVersion.GitVersion -}} +apiVersion: networking.k8s.io/v1 +{{- else if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}} +apiVersion: networking.k8s.io/v1beta1 +{{- else -}} +apiVersion: extensions/v1beta1 +{{- end }} +kind: Ingress +metadata: + name: {{ $fullUIName }} + labels: + {{- include "netmaker.labels" . | nindent 4 }} + {{- with .Values.ingress }} + annotations: + {{- toYaml .annotations.base | nindent 4 }} + {{- if or (eq .className "nginx") (eq .className "public") }} + {{- toYaml .annotations.nginx | nindent 4 }} + {{- end }} + {{- if eq .className "traefik" }} + {{- toYaml .annotations.traefik | nindent 4 }} + {{- end }} + {{- if and .tls.enabled (eq .tls.issuerName "" )}} + {{- toYaml .annotations.tls | nindent 4 }} + {{- else if .tls.enabled}} + cert-manager.io/cluster-issuer: {{ .tls.issuerName }} + {{- end }} + {{- with .annotations.ui }} + {{- toYaml . | nindent 4 }} + {{- end }} + {{- end }} +spec: + {{- if (not (eq .Values.ingress.className "traefik")) }} + {{- if and .Values.ingress.className (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion) }} + ingressClassName: {{ required "A valid .Values.ingress.className entry required!" .Values.ingress.className}} + {{- end }} + {{- end }} + {{- if .Values.ingress.tls.enabled }} + tls: + - hosts: + - {{ .Values.ingress.hostPrefix.ui }}{{ .Values.baseDomain }} + secretName: {{ $fullUIName }}-tls-secret + {{- end}} + rules: + - host: {{ .Values.ingress.hostPrefix.ui }}{{ .Values.baseDomain }} + http: + paths: + - path: / + {{- if (semverCompare ">=1.18-0" $.Capabilities.KubeVersion.GitVersion) }} + pathType: Prefix + {{- end }} + backend: + {{- if semverCompare ">=1.19-0" $.Capabilities.KubeVersion.GitVersion }} + service: + name: {{ $fullUIName }} + port: + number: {{ $uiSvcPort }} + {{- else }} + serviceName: {{ $fullUIName }} + servicePort: {{ $uiSvcPort }} + {{- end }} +{{- end }} diff --git a/charts/netmaker/templates/ingress.yaml b/charts/netmaker/templates/ingress.yaml deleted file mode 100644 index 5056280..0000000 --- a/charts/netmaker/templates/ingress.yaml +++ /dev/null @@ -1,235 +0,0 @@ -{{- if .Values.ingress.enabled -}} -{{- $fullName := include "netmaker.fullname" . -}} -{{- $fullUIName := printf "%s-%s" $fullName "ui" -}} -{{- $fullRESTName := printf "%s-%s" $fullName "rest" -}} -{{- $fullMQName := printf "%s-%s" $fullName "mqtt" -}} -{{- $uiSvcPort := .Values.service.uiPort -}} -{{- $restSvcPort := .Values.service.restPort -}} -{{- $mqSvcPort := 8883 -}} -{{- $classname := required "A valid .Values.ingress.className entry required! Please set this to your ingress class (nginx, traefik)" .Values.ingress.className}} -{{- if and .Values.ingress.className (not (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion)) }} - {{- if not (hasKey .Values.ingress.annotations "kubernetes.io/ingress.class") }} - {{- $_ := set .Values.ingress.annotations "kubernetes.io/ingress.class" .Values.ingress.className}} - {{- end }} -{{- end }} -{{- if semverCompare ">=1.19-0" .Capabilities.KubeVersion.GitVersion -}} -apiVersion: networking.k8s.io/v1 -{{- else if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}} -apiVersion: networking.k8s.io/v1beta1 -{{- else -}} -apiVersion: extensions/v1beta1 -{{- end }} -kind: Ingress -metadata: - name: {{ $fullUIName }} - labels: - {{- include "netmaker.labels" . | nindent 4 }} - {{- with .Values.ingress }} - annotations: - {{- toYaml .annotations.base | nindent 4 }} - {{- if or (eq .className "nginx") (eq .className "public") }} - {{- toYaml .annotations.nginx | nindent 4 }} - {{- end }} - {{- if eq .className "traefik" }} - {{- toYaml .annotations.traefik | nindent 4 }} - {{- end }} - {{- if and .tls.enabled (eq .tls.issuerName "" )}} - {{- toYaml .annotations.tls | nindent 4 }} - {{- else if .tls.enabled}} - cert-manager.io/cluster-issuer: {{ .tls.issuerName }} - {{- end }} - {{- with .annotations.ui }} - {{- toYaml . | nindent 4 }} - {{- end }} - {{- end }} -spec: - {{- if (not (eq .Values.ingress.className "traefik")) }} - {{- if and .Values.ingress.className (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion) }} - ingressClassName: {{ required "A valid .Values.ingress.className entry required!" .Values.ingress.className}} - {{- end }} - {{- end }} - {{- if .Values.ingress.tls.enabled }} - tls: - - hosts: - - {{ .Values.ingress.hostPrefix.ui }}{{ .Values.baseDomain }} - secretName: {{ $fullUIName }}-tls-secret - {{- end}} - rules: - - host: {{ .Values.ingress.hostPrefix.ui }}{{ .Values.baseDomain }} - http: - paths: - - path: / - {{- if (semverCompare ">=1.18-0" $.Capabilities.KubeVersion.GitVersion) }} - pathType: Prefix - {{- end }} - backend: - {{- if semverCompare ">=1.19-0" $.Capabilities.KubeVersion.GitVersion }} - service: - name: {{ $fullUIName }} - port: - number: {{ $uiSvcPort }} - {{- else }} - serviceName: {{ $fullUIName }} - servicePort: {{ $uiSvcPort }} - {{- end }} ---- -{{- if semverCompare ">=1.19-0" .Capabilities.KubeVersion.GitVersion -}} -apiVersion: networking.k8s.io/v1 -{{- else if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}} -apiVersion: networking.k8s.io/v1beta1 -{{- else -}} -apiVersion: extensions/v1beta1 -{{- end }} -kind: Ingress -metadata: - name: {{ $fullRESTName }} - labels: - {{- include "netmaker.labels" . | nindent 4 }} - {{- with .Values.ingress }} - annotations: - {{- toYaml .annotations.base | nindent 4 }} - {{- if or (eq .className "nginx") (eq .className "public") }} - {{- toYaml .annotations.nginx | nindent 4 }} - {{- end }} - {{- if eq .className "traefik" }} - {{- toYaml .annotations.traefik | nindent 4 }} - {{- end }} - {{- if and .tls.enabled (eq .tls.issuerName "" )}} - {{- toYaml .annotations.tls | nindent 4 }} - {{- else if .tls.enabled}} - cert-manager.io/cluster-issuer: {{ .tls.issuerName }} - {{- end }} - {{- with .annotations.rest }} - {{- toYaml . | nindent 4 }} - {{- end }} - {{- end }} -spec: - {{- if (not (eq .Values.ingress.className "traefik")) }} - {{- if and .Values.ingress.className (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion) }} - ingressClassName: {{ required "A valid .Values.ingress.className entry required!" .Values.ingress.className}} - {{- end }} - {{- end }} - {{- if .Values.ingress.tls.enabled }} - tls: - - hosts: - - {{ .Values.ingress.hostPrefix.rest }}{{ .Values.baseDomain }} - secretName: {{ $fullRESTName }}-tls-secret - {{- end }} - rules: - - host: {{ .Values.ingress.hostPrefix.rest }}{{ .Values.baseDomain }} - http: - paths: - - path: / - {{- if (semverCompare ">=1.18-0" $.Capabilities.KubeVersion.GitVersion) }} - pathType: Prefix - {{- end }} - backend: - {{- if semverCompare ">=1.19-0" $.Capabilities.KubeVersion.GitVersion }} - service: - name: {{ $fullRESTName }} - port: - number: {{ $restSvcPort }} - {{- else }} - serviceName: {{ $fullRESTName }} - servicePort: {{ $restSvcPort }} - {{- end }} -{{- if or (eq .Values.ingress.className "nginx") (eq .Values.ingress.className "public") }} ---- -{{- if semverCompare ">=1.19-0" .Capabilities.KubeVersion.GitVersion -}} -apiVersion: networking.k8s.io/v1 -{{- else if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}} -apiVersion: networking.k8s.io/v1beta1 -{{- else -}} -apiVersion: extensions/v1beta1 -{{- end }} -kind: Ingress -metadata: - name: {{ $fullMQName }} - labels: - {{- include "netmaker.labels" . | nindent 4 }} - {{- with .Values.ingress }} - annotations: - {{- toYaml .annotations.nginx | nindent 4 }} - {{- if and .tls.enabled (eq .tls.issuerName "" )}} - {{- toYaml .annotations.tls | nindent 4 }} - {{- else if .tls.enabled}} - cert-manager.io/cluster-issuer: {{ .tls.issuerName }} - {{- end }} - {{- with .annotations.mq }} - {{- toYaml . | nindent 4 }} - {{- end }} - {{- end }} -spec: - {{- if and .Values.ingress.className (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion) }} - ingressClassName: {{ required "A valid .Values.ingress.className entry required!" .Values.ingress.className}} - {{- end }} - {{- if .Values.ingress.tls.enabled }} - tls: - - hosts: - - {{ .Values.ingress.hostPrefix.broker }}{{ .Values.baseDomain }} - secretName: {{ $fullMQName }}-tls-secret - {{- end }} - rules: - - host: {{ .Values.ingress.hostPrefix.broker }}{{ .Values.baseDomain }} - http: - paths: - - path: / - {{- if (semverCompare ">=1.18-0" $.Capabilities.KubeVersion.GitVersion) }} - pathType: Prefix - {{- end }} - backend: - {{- if semverCompare ">=1.19-0" $.Capabilities.KubeVersion.GitVersion }} - service: - name: {{ $fullMQName }} - port: - number: {{ $mqSvcPort }} - {{- else }} - serviceName: {{ $fullMQName }} - servicePort: {{ $mqSvcPort }} - {{- end }} -{{- end }} -{{- if eq .Values.ingress.className "traefik" }} ---- -apiVersion: traefik.containo.us/v1alpha1 -kind: IngressRouteTCP -metadata: - name: {{ $fullMQName }} - labels: - {{- include "netmaker.labels" . | nindent 4 }} -spec: - entryPoints: - - websecure - routes: - - match: HostSNI(`{{ .Values.ingress.hostPrefix.mq }}{{ .Values.baseDomain }}`) - services: - - name: {{ $fullMQName }} - port: {{ $mqSvcPort }} - tls: - passthrough: true - secretName: {{ $fullMQName }}-tls-secret - domains: - - main: {{ .Values.ingress.hostPrefix.mq }}{{ .Values.baseDomain }} -{{- if and .Values.ingress.tls.enabled (not (eq .Values.ingress.tls.issuerName "" ))}} ---- -apiVersion: cert-manager.io/v1 -kind: Certificate -metadata: - annotations: - acme.cert-manager.io/http01-override-ingress-name: {{ $fullMQName }} - labels: - {{- include "netmaker.labels" . | nindent 4 }} - name: {{ $fullMQName }}-tls-secret -spec: - dnsNames: - - {{ .Values.ingress.hostPrefix.mq }}{{ .Values.baseDomain }} - issuerRef: - group: cert-manager.io - kind: ClusterIssuer - name: {{ .Values.ingress.tls.issuerName }} - secretName: {{ $fullMQName }}-tls-secret - usages: - - digital signature - - key encipherment -{{- end }} -{{- end }} -{{- end }} From e36c72ff5fbc6b6f180b8d920f0fbdd049fceec0 Mon Sep 17 00:00:00 2001 From: jessebot Date: Sun, 24 Mar 2024 12:03:22 +0100 Subject: [PATCH 09/40] try to fix mqtt --- charts/netmaker/templates/ingress-mqtt.yaml | 2 -- 1 file changed, 2 deletions(-) diff --git a/charts/netmaker/templates/ingress-mqtt.yaml b/charts/netmaker/templates/ingress-mqtt.yaml index 495b7bc..a730032 100644 --- a/charts/netmaker/templates/ingress-mqtt.yaml +++ b/charts/netmaker/templates/ingress-mqtt.yaml @@ -66,5 +66,3 @@ spec: servicePort: {{ $mqSvcPort }} {{- end }} {{- end }} -{{- end }} -{{- end }} From 5d01ba2634d45dd1b4a93fc4e94c21266bf5994e Mon Sep 17 00:00:00 2001 From: jessebot Date: Sun, 24 Mar 2024 12:08:41 +0100 Subject: [PATCH 10/40] fix mqtt ingress to be less complicated --- charts/netmaker/templates/ingress-mqtt.yaml | 20 +++----------------- 1 file changed, 3 insertions(+), 17 deletions(-) diff --git a/charts/netmaker/templates/ingress-mqtt.yaml b/charts/netmaker/templates/ingress-mqtt.yaml index a730032..a0f9f00 100644 --- a/charts/netmaker/templates/ingress-mqtt.yaml +++ b/charts/netmaker/templates/ingress-mqtt.yaml @@ -1,25 +1,11 @@ {{- if .Values.ingress.enabled -}} {{- $fullName := include "netmaker.fullname" . -}} -{{- $fullUIName := printf "%s-%s" $fullName "ui" -}} -{{- $fullRESTName := printf "%s-%s" $fullName "rest" -}} {{- $fullMQName := printf "%s-%s" $fullName "mqtt" -}} -{{- $uiSvcPort := .Values.service.uiPort -}} -{{- $restSvcPort := .Values.service.restPort -}} {{- $mqSvcPort := 8883 -}} {{- $classname := required "A valid .Values.ingress.className entry required! Please set this to your ingress class (nginx, traefik)" .Values.ingress.className}} -{{- if and .Values.ingress.className (not (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion)) }} - {{- if not (hasKey .Values.ingress.annotations "kubernetes.io/ingress.class") }} - {{- $_ := set .Values.ingress.annotations "kubernetes.io/ingress.class" .Values.ingress.className}} - {{- end }} -{{- end }} + {{- if or (eq .Values.ingress.className "nginx") (eq .Values.ingress.className "public") }} -{{- if semverCompare ">=1.19-0" .Capabilities.KubeVersion.GitVersion -}} apiVersion: networking.k8s.io/v1 -{{- else if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}} -apiVersion: networking.k8s.io/v1beta1 -{{- else -}} -apiVersion: extensions/v1beta1 -{{- end }} kind: Ingress metadata: name: {{ $fullMQName }} @@ -38,9 +24,7 @@ metadata: {{- end }} {{- end }} spec: - {{- if and .Values.ingress.className (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion) }} ingressClassName: {{ required "A valid .Values.ingress.className entry required!" .Values.ingress.className}} - {{- end }} {{- if .Values.ingress.tls.enabled }} tls: - hosts: @@ -66,3 +50,5 @@ spec: servicePort: {{ $mqSvcPort }} {{- end }} {{- end }} + +{{- end }} From 58f295f09ec692d3f51a64825373196e38ab4898 Mon Sep 17 00:00:00 2001 From: jessebot Date: Sun, 24 Mar 2024 12:10:03 +0100 Subject: [PATCH 11/40] fix ingress rest ot be less complicated --- charts/netmaker/templates/ingress-rest.yaml | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/charts/netmaker/templates/ingress-rest.yaml b/charts/netmaker/templates/ingress-rest.yaml index 04dbb94..415b8a2 100644 --- a/charts/netmaker/templates/ingress-rest.yaml +++ b/charts/netmaker/templates/ingress-rest.yaml @@ -1,24 +1,14 @@ {{- if .Values.ingress.enabled -}} {{- $fullName := include "netmaker.fullname" . -}} -{{- $fullUIName := printf "%s-%s" $fullName "ui" -}} {{- $fullRESTName := printf "%s-%s" $fullName "rest" -}} -{{- $fullMQName := printf "%s-%s" $fullName "mqtt" -}} -{{- $uiSvcPort := .Values.service.uiPort -}} {{- $restSvcPort := .Values.service.restPort -}} -{{- $mqSvcPort := 8883 -}} {{- $classname := required "A valid .Values.ingress.className entry required! Please set this to your ingress class (nginx, traefik)" .Values.ingress.className}} {{- if and .Values.ingress.className (not (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion)) }} {{- if not (hasKey .Values.ingress.annotations "kubernetes.io/ingress.class") }} {{- $_ := set .Values.ingress.annotations "kubernetes.io/ingress.class" .Values.ingress.className}} {{- end }} {{- end }} -{{- if semverCompare ">=1.19-0" .Capabilities.KubeVersion.GitVersion -}} apiVersion: networking.k8s.io/v1 -{{- else if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}} -apiVersion: networking.k8s.io/v1beta1 -{{- else -}} -apiVersion: extensions/v1beta1 -{{- end }} kind: Ingress metadata: name: {{ $fullRESTName }} @@ -44,10 +34,8 @@ metadata: {{- end }} spec: {{- if (not (eq .Values.ingress.className "traefik")) }} - {{- if and .Values.ingress.className (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion) }} ingressClassName: {{ required "A valid .Values.ingress.className entry required!" .Values.ingress.className}} {{- end }} - {{- end }} {{- if .Values.ingress.tls.enabled }} tls: - hosts: From bf1112b0ca065143eea1bb22e3f8cbfa8db5c762 Mon Sep 17 00:00:00 2001 From: jessebot Date: Sun, 24 Mar 2024 12:52:16 +0100 Subject: [PATCH 12/40] base64encode secret data --- charts/netmaker/templates/secrets.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/charts/netmaker/templates/secrets.yaml b/charts/netmaker/templates/secrets.yaml index a729ba1..b305b5d 100644 --- a/charts/netmaker/templates/secrets.yaml +++ b/charts/netmaker/templates/secrets.yaml @@ -8,10 +8,10 @@ metadata: type: Opaque data: {{- if not .Values.postgresql.auth.existingSecret }} - SQL_PASS: "{{ include "netmaker.database.password" . }}" + SQL_PASS: "{{ include "netmaker.database.password" . | b64enc | quote }}" {{- end }} {{- if not .Values.mq.existingSecret }} - MQ_ADMIN_PASSWORD: {{ .Values.mq.password }} - MQ_PASSWORD: {{ .Values.mq.password }} + MQ_ADMIN_PASSWORD: {{ .Values.mq.password | b64enc | quote }} + MQ_PASSWORD: {{ .Values.mq.password | b64enc | quote }} {{- end }} - MASTER_KEY: {{ include "netmaker.masterKey" . }} + MASTER_KEY: {{ include "netmaker.masterKey" . | b64enc | quote }} From 2458021fd56bb2e8cae6ea825c2907829a6df6be Mon Sep 17 00:00:00 2001 From: jessebot Date: Sun, 24 Mar 2024 13:03:37 +0100 Subject: [PATCH 13/40] fix secret quoting and remove cruft from ingress files for old k8s versions (before 1.19) --- charts/netmaker/templates/ingress-rest.yaml | 5 ----- charts/netmaker/templates/ingress-route.yaml | 15 +-------------- charts/netmaker/templates/secrets.yaml | 2 +- 3 files changed, 2 insertions(+), 20 deletions(-) diff --git a/charts/netmaker/templates/ingress-rest.yaml b/charts/netmaker/templates/ingress-rest.yaml index 415b8a2..a700e6d 100644 --- a/charts/netmaker/templates/ingress-rest.yaml +++ b/charts/netmaker/templates/ingress-rest.yaml @@ -3,11 +3,6 @@ {{- $fullRESTName := printf "%s-%s" $fullName "rest" -}} {{- $restSvcPort := .Values.service.restPort -}} {{- $classname := required "A valid .Values.ingress.className entry required! Please set this to your ingress class (nginx, traefik)" .Values.ingress.className}} -{{- if and .Values.ingress.className (not (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion)) }} - {{- if not (hasKey .Values.ingress.annotations "kubernetes.io/ingress.class") }} - {{- $_ := set .Values.ingress.annotations "kubernetes.io/ingress.class" .Values.ingress.className}} - {{- end }} -{{- end }} apiVersion: networking.k8s.io/v1 kind: Ingress metadata: diff --git a/charts/netmaker/templates/ingress-route.yaml b/charts/netmaker/templates/ingress-route.yaml index 61c2a39..d553420 100644 --- a/charts/netmaker/templates/ingress-route.yaml +++ b/charts/netmaker/templates/ingress-route.yaml @@ -1,19 +1,7 @@ -{{- if .Values.ingress.enabled -}} +{{- if and .Values.ingress.enabled (eq .Values.ingress.className "traefik") -}} {{- $fullName := include "netmaker.fullname" . -}} -{{- $fullUIName := printf "%s-%s" $fullName "ui" -}} -{{- $fullRESTName := printf "%s-%s" $fullName "rest" -}} {{- $fullMQName := printf "%s-%s" $fullName "mqtt" -}} -{{- $uiSvcPort := .Values.service.uiPort -}} -{{- $restSvcPort := .Values.service.restPort -}} {{- $mqSvcPort := 8883 -}} -{{- $classname := required "A valid .Values.ingress.className entry required! Please set this to your ingress class (nginx, traefik)" .Values.ingress.className}} -{{- if and .Values.ingress.className (not (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion)) }} - {{- if not (hasKey .Values.ingress.annotations "kubernetes.io/ingress.class") }} - {{- $_ := set .Values.ingress.annotations "kubernetes.io/ingress.class" .Values.ingress.className}} - {{- end }} -{{- end }} -{{- if eq .Values.ingress.className "traefik" }} ---- apiVersion: traefik.containo.us/v1alpha1 kind: IngressRouteTCP metadata: @@ -34,4 +22,3 @@ spec: domains: - main: {{ .Values.ingress.hostPrefix.mq }}{{ .Values.baseDomain }} {{- end }} -{{- end }} diff --git a/charts/netmaker/templates/secrets.yaml b/charts/netmaker/templates/secrets.yaml index b305b5d..2cb6766 100644 --- a/charts/netmaker/templates/secrets.yaml +++ b/charts/netmaker/templates/secrets.yaml @@ -8,7 +8,7 @@ metadata: type: Opaque data: {{- if not .Values.postgresql.auth.existingSecret }} - SQL_PASS: "{{ include "netmaker.database.password" . | b64enc | quote }}" + SQL_PASS: {{ include "netmaker.database.password" . | b64enc | quote }} {{- end }} {{- if not .Values.mq.existingSecret }} MQ_ADMIN_PASSWORD: {{ .Values.mq.password | b64enc | quote }} From 7af8f066657d9ffb7120161a1943c82b7187185b Mon Sep 17 00:00:00 2001 From: jessebot Date: Sun, 24 Mar 2024 13:04:16 +0100 Subject: [PATCH 14/40] adding max and jesse maintainers --- charts/netmaker/README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/charts/netmaker/README.md b/charts/netmaker/README.md index 65d644e..815ad1d 100644 --- a/charts/netmaker/README.md +++ b/charts/netmaker/README.md @@ -4,6 +4,13 @@ A Helm chart to run HA Netmaker on Kubernetes +## Maintainers + +| Name | Email | Url | +| ---- | ------ | --- | +| jessebot | | | +| cloudymax | | | + ## Requirements | Repository | Name | Version | From 5bb3c424a3210fd1391a0065f20e2912f0f96469 Mon Sep 17 00:00:00 2001 From: jessebot Date: Sun, 24 Mar 2024 13:05:03 +0100 Subject: [PATCH 15/40] add maintainers to the docs --- charts/netmaker/README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/charts/netmaker/README.md b/charts/netmaker/README.md index 815ad1d..9a6e168 100644 --- a/charts/netmaker/README.md +++ b/charts/netmaker/README.md @@ -6,10 +6,10 @@ A Helm chart to run HA Netmaker on Kubernetes ## Maintainers -| Name | Email | Url | -| ---- | ------ | --- | -| jessebot | | | -| cloudymax | | | +| Name | Url | +| ---- | --- | +| jessebot | | +| cloudymax | | ## Requirements From fe0ca53e7b088c6ea40ec56338cca35d292a747d Mon Sep 17 00:00:00 2001 From: jessebot Date: Sun, 24 Mar 2024 14:18:27 +0100 Subject: [PATCH 16/40] clean up persistent volumes --- charts/netmaker/README.md | 27 ++++++++++--------- charts/netmaker/templates/coredns-pvc.yaml | 12 +++++++++ charts/netmaker/templates/coredns.yaml | 18 +++---------- charts/netmaker/templates/mq-deployment.yaml | 6 +---- charts/netmaker/templates/mq-pvc.yaml | 9 +++---- .../templates/netmaker-statefulset.yaml | 14 +++++----- charts/netmaker/values.yaml | 25 +++++++---------- 7 files changed, 51 insertions(+), 60 deletions(-) create mode 100644 charts/netmaker/templates/coredns-pvc.yaml diff --git a/charts/netmaker/README.md b/charts/netmaker/README.md index 9a6e168..f1e1588 100644 --- a/charts/netmaker/README.md +++ b/charts/netmaker/README.md @@ -6,10 +6,10 @@ A Helm chart to run HA Netmaker on Kubernetes ## Maintainers -| Name | Url | -| ---- | --- | -| jessebot | | -| cloudymax | | +| Name | Email | Url | +| ---- | ------ | --- | +| jessebot | | | +| cloudymax | | | ## Requirements @@ -22,10 +22,11 @@ A Helm chart to run HA Netmaker on Kubernetes | Key | Type | Default | Description | |-----|------|---------|-------------| | baseDomain | string | `"example.com"` | | -| dns.RWX.storageClassName | string | `""` | | +| dns.accessMode | string | `"ReadWriteOnce"` | | | dns.enabled | bool | `false` | whether or not to deploy coredns | -| dns.existingClaim | string | `""` | | -| dns.storageSize | string | `"128Mi"` | | +| dns.existingClaim | string | `""` | existingClaim, if not set, defaults to HELM.RELEASE.NAME-dns | +| dns.storage | string | `"1Gi"` | | +| dns.storageClassName | string | `""` | | | externalDatabase.database | string | `"netmaker"` | postgress db | | externalDatabase.existingSecret | string | `""` | | | externalDatabase.host | string | `"external.postgres.url"` | postgres host | @@ -54,14 +55,15 @@ A Helm chart to run HA Netmaker on Kubernetes | ingress.hostPrefix.ui | string | `"dashboard."` | ui route subdomain | | ingress.tls.enabled | bool | `false` | | | ingress.tls.issuerName | string | `"letsencrypt-prod"` | | -| mq.RWX.storageClassName | string | `""` | | -| mq.existingClaim | string | `""` | | -| mq.existingSecret | string | `""` | | +| mq.accessMode | string | `"ReadWriteMany"` | | +| mq.existingClaim | string | `""` | name of existing PVC claim to use. if set, storageClassName is ignored | +| mq.existingSecret | string | `""` | name of an existing secret to use for mq password. If set, ignores mq.password | | mq.password | string | `"3yyerWGdds43yegGR"` | | | mq.replicas | int | `1` | how many MQTT replicas to create change to 2 or more and set singlenode to false if needed | -| mq.secretKey | string | `""` | | +| mq.secretKey | string | `""` | name of key in existing secret to grab password from. If set, ignores mq.password | | mq.singlenode | bool | `true` | | -| mq.storageSize | string | `"128Mi"` | | +| mq.storage | string | `"128Mi"` | | +| mq.storageClassName | string | `""` | | | mq.username | string | `"netmaker"` | | | nameOverride | string | `""` | override the name for netmaker objects | | oauth.enabled | bool | `false` | | @@ -71,7 +73,6 @@ A Helm chart to run HA Netmaker on Kubernetes | oauth.secretKeys.clientSecret | string | `nil` | | | oauth.secretKeys.frontendURL | string | `nil` | | | oauth.secretKeys.issuer | string | `nil` | | -| persistence.sharedData.existingClaim | string | `""` | | | podAnnotations | object | `{}` | pod annotations to add | | podSecurityContext | object | `{}` | pod security contect to add | | postgresql.auth.database | string | `"netmaker"` | | diff --git a/charts/netmaker/templates/coredns-pvc.yaml b/charts/netmaker/templates/coredns-pvc.yaml new file mode 100644 index 0000000..7b9b9cc --- /dev/null +++ b/charts/netmaker/templates/coredns-pvc.yaml @@ -0,0 +1,12 @@ +{{- if and .Values.dns.enabled (not .Values.dns.existingClaim) -}} +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: {{ include "netmaker.fullname" . }}-dns +spec: + storageClassName: {{ required "A valid .Values.dns.storageClassName entry required! Specify an available storage class." .Values.dns.storageClassName}} + accessModes: [{{ .Values.dns.accessMode }}] + resources: + requests: + storage: {{ .Values.dns.storage }} +{{- end }} diff --git a/charts/netmaker/templates/coredns.yaml b/charts/netmaker/templates/coredns.yaml index f35c0cf..1830642 100644 --- a/charts/netmaker/templates/coredns.yaml +++ b/charts/netmaker/templates/coredns.yaml @@ -31,7 +31,7 @@ spec: protocol: TCP volumeMounts: - mountPath: /root/dnsconfig - name: {{ include "netmaker.fullname" . }}-dns-pvc + name: dns-pvc readOnly: true securityContext: allowPrivilegeEscalation: false @@ -45,12 +45,12 @@ spec: nameservers: - 127.0.0.1 volumes: - - name: {{ include "netmaker.fullname" . }}-dns-pvc + - name: dns-pvc persistentVolumeClaim: {{- if .Values.dns.existingClaim }} claimName: {{ .Values.dns.existingClaim }} {{- else }} - claimName: {{ include "netmaker.fullname" . }}-dns-pvc + claimName: {{ include "netmaker.fullname" . }}-dns {{- end }} --- apiVersion: v1 @@ -74,16 +74,4 @@ spec: sessionAffinity: None type: ClusterIP clusterIP: {{ required "A valid .Values.dns.clusterIP entry required! Choose an IP from your k8s service IP CIDR" .Values.dns.clusterIP}} ---- -apiVersion: v1 -kind: PersistentVolumeClaim -metadata: - name: {{ include "netmaker.fullname" . }}-dns-pvc -spec: - storageClassName: {{ required "A valid .Values.dns.RWX.storageClassName entry required! Specify an available RWX storage class." .Values.dns.RWX.storageClassName}} - accessModes: - - ReadWriteMany - resources: - requests: - storage: {{ .Values.dns.storageSize }} {{- end }} diff --git a/charts/netmaker/templates/mq-deployment.yaml b/charts/netmaker/templates/mq-deployment.yaml index 32cdbb8..885e895 100644 --- a/charts/netmaker/templates/mq-deployment.yaml +++ b/charts/netmaker/templates/mq-deployment.yaml @@ -88,9 +88,5 @@ spec: {{- if .Values.mq.existingClaim }} claimName: {{ .Values.mq.existingClaim }} {{- else }} - {{- if .Values.persistence.sharedData.existingClaim }} - claimName: {{ .Values.persistence.sharedData.existingClaim }} - {{- else }} - claimName: {{ include "netmaker.fullname" . }}-shared-data-pvc - {{- end }} + claimName: {{ include "netmaker.fullname" . }}-shared-data {{- end }} diff --git a/charts/netmaker/templates/mq-pvc.yaml b/charts/netmaker/templates/mq-pvc.yaml index a5c3227..f87f67d 100644 --- a/charts/netmaker/templates/mq-pvc.yaml +++ b/charts/netmaker/templates/mq-pvc.yaml @@ -3,12 +3,11 @@ kind: PersistentVolumeClaim apiVersion: v1 metadata: - name: {{ include "netmaker.fullname" . }}-shared-data-pvc + name: {{ include "netmaker.fullname" . }}-shared-data spec: - storageClassName: {{ .Values.mq.RWX.storageClassName }} - accessModes: - - ReadWriteMany + storageClassName: {{ .Values.mq.storageClassName }} + accessModes: [{{ .Values.mq.accessMode }}] resources: requests: - storage: {{ .Values.mq.storageSize }} + storage: {{ .Values.mq.storage }} {{- end }} diff --git a/charts/netmaker/templates/netmaker-statefulset.yaml b/charts/netmaker/templates/netmaker-statefulset.yaml index 0a1ffed..15b3e43 100644 --- a/charts/netmaker/templates/netmaker-statefulset.yaml +++ b/charts/netmaker/templates/netmaker-statefulset.yaml @@ -125,23 +125,23 @@ spec: - mountPath: /etc/netmaker/ name: shared-data {{- if .Values.dns.enabled }} - - name: {{ include "netmaker.fullname" . }}-dns-pvc + - name: dns-pvc mountPath: /root/config/dnsconfig {{- end }} volumes: - name: shared-data persistentVolumeClaim: - {{- if .Values.persistence.sharedData.existingClaim }} - claimName: {{ .Values.persistence.sharedData.existingClaim }} + {{- if .Values.mq.existingClaim }} + claimName: {{ .Values.mq.existingClaim }} {{- else }} - claimName: {{ include "netmaker.fullname" . }}-shared-data-pvc + claimName: {{ include "netmaker.fullname" . }}-shared-data {{- end }} {{- if .Values.dns.enabled }} - - name: {{ include "netmaker.fullname" . }}-dns-pvc + - name: dns-pvc persistentVolumeClaim: {{- if .Values.dns.existingClaim }} claimName: {{ .Values.dns.existingClaim }} {{- else }} - claimName: {{ include "netmaker.fullname" . }}-dns-pvc + claimName: {{ include "netmaker.fullname" . }}-dns {{- end }} - {{- end }} + {{- end }} {{/* end if dns.enabled */}} diff --git a/charts/netmaker/values.yaml b/charts/netmaker/values.yaml index 3eac2d5..46d3c24 100644 --- a/charts/netmaker/values.yaml +++ b/charts/netmaker/values.yaml @@ -34,11 +34,6 @@ podAnnotations: {} podSecurityContext: {} # fsGroup: 2000 -persistence: - sharedData: - # if not specified, this will create HELM.RELEASE.NAME-shared-data-pvc by default - existingClaim: "" - ui: # -- how many UI replicas to create replicas: 1 @@ -48,26 +43,26 @@ mq: # change to 2 or more and set singlenode to false if needed replicas: 1 singlenode: true - storageSize: 128Mi username: netmaker password: 3yyerWGdds43yegGR - # name of an existing secret to use for mq password. If set, ignores mq.password + # -- name of an existing secret to use for mq password. If set, ignores mq.password existingSecret: '' - # name of key in existing secret to grab password from. If set, ignores mq.password + # -- name of key in existing secret to grab password from. If set, ignores mq.password secretKey: '' - # name of existing PVC claim to use. if set, RWX.storageClassName is ignored + # -- name of existing PVC claim to use. if set, storageClassName is ignored existingClaim: "" - RWX: - storageClassName: "" + accessMode: "ReadWriteMany" + storageClassName: "" + storage: 128Mi dns: # -- whether or not to deploy coredns enabled: false - # existingClaim, if not set, defaults to HELM.RELEASE.NAME-dns-pvc + # -- existingClaim, if not set, defaults to HELM.RELEASE.NAME-dns existingClaim: '' - storageSize: 128Mi - RWX: - storageClassName: "" + storage: 1Gi + storageClassName: "" + accessMode: ReadWriteOnce setIpForwarding: enabled: true From e72586868c8d3772139f63ad545548dd12b29a9f Mon Sep 17 00:00:00 2001 From: Max! Date: Sun, 24 Mar 2024 14:41:42 +0100 Subject: [PATCH 17/40] disable conficted affinity rule --- .../templates/netmaker-statefulset.yaml | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/charts/netmaker/templates/netmaker-statefulset.yaml b/charts/netmaker/templates/netmaker-statefulset.yaml index 15b3e43..da4edc9 100644 --- a/charts/netmaker/templates/netmaker-statefulset.yaml +++ b/charts/netmaker/templates/netmaker-statefulset.yaml @@ -24,16 +24,16 @@ spec: securityContext: privileged: true dnsPolicy: ClusterFirstWithHostNet - affinity: - podAntiAffinity: - requiredDuringSchedulingIgnoredDuringExecution: - - labelSelector: - matchExpressions: - - key: app - operator: In - values: - - {{ include "netmaker.fullname" . }} - topologyKey: "kubernetes.io/hostname" + #affinity: + # podAntiAffinity: + # requiredDuringSchedulingIgnoredDuringExecution: + # - labelSelector: + # matchExpressions: + # - key: app + # operator: In + # values: + # - {{ include "netmaker.fullname" . }} + # topologyKey: "kubernetes.io/hostname" containers: - env: - name: NODE_ID From 84ee4f4db413cfbba1553c497beaeeeedb42e82b Mon Sep 17 00:00:00 2001 From: jessebot Date: Sun, 24 Mar 2024 14:53:52 +0100 Subject: [PATCH 18/40] try testing on k3s with longhorn instead --- .github/workflows/ci-helm-lint-test.yml | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci-helm-lint-test.yml b/.github/workflows/ci-helm-lint-test.yml index 0c068b8..e8af628 100644 --- a/.github/workflows/ci-helm-lint-test.yml +++ b/.github/workflows/ci-helm-lint-test.yml @@ -41,11 +41,19 @@ jobs: if: steps.list-changed.outputs.changed == 'true' run: ct lint --target-branch ${{ github.event.repository.default_branch }} - - name: Create kind cluster - uses: helm/kind-action@v1.9.0 - if: steps.list-changed.outputs.changed == 'true' + - name: Create K3s cluster + uses: debianmaster/actions-k3s@master + id: k3s + with: + version: 'latest' + + - name: Install longhorn + run: | + helm repo add longhorn https://charts.longhorn.io + helm repo update + helm install longhorn longhorn/longhorn --namespace longhorn-system --create-namespace - name: Run chart-testing (install) id: install if: steps.list-changed.outputs.changed == 'true' - run: ct install --target-branch ${{ github.event.repository.default_branch }} + run: ct install --target-branch ${{ github.event.repository.default_branch }} --helm-extra-set-args "--set=mq.storageClassName=longhorn --set=dns.storageClassName=longhorn" From 3e61df891e07577d706b96eefc49b10b2a2c5bd0 Mon Sep 17 00:00:00 2001 From: jessebot Date: Sun, 24 Mar 2024 15:02:39 +0100 Subject: [PATCH 19/40] rename ingresses and ui files to make more clear --- charts/netmaker/templates/{ingress-mqtt.yaml => mq-ingress.yaml} | 0 .../templates/{netmaker-ui-deployment.yaml => ui-deployment.yaml} | 0 charts/netmaker/templates/{ingress-ui.yaml => ui-ingress.yaml} | 0 3 files changed, 0 insertions(+), 0 deletions(-) rename charts/netmaker/templates/{ingress-mqtt.yaml => mq-ingress.yaml} (100%) rename charts/netmaker/templates/{netmaker-ui-deployment.yaml => ui-deployment.yaml} (100%) rename charts/netmaker/templates/{ingress-ui.yaml => ui-ingress.yaml} (100%) diff --git a/charts/netmaker/templates/ingress-mqtt.yaml b/charts/netmaker/templates/mq-ingress.yaml similarity index 100% rename from charts/netmaker/templates/ingress-mqtt.yaml rename to charts/netmaker/templates/mq-ingress.yaml diff --git a/charts/netmaker/templates/netmaker-ui-deployment.yaml b/charts/netmaker/templates/ui-deployment.yaml similarity index 100% rename from charts/netmaker/templates/netmaker-ui-deployment.yaml rename to charts/netmaker/templates/ui-deployment.yaml diff --git a/charts/netmaker/templates/ingress-ui.yaml b/charts/netmaker/templates/ui-ingress.yaml similarity index 100% rename from charts/netmaker/templates/ingress-ui.yaml rename to charts/netmaker/templates/ui-ingress.yaml From e0302dc884dc455ac9e97433d5f1536a9354f5f6 Mon Sep 17 00:00:00 2001 From: jessebot Date: Sun, 24 Mar 2024 15:07:32 +0100 Subject: [PATCH 20/40] change from longhorn pvc class to standard --- .github/workflows/ci-helm-lint-test.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci-helm-lint-test.yml b/.github/workflows/ci-helm-lint-test.yml index e8af628..011d32a 100644 --- a/.github/workflows/ci-helm-lint-test.yml +++ b/.github/workflows/ci-helm-lint-test.yml @@ -56,4 +56,6 @@ jobs: - name: Run chart-testing (install) id: install if: steps.list-changed.outputs.changed == 'true' - run: ct install --target-branch ${{ github.event.repository.default_branch }} --helm-extra-set-args "--set=mq.storageClassName=longhorn --set=dns.storageClassName=longhorn" + # install the chart using longhorn pvcs + run: | + ct install --target-branch ${{ github.event.repository.default_branch }} --helm-extra-set-args "--set=mq.storageClassName=standard --set=dns.storageClassName=standard" From 967c00c589f9b463740bd9a51f74e7fa04e43c42 Mon Sep 17 00:00:00 2001 From: jessebot Date: Sun, 24 Mar 2024 15:21:24 +0100 Subject: [PATCH 21/40] switch back to longhorn storage class, but this time install with kubectl vs helm --- .github/workflows/ci-helm-lint-test.yml | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci-helm-lint-test.yml b/.github/workflows/ci-helm-lint-test.yml index 011d32a..3ef97d2 100644 --- a/.github/workflows/ci-helm-lint-test.yml +++ b/.github/workflows/ci-helm-lint-test.yml @@ -49,13 +49,11 @@ jobs: - name: Install longhorn run: | - helm repo add longhorn https://charts.longhorn.io - helm repo update - helm install longhorn longhorn/longhorn --namespace longhorn-system --create-namespace + kubectl apply -f https://raw.githubusercontent.com/longhorn/longhorn/v1.6.0/deploy/longhorn.yaml - name: Run chart-testing (install) id: install if: steps.list-changed.outputs.changed == 'true' # install the chart using longhorn pvcs run: | - ct install --target-branch ${{ github.event.repository.default_branch }} --helm-extra-set-args "--set=mq.storageClassName=standard --set=dns.storageClassName=standard" + ct install --target-branch ${{ github.event.repository.default_branch }} --helm-extra-set-args "--set=mq.storageClassName=longhorn --set=dns.storageClassName=longhorn" From 0c2533bba2c952b5777a686e77a9fcbdf6790fae Mon Sep 17 00:00:00 2001 From: jessebot Date: Sun, 24 Mar 2024 15:24:03 +0100 Subject: [PATCH 22/40] add most basic testing locally script --- test-locally.sh | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 test-locally.sh diff --git a/test-locally.sh b/test-locally.sh new file mode 100644 index 0000000..512a89e --- /dev/null +++ b/test-locally.sh @@ -0,0 +1,3 @@ +kind create cluster +kubectl apply -f https://raw.githubusercontent.com/longhorn/longhorn/v1.6.0/deploy/longhorn.yaml +ct install --target-branch main --helm-extra-set-args "--set=mq.storageClassName=longhorn --set=dns.storageClassName=longhorn" From 32e752c796dabdcdec67906db7674668b54ed057 Mon Sep 17 00:00:00 2001 From: jessebot Date: Sun, 24 Mar 2024 15:31:36 +0100 Subject: [PATCH 23/40] remove weird default affinity settings in this chart --- charts/netmaker/README.md | 2 ++ charts/netmaker/templates/mq-deployment.yaml | 15 ++++++--------- .../netmaker/templates/netmaker-statefulset.yaml | 10 ---------- charts/netmaker/values.yaml | 12 ++++++++++++ 4 files changed, 20 insertions(+), 19 deletions(-) diff --git a/charts/netmaker/README.md b/charts/netmaker/README.md index f1e1588..15019a9 100644 --- a/charts/netmaker/README.md +++ b/charts/netmaker/README.md @@ -56,6 +56,7 @@ A Helm chart to run HA Netmaker on Kubernetes | ingress.tls.enabled | bool | `false` | | | ingress.tls.issuerName | string | `"letsencrypt-prod"` | | | mq.accessMode | string | `"ReadWriteMany"` | | +| mq.affinity | object | `{}` | optional affinity settings for mqtt | | mq.existingClaim | string | `""` | name of existing PVC claim to use. if set, storageClassName is ignored | | mq.existingSecret | string | `""` | name of an existing secret to use for mq password. If set, ignores mq.password | | mq.password | string | `"3yyerWGdds43yegGR"` | | @@ -64,6 +65,7 @@ A Helm chart to run HA Netmaker on Kubernetes | mq.singlenode | bool | `true` | | | mq.storage | string | `"128Mi"` | | | mq.storageClassName | string | `""` | | +| mq.tolerations | object | `{}` | optional tolerations settings for mqtt | | mq.username | string | `"netmaker"` | | | nameOverride | string | `""` | override the name for netmaker objects | | oauth.enabled | bool | `false` | | diff --git a/charts/netmaker/templates/mq-deployment.yaml b/charts/netmaker/templates/mq-deployment.yaml index 885e895..d9fa7cc 100644 --- a/charts/netmaker/templates/mq-deployment.yaml +++ b/charts/netmaker/templates/mq-deployment.yaml @@ -16,16 +16,13 @@ spec: labels: app: {{ include "netmaker.fullname" . }}-mqtt spec: - {{- if .Values.mq.singlenode }} + {{- with .Values.mq.affinity }} affinity: - nodeAffinity: - requiredDuringSchedulingIgnoredDuringExecution: - nodeSelectorTerms: - - matchExpressions: - - key: mqhost - operator: In - values: - - "true" + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.mq.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} {{- end }} containers: - env: diff --git a/charts/netmaker/templates/netmaker-statefulset.yaml b/charts/netmaker/templates/netmaker-statefulset.yaml index da4edc9..076ef72 100644 --- a/charts/netmaker/templates/netmaker-statefulset.yaml +++ b/charts/netmaker/templates/netmaker-statefulset.yaml @@ -24,16 +24,6 @@ spec: securityContext: privileged: true dnsPolicy: ClusterFirstWithHostNet - #affinity: - # podAntiAffinity: - # requiredDuringSchedulingIgnoredDuringExecution: - # - labelSelector: - # matchExpressions: - # - key: app - # operator: In - # values: - # - {{ include "netmaker.fullname" . }} - # topologyKey: "kubernetes.io/hostname" containers: - env: - name: NODE_ID diff --git a/charts/netmaker/values.yaml b/charts/netmaker/values.yaml index 46d3c24..f9d7739 100644 --- a/charts/netmaker/values.yaml +++ b/charts/netmaker/values.yaml @@ -43,6 +43,18 @@ mq: # change to 2 or more and set singlenode to false if needed replicas: 1 singlenode: true + # -- optional tolerations settings for mqtt + tolerations: {} + # -- optional affinity settings for mqtt + affinity: {} + # nodeAffinity: + # requiredDuringSchedulingIgnoredDuringExecution: + # nodeSelectorTerms: + # - matchExpressions: + # - key: mqhost + # operator: In + # values: + # - "true" username: netmaker password: 3yyerWGdds43yegGR # -- name of an existing secret to use for mq password. If set, ignores mq.password From 11a76ae3f96aa694e43ae5bea8c9b1d35a27d8c5 Mon Sep 17 00:00:00 2001 From: Max! Date: Sun, 24 Mar 2024 17:06:37 +0100 Subject: [PATCH 24/40] add missing env vars for oidc --- charts/netmaker/templates/netmaker-statefulset.yaml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/charts/netmaker/templates/netmaker-statefulset.yaml b/charts/netmaker/templates/netmaker-statefulset.yaml index 076ef72..124ca29 100644 --- a/charts/netmaker/templates/netmaker-statefulset.yaml +++ b/charts/netmaker/templates/netmaker-statefulset.yaml @@ -59,6 +59,16 @@ spec: key: {{ .Values.mq.secretKey }} {{- end }} {{- if .Values.oauth.enabled }} + - name: SERVER_HTTP_HOST + valueFrom: + secretKeyRef: + name: {{ .Values.oauth.existingSecret }} + key: {{ .Values.oauth.secretKeys.serverHttpHost }} + - name: AUTH_PROVIDER + valueFrom: + secretKeyRef: + name: {{ .Values.oauth.existingSecret }} + key: {{ .Values.oauth.secretKeys.authProvider }} - name: CLIENT_ID valueFrom: secretKeyRef: From 9d2e4e5a2544ebf39e4003cd9ea72749005bf323 Mon Sep 17 00:00:00 2001 From: jessebot Date: Sun, 24 Mar 2024 17:06:35 +0100 Subject: [PATCH 25/40] add affinity back to the statefulset, but leave blank --- charts/netmaker/README.md | 2 ++ .../templates/netmaker-statefulset.yaml | 8 ++++++++ charts/netmaker/values.yaml | 17 +++++++++++++++++ 3 files changed, 27 insertions(+) diff --git a/charts/netmaker/README.md b/charts/netmaker/README.md index 15019a9..e60f34c 100644 --- a/charts/netmaker/README.md +++ b/charts/netmaker/README.md @@ -21,6 +21,7 @@ A Helm chart to run HA Netmaker on Kubernetes | Key | Type | Default | Description | |-----|------|---------|-------------| +| affinity | object | `{}` | optional affinity settings for netmaker | | baseDomain | string | `"example.com"` | | | dns.accessMode | string | `"ReadWriteOnce"` | | | dns.enabled | bool | `false` | whether or not to deploy coredns | @@ -94,6 +95,7 @@ A Helm chart to run HA Netmaker on Kubernetes | serviceAccount.create | bool | `true` | Specifies whether a service account should be created | | serviceAccount.name | string | `""` | Name of SA to use. If not set and create is true, a name is generated using the fullname template | | setIpForwarding.enabled | bool | `true` | | +| tolerations | object | `{}` | optional tolerations settings for netmaker | | ui.replicas | int | `1` | how many UI replicas to create | | wireguard.enabled | bool | `true` | whether or not to use WireGuard on server | | wireguard.kernel | bool | `false` | whether or not to use Kernel WG (should be false unless WireGuard is installed on hosts). | diff --git a/charts/netmaker/templates/netmaker-statefulset.yaml b/charts/netmaker/templates/netmaker-statefulset.yaml index 124ca29..089c070 100644 --- a/charts/netmaker/templates/netmaker-statefulset.yaml +++ b/charts/netmaker/templates/netmaker-statefulset.yaml @@ -15,6 +15,14 @@ spec: labels: app: {{ include "netmaker.fullname" . }} spec: + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} initContainers: - name: init-sysctl image: busybox diff --git a/charts/netmaker/values.yaml b/charts/netmaker/values.yaml index f9d7739..084d801 100644 --- a/charts/netmaker/values.yaml +++ b/charts/netmaker/values.yaml @@ -19,6 +19,23 @@ nameOverride: "" # -- override the full name for netmaker objects fullnameOverride: "" +# -- optional tolerations settings for netmaker +tolerations: {} + +# -- optional affinity settings for netmaker +affinity: {} +# example affinity +# affinity: +# podAntiAffinity: +# requiredDuringSchedulingIgnoredDuringExecution: +# - labelSelector: +# matchExpressions: +# - key: app +# operator: In +# values: +# - {{ include "netmaker.fullname" . }} +# topologyKey: "kubernetes.io/hostname" + serviceAccount: # -- Specifies whether a service account should be created create: true From 2afbbb8795a1f36849b6546b6a1645b40c876364 Mon Sep 17 00:00:00 2001 From: Max! Date: Sun, 24 Mar 2024 17:25:53 +0100 Subject: [PATCH 26/40] fix name authProvider to auth_provider --- charts/netmaker/templates/netmaker-statefulset.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/charts/netmaker/templates/netmaker-statefulset.yaml b/charts/netmaker/templates/netmaker-statefulset.yaml index 089c070..ea4a321 100644 --- a/charts/netmaker/templates/netmaker-statefulset.yaml +++ b/charts/netmaker/templates/netmaker-statefulset.yaml @@ -76,7 +76,7 @@ spec: valueFrom: secretKeyRef: name: {{ .Values.oauth.existingSecret }} - key: {{ .Values.oauth.secretKeys.authProvider }} + key: {{ .Values.oauth.secretKeys.auth_provider }} - name: CLIENT_ID valueFrom: secretKeyRef: From eee4fecf41a92c98f7429b56def6e5f583236b86 Mon Sep 17 00:00:00 2001 From: jessebot Date: Sun, 24 Mar 2024 20:19:22 +0100 Subject: [PATCH 27/40] add current version of netmaker --- charts/netmaker/Chart.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/charts/netmaker/Chart.yaml b/charts/netmaker/Chart.yaml index 319d110..a13392e 100644 --- a/charts/netmaker/Chart.yaml +++ b/charts/netmaker/Chart.yaml @@ -21,7 +21,7 @@ version: 0.10.0 # incremented each time you make changes to the application. Versions are not expected to # follow Semantic Versioning. They should reflect the version the application is using. # It is recommended to use it with quotes. -appVersion: "v0.20.3" +appVersion: "v0.21.2" maintainers: - name: "jessebot" From 3cc4af15dd5e205bf586575077b553b95a147fe7 Mon Sep 17 00:00:00 2001 From: Max! Date: Sun, 24 Mar 2024 20:52:41 +0100 Subject: [PATCH 28/40] Update netmaker-statefulset.yaml --- charts/netmaker/templates/netmaker-statefulset.yaml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/charts/netmaker/templates/netmaker-statefulset.yaml b/charts/netmaker/templates/netmaker-statefulset.yaml index ea4a321..6abbcf5 100644 --- a/charts/netmaker/templates/netmaker-statefulset.yaml +++ b/charts/netmaker/templates/netmaker-statefulset.yaml @@ -67,6 +67,12 @@ spec: key: {{ .Values.mq.secretKey }} {{- end }} {{- if .Values.oauth.enabled }} + - name: SERVER_BROKER_ENDPOINT + valueFrom: + secretKeyRef: + name: {{ .Values.oauth.existingSecret }} + key: {{ .Values.oauth.secretKeys.serverBrokerEndpoint }} + - name: SERVER_HTTP_HOST valueFrom: secretKeyRef: From ddf77080549417f8c92c27f0406cb42215eacf95 Mon Sep 17 00:00:00 2001 From: jessebot Date: Sun, 24 Mar 2024 21:51:17 +0100 Subject: [PATCH 29/40] reorganize all the services and ingresses --- charts/netmaker/README.md | 72 +++++---- charts/netmaker/templates/NOTES.txt | 22 --- charts/netmaker/templates/api-ingress.yaml | 31 ++++ charts/netmaker/templates/api-service.yaml | 17 ++ charts/netmaker/templates/cert.yaml | 19 +-- charts/netmaker/templates/confgmap.yaml | 8 +- charts/netmaker/templates/ingress-rest.yaml | 58 ------- charts/netmaker/templates/mq-deployment.yaml | 135 +++++++++------- ...gress-route.yaml => mq-ingress-route.yaml} | 9 +- charts/netmaker/templates/mq-ingress.yaml | 38 +---- charts/netmaker/templates/mq-service.yaml | 2 +- .../templates/netmaker-statefulset.yaml | 2 +- charts/netmaker/templates/services.yaml | 57 ------- .../templates/tests/test-connection.yaml | 15 -- charts/netmaker/templates/ui-deployment.yaml | 14 +- charts/netmaker/templates/ui-ingress.yaml | 60 +------ charts/netmaker/templates/ui-service.yaml | 15 ++ .../netmaker/templates/wireguard-service.yaml | 25 +++ charts/netmaker/values.yaml | 150 ++++++++++-------- 19 files changed, 322 insertions(+), 427 deletions(-) delete mode 100644 charts/netmaker/templates/NOTES.txt create mode 100644 charts/netmaker/templates/api-ingress.yaml create mode 100644 charts/netmaker/templates/api-service.yaml delete mode 100644 charts/netmaker/templates/ingress-rest.yaml rename charts/netmaker/templates/{ingress-route.yaml => mq-ingress-route.yaml} (61%) delete mode 100644 charts/netmaker/templates/services.yaml delete mode 100644 charts/netmaker/templates/tests/test-connection.yaml create mode 100644 charts/netmaker/templates/ui-service.yaml create mode 100644 charts/netmaker/templates/wireguard-service.yaml diff --git a/charts/netmaker/README.md b/charts/netmaker/README.md index e60f34c..e1b2e01 100644 --- a/charts/netmaker/README.md +++ b/charts/netmaker/README.md @@ -1,6 +1,6 @@ # netmaker -![Version: 0.10.0](https://img.shields.io/badge/Version-0.10.0-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: v0.20.3](https://img.shields.io/badge/AppVersion-v0.20.3-informational?style=flat-square) +![Version: 0.10.0](https://img.shields.io/badge/Version-0.10.0-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: v0.21.2](https://img.shields.io/badge/AppVersion-v0.21.2-informational?style=flat-square) A Helm chart to run HA Netmaker on Kubernetes @@ -22,12 +22,19 @@ A Helm chart to run HA Netmaker on Kubernetes | Key | Type | Default | Description | |-----|------|---------|-------------| | affinity | object | `{}` | optional affinity settings for netmaker | -| baseDomain | string | `"example.com"` | | -| dns.accessMode | string | `"ReadWriteOnce"` | | +| api.ingress.annotations | object | `{}` | annotations for the netmaker API ingress object | +| api.ingress.className | string | `"nginx"` | api ingress className | +| api.ingress.enabled | bool | `true` | attempts to configure ingress if true | +| api.ingress.host | string | `"api.cluster.local"` | api (REST) route subdomain | +| api.ingress.tls | list | `[]` | ingress api tls list | +| api.service.port | int | `8081` | port for API service | +| api.service.targetPort | int | `8081` | targetport for API service | +| api.service.type | string | `"ClusterIP"` | type for netmaker server services | | dns.enabled | bool | `false` | whether or not to deploy coredns | -| dns.existingClaim | string | `""` | existingClaim, if not set, defaults to HELM.RELEASE.NAME-dns | -| dns.storage | string | `"1Gi"` | | -| dns.storageClassName | string | `""` | | +| dns.persistence.accessMode | string | `"ReadWriteOnce"` | | +| dns.persistence.existingClaim | string | `""` | existingClaim, if not set, defaults to HELM.RELEASE.NAME-dns | +| dns.persistence.storage | string | `"1Gi"` | | +| dns.persistence.storageClassName | string | `""` | | | externalDatabase.database | string | `"netmaker"` | postgress db | | externalDatabase.existingSecret | string | `""` | | | externalDatabase.host | string | `"external.postgres.url"` | postgres host | @@ -39,33 +46,24 @@ A Helm chart to run HA Netmaker on Kubernetes | fullnameOverride | string | `""` | override the full name for netmaker objects | | image.pullPolicy | string | `"IfNotPresent"` | Pull Policy for images | | image.repository | string | `"gravitl/netmaker"` | The image repo to pull Netmaker image from | -| ingress.annotations.base."kubernetes.io/ingress.allow-http" | string | `"false"` | annotation to generate ACME certs if available | -| ingress.annotations.mq | object | `{}` | | -| ingress.annotations.nginx."nginx.ingress.kubernetes.io/rewrite-target" | string | `"/"` | destination addr for route | -| ingress.annotations.nginx."nginx.ingress.kubernetes.io/ssl-redirect" | string | `"true"` | Redirect http to https | -| ingress.annotations.rest | object | `{}` | | -| ingress.annotations.tls."kubernetes.io/tls-acme" | string | `"true"` | use acme cert if available | -| ingress.annotations.traefik."traefik.ingress.kubernetes.io/redirect-entry-point" | string | `"https"` | Redirect to https | -| ingress.annotations.traefik."traefik.ingress.kubernetes.io/redirect-permanent" | string | `"true"` | Redirect to https permanently | -| ingress.annotations.traefik."traefik.ingress.kubernetes.io/rule-type" | string | `"PathPrefixStrip"` | rule type | -| ingress.annotations.ui | object | `{}` | | -| ingress.className | string | `"nginx"` | | -| ingress.enabled | bool | `true` | attempts to configure ingress if true | -| ingress.hostPrefix.broker | string | `"broker."` | mqtt route subdomain | -| ingress.hostPrefix.rest | string | `"api."` | api (REST) route subdomain | -| ingress.hostPrefix.ui | string | `"dashboard."` | ui route subdomain | -| ingress.tls.enabled | bool | `false` | | -| ingress.tls.issuerName | string | `"letsencrypt-prod"` | | -| mq.accessMode | string | `"ReadWriteMany"` | | | mq.affinity | object | `{}` | optional affinity settings for mqtt | -| mq.existingClaim | string | `""` | name of existing PVC claim to use. if set, storageClassName is ignored | | mq.existingSecret | string | `""` | name of an existing secret to use for mq password. If set, ignores mq.password | -| mq.password | string | `"3yyerWGdds43yegGR"` | | -| mq.replicas | int | `1` | how many MQTT replicas to create change to 2 or more and set singlenode to false if needed | +| mq.generateCert | bool | `false` | | +| mq.ingress.annotations | object | `{}` | annotations for the mqtt ingress object | +| mq.ingress.className | string | `"nginx"` | | +| mq.ingress.enabled | bool | `true` | attempts to configure ingress if true | +| mq.ingress.host | string | `"broker.cluster.local"` | hostname for mqtt ingress | +| mq.ingress.tls | list | `[]` | ingress tls list | +| mq.password | string | `""` | | +| mq.persistence.accessMode | string | `"ReadWriteMany"` | | +| mq.persistence.existingClaim | string | `""` | name of existing PVC claim to use. if set, storageClassName is ignored | +| mq.persistence.storage | string | `"128Mi"` | | +| mq.persistence.storageClassName | string | `""` | | +| mq.replicas | int | `1` | how many MQTT replicas to create | | mq.secretKey | string | `""` | name of key in existing secret to grab password from. If set, ignores mq.password | -| mq.singlenode | bool | `true` | | -| mq.storage | string | `"128Mi"` | | -| mq.storageClassName | string | `""` | | +| mq.service.port | int | `443` | port for MQTT service | +| mq.service.targetPort | int | `8883` | Target port for MQTT service | +| mq.service.type | string | `"ClusterIP"` | type for netmaker server services | | mq.tolerations | object | `{}` | optional tolerations settings for mqtt | | mq.username | string | `"netmaker"` | | | nameOverride | string | `""` | override the name for netmaker objects | @@ -87,21 +85,25 @@ A Helm chart to run HA Netmaker on Kubernetes | postgresql.auth.username | string | `"netmaker"` | | | postgresql.enabled | bool | `true` | | | replicas | int | `1` | number of netmaker server replicas to create | -| service.mqPort | int | `443` | port for MQTT service | -| service.restPort | int | `8081` | port for API service | -| service.type | string | `"ClusterIP"` | type for netmaker server services | -| service.uiPort | int | `80` | port for UI service | | serviceAccount.annotations | object | `{}` | Annotations to add to the service account | | serviceAccount.create | bool | `true` | Specifies whether a service account should be created | | serviceAccount.name | string | `""` | Name of SA to use. If not set and create is true, a name is generated using the fullname template | | setIpForwarding.enabled | bool | `true` | | | tolerations | object | `{}` | optional tolerations settings for netmaker | +| ui.ingress.annotations | object | `{}` | annotations for the netmaker UI ingress object | +| ui.ingress.className | string | `"nginx"` | UI ingress className | +| ui.ingress.enabled | bool | `true` | attempts to configure ingress if true | +| ui.ingress.host | string | `"dashboard.cluster.local"` | hostname for mqtt ingress | +| ui.ingress.tls | list | `[]` | ingress tls list | | ui.replicas | int | `1` | how many UI replicas to create | +| ui.service.port | int | `80` | port for UI service | +| ui.service.targetport | int | `80` | target port for UI service | +| ui.service.type | string | `"ClusterIP"` | type for netmaker server services | | wireguard.enabled | bool | `true` | whether or not to use WireGuard on server | | wireguard.kernel | bool | `false` | whether or not to use Kernel WG (should be false unless WireGuard is installed on hosts). | | wireguard.networkLimit | int | `10` | max number of networks that Netmaker will support if running with WireGuard enabled | | wireguard.service.annotations | object | `{}` | | -| wireguard.service.serviceType | string | `"NodePort"` | | +| wireguard.service.type | string | `"NodePort"` | | ---------------------------------------------- Autogenerated from chart metadata using [helm-docs v1.13.1](https://github.com/norwoodj/helm-docs/releases/v1.13.1) diff --git a/charts/netmaker/templates/NOTES.txt b/charts/netmaker/templates/NOTES.txt deleted file mode 100644 index 53b369e..0000000 --- a/charts/netmaker/templates/NOTES.txt +++ /dev/null @@ -1,22 +0,0 @@ -1. Get the application URL by running these commands: -{{- if .Values.ingress.enabled }} -{{- range $host := .Values.ingress.hosts }} - {{- range .paths }} - http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host.host }}{{ .path }} - {{- end }} -{{- end }} -{{- else if contains "NodePort" .Values.service.type }} - export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "netmaker.fullname" . }}) - export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}") - echo http://$NODE_IP:$NODE_PORT -{{- else if contains "LoadBalancer" .Values.service.type }} - NOTE: It may take a few minutes for the LoadBalancer IP to be available. - You can watch the status of by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "netmaker.fullname" . }}' - export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "netmaker.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}") - echo http://$SERVICE_IP:{{ .Values.service.port }} -{{- else if contains "ClusterIP" .Values.service.type }} - export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "netmaker.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}") - export CONTAINER_PORT=$(kubectl get pod --namespace {{ .Release.Namespace }} $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}") - echo "Visit http://127.0.0.1:8080 to use your application" - kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8080:$CONTAINER_PORT -{{- end }} diff --git a/charts/netmaker/templates/api-ingress.yaml b/charts/netmaker/templates/api-ingress.yaml new file mode 100644 index 0000000..ab84924 --- /dev/null +++ b/charts/netmaker/templates/api-ingress.yaml @@ -0,0 +1,31 @@ +{{- if .Values.api.ingress.enabled -}} +{{- $fullName := include "netmaker.fullname" . -}} +{{- $fullRESTName := printf "%s-%s" $fullName "api" -}} +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: {{ $fullRESTName }} + labels: + {{- include "netmaker.labels" . | nindent 4 }} + {{- with .Values.api.ingress.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + ingressClassName: {{ required "A valid .Values.ingress.className entry required!" .Values.api.ingress.className}} + {{- with .Values.api.ingress.tls }} + tls: + {{- toYaml . }} + {{- end }} + rules: + - host: {{ .Values.api.ingress.host }} + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: {{ $fullRESTName }} + port: + number: {{ .Values.api.service.port }} +{{- end }} diff --git a/charts/netmaker/templates/api-service.yaml b/charts/netmaker/templates/api-service.yaml new file mode 100644 index 0000000..08ca153 --- /dev/null +++ b/charts/netmaker/templates/api-service.yaml @@ -0,0 +1,17 @@ +--- +apiVersion: v1 +kind: Service +metadata: + labels: + {{- include "netmaker.labels" . | nindent 4 }} + name: '{{ include "netmaker.fullname" . }}-rest' +spec: + ports: + - name: api + port: {{ .Values.api.service.port }} + protocol: TCP + targetPort: {{ .Values.api.service.targetPort }} + selector: + app: '{{ include "netmaker.fullname" . }}' + sessionAffinity: None + type: {{ .Values.api.service.type }} diff --git a/charts/netmaker/templates/cert.yaml b/charts/netmaker/templates/cert.yaml index c1141d2..4e1743c 100644 --- a/charts/netmaker/templates/cert.yaml +++ b/charts/netmaker/templates/cert.yaml @@ -1,18 +1,6 @@ -{{- if .Values.ingress.enabled -}} +{{- if .Values.mq.ingress.generateCert }} {{- $fullName := include "netmaker.fullname" . -}} -{{- $fullUIName := printf "%s-%s" $fullName "ui" -}} -{{- $fullRESTName := printf "%s-%s" $fullName "rest" -}} {{- $fullMQName := printf "%s-%s" $fullName "mqtt" -}} -{{- $uiSvcPort := .Values.service.uiPort -}} -{{- $restSvcPort := .Values.service.restPort -}} -{{- $mqSvcPort := 8883 -}} -{{- $classname := required "A valid .Values.ingress.className entry required! Please set this to your ingress class (nginx, traefik)" .Values.ingress.className}} -{{- if and .Values.ingress.className (not (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion)) }} - {{- if not (hasKey .Values.ingress.annotations "kubernetes.io/ingress.class") }} - {{- $_ := set .Values.ingress.annotations "kubernetes.io/ingress.class" .Values.ingress.className}} - {{- end }} -{{- end }} -{{- if and .Values.ingress.tls.enabled (not (eq .Values.ingress.tls.issuerName "" ))}} apiVersion: cert-manager.io/v1 kind: Certificate metadata: @@ -23,14 +11,13 @@ metadata: name: {{ $fullMQName }}-tls-secret spec: dnsNames: - - {{ .Values.ingress.hostPrefix.mq }}{{ .Values.baseDomain }} + - {{ .Values.mq.ingress.hostname }} issuerRef: group: cert-manager.io kind: ClusterIssuer - name: {{ .Values.ingress.tls.issuerName }} + name: {{ .Values.mq.ingress.tls.issuerName }} secretName: {{ $fullMQName }}-tls-secret usages: - digital signature - key encipherment {{- end }} -{{- end }} diff --git a/charts/netmaker/templates/confgmap.yaml b/charts/netmaker/templates/confgmap.yaml index ef94db1..2ee9d3d 100644 --- a/charts/netmaker/templates/confgmap.yaml +++ b/charts/netmaker/templates/confgmap.yaml @@ -5,9 +5,9 @@ metadata: labels: {{- include "netmaker.labels" . | nindent 4 }} data: - SERVER_NAME: broker.{{ required "A valid .Values.baseDomain entry required!" .Values.baseDomain}} - SERVER_API_CONN_STRING: api.{{ required "A valid .Values.baseDomain entry required!" .Values.baseDomain}}:443 - SERVER_HTTP_HOST: api.{{ required "A valid .Values.baseDomain entry required!" .Values.baseDomain}} + SERVER_NAME: {{ required "A valid .Values.mq.ingress.host entry required!" .Values.mq.ingress.host }} + SERVER_API_CONN_STRING: {{ required "A valid .Values.api.ingress.host entry required!" .Values.api.ingress.host }}:443 + SERVER_HTTP_HOST: {{ required "A valid .Values.api.ingress.host entry required!" .Values.api.ingress.host }} API_PORT: "8081" {{- if not .Values.wireguard.kernel }} WG_QUICK_USERSPACE_IMPLEMENTATION: wireguard-go @@ -27,7 +27,7 @@ data: SQL_DB: "{{ include "netmaker.database.database" . }}" DISPLAY_KEYS: "on" MQ_HOST: {{ include "netmaker.fullname" . }}-mqtt - MQ_PORT: "{{ .Values.service.mqPort }}" + MQ_PORT: "{{ .Values.mq.service.port }}" MQ_USERNAME: "{{ .Values.mq.username }}" MQ_SERVER_PORT: "1883" PLATFORM: "Kubernetes" diff --git a/charts/netmaker/templates/ingress-rest.yaml b/charts/netmaker/templates/ingress-rest.yaml deleted file mode 100644 index a700e6d..0000000 --- a/charts/netmaker/templates/ingress-rest.yaml +++ /dev/null @@ -1,58 +0,0 @@ -{{- if .Values.ingress.enabled -}} -{{- $fullName := include "netmaker.fullname" . -}} -{{- $fullRESTName := printf "%s-%s" $fullName "rest" -}} -{{- $restSvcPort := .Values.service.restPort -}} -{{- $classname := required "A valid .Values.ingress.className entry required! Please set this to your ingress class (nginx, traefik)" .Values.ingress.className}} -apiVersion: networking.k8s.io/v1 -kind: Ingress -metadata: - name: {{ $fullRESTName }} - labels: - {{- include "netmaker.labels" . | nindent 4 }} - {{- with .Values.ingress }} - annotations: - {{- toYaml .annotations.base | nindent 4 }} - {{- if or (eq .className "nginx") (eq .className "public") }} - {{- toYaml .annotations.nginx | nindent 4 }} - {{- end }} - {{- if eq .className "traefik" }} - {{- toYaml .annotations.traefik | nindent 4 }} - {{- end }} - {{- if and .tls.enabled (eq .tls.issuerName "" )}} - {{- toYaml .annotations.tls | nindent 4 }} - {{- else if .tls.enabled}} - cert-manager.io/cluster-issuer: {{ .tls.issuerName }} - {{- end }} - {{- with .annotations.rest }} - {{- toYaml . | nindent 4 }} - {{- end }} - {{- end }} -spec: - {{- if (not (eq .Values.ingress.className "traefik")) }} - ingressClassName: {{ required "A valid .Values.ingress.className entry required!" .Values.ingress.className}} - {{- end }} - {{- if .Values.ingress.tls.enabled }} - tls: - - hosts: - - {{ .Values.ingress.hostPrefix.rest }}{{ .Values.baseDomain }} - secretName: {{ $fullRESTName }}-tls-secret - {{- end }} - rules: - - host: {{ .Values.ingress.hostPrefix.rest }}{{ .Values.baseDomain }} - http: - paths: - - path: / - {{- if (semverCompare ">=1.18-0" $.Capabilities.KubeVersion.GitVersion) }} - pathType: Prefix - {{- end }} - backend: - {{- if semverCompare ">=1.19-0" $.Capabilities.KubeVersion.GitVersion }} - service: - name: {{ $fullRESTName }} - port: - number: {{ $restSvcPort }} - {{- else }} - serviceName: {{ $fullRESTName }} - servicePort: {{ $restSvcPort }} - {{- end }} -{{- end }} diff --git a/charts/netmaker/templates/mq-deployment.yaml b/charts/netmaker/templates/mq-deployment.yaml index d9fa7cc..6a3af6e 100644 --- a/charts/netmaker/templates/mq-deployment.yaml +++ b/charts/netmaker/templates/mq-deployment.yaml @@ -20,70 +20,83 @@ spec: affinity: {{- toYaml . | nindent 8 }} {{- end }} + {{- with .Values.mq.tolerations }} tolerations: {{- toYaml . | nindent 8 }} {{- end }} + containers: - - env: - - name: NETMAKER_SERVER_HOST - value: https://api.{{ required "A valid .Values.baseDomain entry required!" .Values.baseDomain}} - image: eclipse-mosquitto:openssl - command: ["/mosquitto/config/wait.sh"] - imagePullPolicy: Always - name: mosquitto - livenessProbe: - failureThreshold: 3 - periodSeconds: 10 - successThreshold: 1 - tcpSocket: - port: 8883 - timeoutSeconds: 1 - ports: - - containerPort: 1883 - name: mqtt - protocol: TCP - - containerPort: 8883 - name: mqtt2 - protocol: TCP - readinessProbe: - failureThreshold: 3 - periodSeconds: 10 - successThreshold: 1 - tcpSocket: - port: 8883 - timeoutSeconds: 1 - resources: {} - startupProbe: - failureThreshold: 30 - periodSeconds: 5 - successThreshold: 1 - tcpSocket: - port: 8883 - timeoutSeconds: 1 - terminationMessagePath: /dev/termination-log - terminationMessagePolicy: File - volumeMounts: - - mountPath: /mosquitto/config/mosquitto.conf - name: mosquitto-config - subPath: mosquitto.conf - - mountPath: /mosquitto/config/wait.sh - name: wait-script - subPath: wait.sh - - mountPath: /mosquitto/data - name: shared-data + - name: mosquitto + image: eclipse-mosquitto:openssl + command: ["/mosquitto/config/wait.sh"] + imagePullPolicy: Always + + env: + - name: NETMAKER_SERVER_HOST + value: https://{{ required "A valid .Values.mq.ingress.host entry required!" .Values.mq.ingress.host }} + + livenessProbe: + failureThreshold: 3 + periodSeconds: 10 + successThreshold: 1 + tcpSocket: + port: {{ .Values.mq.service.targetPort }} + timeoutSeconds: 1 + + ports: + - containerPort: 1883 + name: mqtt + protocol: TCP + - containerPort: {{ .Values.mq.service.targetPort }} + name: mqtt2 + protocol: TCP + + readinessProbe: + failureThreshold: 3 + periodSeconds: 10 + successThreshold: 1 + tcpSocket: + port: {{ .Values.mq.service.targetPort }} + timeoutSeconds: 1 + + resources: {} + + startupProbe: + failureThreshold: 30 + periodSeconds: 5 + successThreshold: 1 + tcpSocket: + port: {{ .Values.mq.service.targetPort }} + timeoutSeconds: 1 + + terminationMessagePath: /dev/termination-log + terminationMessagePolicy: File + + volumeMounts: + - name: mosquitto-config + mountPath: /mosquitto/config/mosquitto.conf + subPath: mosquitto.conf + - name: wait-script + mountPath: /mosquitto/config/wait.sh + subPath: wait.sh + - name: shared-data + mountPath: /mosquitto/data + volumes: - - configMap: - name: {{ include "netmaker.fullname" . }}-mqtt-config - name: mosquitto-config - - configMap: - name: {{ include "netmaker.fullname" . }}-mqtt-wait - defaultMode: 0744 - name: wait-script - - name: shared-data - persistentVolumeClaim: - {{- if .Values.mq.existingClaim }} - claimName: {{ .Values.mq.existingClaim }} - {{- else }} - claimName: {{ include "netmaker.fullname" . }}-shared-data - {{- end }} + - name: mosquitto-config + configMap: + name: {{ include "netmaker.fullname" . }}-mqtt-config + + - name: wait-script + configMap: + name: {{ include "netmaker.fullname" . }}-mqtt-wait + defaultMode: 0744 + + - name: shared-data + persistentVolumeClaim: + {{- if .Values.mq.existingClaim }} + claimName: {{ .Values.mq.existingClaim }} + {{- else }} + claimName: {{ include "netmaker.fullname" . }}-shared-data + {{- end }} diff --git a/charts/netmaker/templates/ingress-route.yaml b/charts/netmaker/templates/mq-ingress-route.yaml similarity index 61% rename from charts/netmaker/templates/ingress-route.yaml rename to charts/netmaker/templates/mq-ingress-route.yaml index d553420..e285ea9 100644 --- a/charts/netmaker/templates/ingress-route.yaml +++ b/charts/netmaker/templates/mq-ingress-route.yaml @@ -1,7 +1,6 @@ -{{- if and .Values.ingress.enabled (eq .Values.ingress.className "traefik") -}} +{{- if and .Values.mq.ingress.enabled (eq .Values.mq.ingress.className "traefik") -}} {{- $fullName := include "netmaker.fullname" . -}} {{- $fullMQName := printf "%s-%s" $fullName "mqtt" -}} -{{- $mqSvcPort := 8883 -}} apiVersion: traefik.containo.us/v1alpha1 kind: IngressRouteTCP metadata: @@ -12,13 +11,13 @@ spec: entryPoints: - websecure routes: - - match: HostSNI(`{{ .Values.ingress.hostPrefix.mq }}{{ .Values.baseDomain }}`) + - match: HostSNI(`{{ .Values.mq.ingress.host }}`) services: - name: {{ $fullMQName }} - port: {{ $mqSvcPort }} + port: {{ .Values.mq.service.targetPort }} tls: passthrough: true secretName: {{ $fullMQName }}-tls-secret domains: - - main: {{ .Values.ingress.hostPrefix.mq }}{{ .Values.baseDomain }} + - main: {{ .Values.mq.ingress.host }} {{- end }} diff --git a/charts/netmaker/templates/mq-ingress.yaml b/charts/netmaker/templates/mq-ingress.yaml index a0f9f00..77351a7 100644 --- a/charts/netmaker/templates/mq-ingress.yaml +++ b/charts/netmaker/templates/mq-ingress.yaml @@ -1,54 +1,32 @@ -{{- if .Values.ingress.enabled -}} +{{- if .Values.mq.ingress.enabled -}} {{- $fullName := include "netmaker.fullname" . -}} {{- $fullMQName := printf "%s-%s" $fullName "mqtt" -}} -{{- $mqSvcPort := 8883 -}} -{{- $classname := required "A valid .Values.ingress.className entry required! Please set this to your ingress class (nginx, traefik)" .Values.ingress.className}} -{{- if or (eq .Values.ingress.className "nginx") (eq .Values.ingress.className "public") }} apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: {{ $fullMQName }} labels: {{- include "netmaker.labels" . | nindent 4 }} - {{- with .Values.ingress }} + {{- with .Values.mq.ingress.annotations }} annotations: - {{- toYaml .annotations.nginx | nindent 4 }} - {{- if and .tls.enabled (eq .tls.issuerName "" )}} - {{- toYaml .annotations.tls | nindent 4 }} - {{- else if .tls.enabled}} - cert-manager.io/cluster-issuer: {{ .tls.issuerName }} - {{- end }} - {{- with .annotations.mq }} - {{- toYaml . | nindent 4 }} - {{- end }} + {{- toYaml . }} {{- end }} spec: - ingressClassName: {{ required "A valid .Values.ingress.className entry required!" .Values.ingress.className}} - {{- if .Values.ingress.tls.enabled }} + ingressClassName: {{ required "A valid .Values.ingress.className entry required!" .Values.mq.ingress.className}} + {{- with .Values.mq.ingress.tls }} tls: - - hosts: - - {{ .Values.ingress.hostPrefix.broker }}{{ .Values.baseDomain }} - secretName: {{ $fullMQName }}-tls-secret + {{- toYaml . }} {{- end }} rules: - - host: {{ .Values.ingress.hostPrefix.broker }}{{ .Values.baseDomain }} + - host: {{ .Values.mq.ingress.host }} http: paths: - path: / - {{- if (semverCompare ">=1.18-0" $.Capabilities.KubeVersion.GitVersion) }} pathType: Prefix - {{- end }} backend: - {{- if semverCompare ">=1.19-0" $.Capabilities.KubeVersion.GitVersion }} service: name: {{ $fullMQName }} port: - number: {{ $mqSvcPort }} - {{- else }} - serviceName: {{ $fullMQName }} - servicePort: {{ $mqSvcPort }} - {{- end }} -{{- end }} - + number: {{ .Values.mq.service.targetPort }} {{- end }} diff --git a/charts/netmaker/templates/mq-service.yaml b/charts/netmaker/templates/mq-service.yaml index 1bbfddf..0320185 100644 --- a/charts/netmaker/templates/mq-service.yaml +++ b/charts/netmaker/templates/mq-service.yaml @@ -10,7 +10,7 @@ spec: protocol: TCP targetPort: mqtt - name: mqtt2 - port: 8883 + port: {{ .Values.mq.service.targetPort }} protocol: TCP targetPort: mqtt2 selector: diff --git a/charts/netmaker/templates/netmaker-statefulset.yaml b/charts/netmaker/templates/netmaker-statefulset.yaml index 6abbcf5..1fbaaab 100644 --- a/charts/netmaker/templates/netmaker-statefulset.yaml +++ b/charts/netmaker/templates/netmaker-statefulset.yaml @@ -120,7 +120,7 @@ spec: imagePullPolicy: {{ .Values.image.pullPolicy }} name: {{ include "netmaker.fullname" . }} ports: - - containerPort: {{ .Values.service.restPort }} + - containerPort: {{ .Values.api.service.port }} protocol: TCP {{- if .Values.wireguard.enabled }} {{ $count := (add .Values.wireguard.networkLimit 1 | int) }} diff --git a/charts/netmaker/templates/services.yaml b/charts/netmaker/templates/services.yaml deleted file mode 100644 index a5652e2..0000000 --- a/charts/netmaker/templates/services.yaml +++ /dev/null @@ -1,57 +0,0 @@ -apiVersion: v1 -kind: Service -metadata: - labels: - {{- include "netmaker.labels" . | nindent 4 }} - name: '{{ include "netmaker.fullname" . }}-ui' -spec: - ports: - - port: {{ .Values.service.uiPort }} - protocol: TCP - targetPort: {{ .Values.service.uiPort }} - selector: - app: '{{ include "netmaker.fullname" . }}-ui' - sessionAffinity: None - type: '{{ .Values.service.type }}' ---- -apiVersion: v1 -kind: Service -metadata: - labels: - {{- include "netmaker.labels" . | nindent 4 }} - name: '{{ include "netmaker.fullname" . }}-rest' -spec: - ports: - - name: rest - port: {{ .Values.service.restPort }} - protocol: TCP - targetPort: {{ .Values.service.restPort }} - selector: - app: '{{ include "netmaker.fullname" . }}' - sessionAffinity: None - type: {{ .Values.service.type }} ---- -apiVersion: v1 -kind: Service -metadata: - labels: - {{- include "netmaker.labels" . | nindent 4 }} - name: '{{ include "netmaker.fullname" . }}-wireguard' - {{- with .Values.wireguard.service.annotations }} - annotations: - {{- toYaml . | nindent 4 }} - {{- end }} -spec: - externalTrafficPolicy: Local - type: {{ .Values.wireguard.service.serviceType }} - ports: - {{ $count := (add .Values.wireguard.networkLimit 1 | int) }} - {{- range untilStep 1 $count 1 }} - - port: {{ add 31820 . }} - nodePort: {{ add 31820 . }} - protocol: UDP - targetPort: {{ add 31820 . }} - name: wg-iface-{{ add 31820 . }} - {{- end }} - selector: - app: '{{ include "netmaker.fullname" . }}' diff --git a/charts/netmaker/templates/tests/test-connection.yaml b/charts/netmaker/templates/tests/test-connection.yaml deleted file mode 100644 index c0d498c..0000000 --- a/charts/netmaker/templates/tests/test-connection.yaml +++ /dev/null @@ -1,15 +0,0 @@ -apiVersion: v1 -kind: Pod -metadata: - name: "{{ include "netmaker.fullname" . }}-test-connection" - labels: - {{- include "netmaker.labels" . | nindent 4 }} - annotations: - "helm.sh/hook": test -spec: - containers: - - name: wget - image: busybox - command: ['wget'] - args: ['{{ include "netmaker.fullname" . }}:{{ .Values.service.port }}'] - restartPolicy: Never diff --git a/charts/netmaker/templates/ui-deployment.yaml b/charts/netmaker/templates/ui-deployment.yaml index 38d3a6e..49c8d33 100644 --- a/charts/netmaker/templates/ui-deployment.yaml +++ b/charts/netmaker/templates/ui-deployment.yaml @@ -15,11 +15,11 @@ spec: app: {{ include "netmaker.fullname" . }}-ui spec: containers: - - name: {{ include "netmaker.fullname" . }}-ui - image: {{ include "netmaker.ui.image" . }} - ports: - - containerPort: {{ .Values.service.uiPort }} - env: - - name: BACKEND_URL - value: 'https://{{ .Values.ingress.hostPrefix.rest }}{{ required "A valid .Values.baseDomain entry required!" .Values.baseDomain}}' + - name: {{ include "netmaker.fullname" . }}-ui + image: {{ include "netmaker.ui.image" . }} + ports: + - containerPort: {{ .Values.ui.service.port }} + env: + - name: BACKEND_URL + value: 'https://{{ required "A valid .Values.api.ingress.host entry required!" .Values.api.ingress.host}}' terminationGracePeriodSeconds: 15 diff --git a/charts/netmaker/templates/ui-ingress.yaml b/charts/netmaker/templates/ui-ingress.yaml index f22d270..b22bbd4 100644 --- a/charts/netmaker/templates/ui-ingress.yaml +++ b/charts/netmaker/templates/ui-ingress.yaml @@ -1,75 +1,31 @@ -{{- if .Values.ingress.enabled -}} +{{- if .Values.ui.ingress.enabled -}} {{- $fullName := include "netmaker.fullname" . -}} {{- $fullUIName := printf "%s-%s" $fullName "ui" -}} -{{- $fullRESTName := printf "%s-%s" $fullName "rest" -}} -{{- $fullMQName := printf "%s-%s" $fullName "mqtt" -}} -{{- $uiSvcPort := .Values.service.uiPort -}} -{{- $restSvcPort := .Values.service.restPort -}} -{{- $mqSvcPort := 8883 -}} -{{- $classname := required "A valid .Values.ingress.className entry required! Please set this to your ingress class (nginx, traefik)" .Values.ingress.className}} -{{- if and .Values.ingress.className (not (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion)) }} - {{- if not (hasKey .Values.ingress.annotations "kubernetes.io/ingress.class") }} - {{- $_ := set .Values.ingress.annotations "kubernetes.io/ingress.class" .Values.ingress.className}} - {{- end }} -{{- end }} -{{- if semverCompare ">=1.19-0" .Capabilities.KubeVersion.GitVersion -}} apiVersion: networking.k8s.io/v1 -{{- else if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}} -apiVersion: networking.k8s.io/v1beta1 -{{- else -}} -apiVersion: extensions/v1beta1 -{{- end }} kind: Ingress metadata: name: {{ $fullUIName }} labels: {{- include "netmaker.labels" . | nindent 4 }} - {{- with .Values.ingress }} + {{- with .Values.ui.ingress.annotations }} annotations: - {{- toYaml .annotations.base | nindent 4 }} - {{- if or (eq .className "nginx") (eq .className "public") }} - {{- toYaml .annotations.nginx | nindent 4 }} - {{- end }} - {{- if eq .className "traefik" }} - {{- toYaml .annotations.traefik | nindent 4 }} - {{- end }} - {{- if and .tls.enabled (eq .tls.issuerName "" )}} - {{- toYaml .annotations.tls | nindent 4 }} - {{- else if .tls.enabled}} - cert-manager.io/cluster-issuer: {{ .tls.issuerName }} - {{- end }} - {{- with .annotations.ui }} - {{- toYaml . | nindent 4 }} - {{- end }} + {{- toYaml . }} {{- end }} spec: - {{- if (not (eq .Values.ingress.className "traefik")) }} - {{- if and .Values.ingress.className (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion) }} - ingressClassName: {{ required "A valid .Values.ingress.className entry required!" .Values.ingress.className}} - {{- end }} - {{- end }} - {{- if .Values.ingress.tls.enabled }} + ingressClassName: {{ required "A valid .Values.ingress.className entry required!" .Values.ui.ingress.className}} + {{- with .Values.ui.ingress.tls }} tls: - - hosts: - - {{ .Values.ingress.hostPrefix.ui }}{{ .Values.baseDomain }} - secretName: {{ $fullUIName }}-tls-secret + {{- toYaml . }} {{- end}} rules: - - host: {{ .Values.ingress.hostPrefix.ui }}{{ .Values.baseDomain }} + - host: {{ .Values.ui.ingress.host }} http: paths: - path: / - {{- if (semverCompare ">=1.18-0" $.Capabilities.KubeVersion.GitVersion) }} pathType: Prefix - {{- end }} backend: - {{- if semverCompare ">=1.19-0" $.Capabilities.KubeVersion.GitVersion }} service: name: {{ $fullUIName }} port: - number: {{ $uiSvcPort }} - {{- else }} - serviceName: {{ $fullUIName }} - servicePort: {{ $uiSvcPort }} - {{- end }} + number: {{ .Values.ui.service.port }} {{- end }} diff --git a/charts/netmaker/templates/ui-service.yaml b/charts/netmaker/templates/ui-service.yaml new file mode 100644 index 0000000..db882ad --- /dev/null +++ b/charts/netmaker/templates/ui-service.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Service +metadata: + labels: + {{- include "netmaker.labels" . | nindent 4 }} + name: '{{ include "netmaker.fullname" . }}-ui' +spec: + ports: + - port: {{ .Values.ui.service.port }} + protocol: TCP + targetPort: {{ .Values.ui.service.targetPort }} + selector: + app: '{{ include "netmaker.fullname" . }}-ui' + sessionAffinity: None + type: {{ .Values.ui.service.type }} diff --git a/charts/netmaker/templates/wireguard-service.yaml b/charts/netmaker/templates/wireguard-service.yaml new file mode 100644 index 0000000..c54304d --- /dev/null +++ b/charts/netmaker/templates/wireguard-service.yaml @@ -0,0 +1,25 @@ +--- +apiVersion: v1 +kind: Service +metadata: + labels: + {{- include "netmaker.labels" . | nindent 4 }} + name: '{{ include "netmaker.fullname" . }}-wireguard' + {{- with .Values.wireguard.service.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + externalTrafficPolicy: Local + type: {{ .Values.wireguard.service.type }} + ports: + {{ $count := (add .Values.wireguard.networkLimit 1 | int) }} + {{- range untilStep 1 $count 1 }} + - port: {{ add 31820 . }} + nodePort: {{ add 31820 . }} + protocol: UDP + targetPort: {{ add 31820 . }} + name: wg-iface-{{ add 31820 . }} + {{- end }} + selector: + app: '{{ include "netmaker.fullname" . }}' diff --git a/charts/netmaker/values.yaml b/charts/netmaker/values.yaml index 084d801..f9dc452 100644 --- a/charts/netmaker/values.yaml +++ b/charts/netmaker/values.yaml @@ -3,8 +3,6 @@ # -- number of netmaker server replicas to create replicas: 1 -baseDomain: example.com - image: # -- The image repo to pull Netmaker image from repository: gravitl/netmaker @@ -51,15 +49,61 @@ podAnnotations: {} podSecurityContext: {} # fsGroup: 2000 +api: + service: + # -- type for netmaker server services + type: ClusterIP + # -- port for API service + port: 8081 + # -- targetport for API service + targetPort: 8081 + + ingress: + # -- attempts to configure ingress if true + enabled: true + # -- api (REST) route subdomain + host: 'api.cluster.local' + # -- api ingress className + className: "nginx" + # -- annotations for the netmaker API ingress object + annotations: {} + # -- ingress api tls list + tls: [] + # tls: + # - secretName: netmaker-api-tls + # hosts: + # - api.mydomain.tld + ui: # -- how many UI replicas to create replicas: 1 + service: + # -- type for netmaker server services + type: ClusterIP + # -- port for UI service + port: 80 + # -- target port for UI service + targetport: 80 + ingress: + # -- attempts to configure ingress if true + enabled: true + # -- hostname for mqtt ingress + host: "dashboard.cluster.local" + # -- UI ingress className + className: "nginx" + # -- annotations for the netmaker UI ingress object + annotations: {} + # -- ingress tls list + tls: [] + # tls: + # - secretName: netmaker-mqtt-tls + # hosts: + # - dashboard.mydomain.tld mq: # -- how many MQTT replicas to create - # change to 2 or more and set singlenode to false if needed replicas: 1 - singlenode: true + generateCert: false # -- optional tolerations settings for mqtt tolerations: {} # -- optional affinity settings for mqtt @@ -73,82 +117,62 @@ mq: # values: # - "true" username: netmaker - password: 3yyerWGdds43yegGR + password: '' # -- name of an existing secret to use for mq password. If set, ignores mq.password existingSecret: '' # -- name of key in existing secret to grab password from. If set, ignores mq.password secretKey: '' - # -- name of existing PVC claim to use. if set, storageClassName is ignored - existingClaim: "" - accessMode: "ReadWriteMany" - storageClassName: "" - storage: 128Mi + persistence: + # -- name of existing PVC claim to use. if set, storageClassName is ignored + existingClaim: "" + accessMode: "ReadWriteMany" + storageClassName: "" + storage: 128Mi + service: + # -- type for netmaker server services + type: ClusterIP + # -- port for MQTT service + port: 443 + # -- Target port for MQTT service + targetPort: 8883 + ingress: + # -- attempts to configure ingress if true + enabled: true + # -- hostname for mqtt ingress + host: "broker.cluster.local" + className: "nginx" + # -- annotations for the mqtt ingress object + annotations: {} + ## Redirect http to https + # nginx.ingress.kubernetes.io/ssl-redirect: 'true' + ## destination addr for route + # nginx.ingress.kubernetes.io/rewrite-target: / + # -- ingress tls list + tls: [] + # tls: + # - secretName: netmaker-mqtt-tls + # hosts: + # - broker.mydomain.tld dns: # -- whether or not to deploy coredns enabled: false - # -- existingClaim, if not set, defaults to HELM.RELEASE.NAME-dns - existingClaim: '' - storage: 1Gi - storageClassName: "" - accessMode: ReadWriteOnce + persistence: + # -- existingClaim, if not set, defaults to HELM.RELEASE.NAME-dns + existingClaim: '' + storage: 1Gi + storageClassName: "" + accessMode: ReadWriteOnce setIpForwarding: enabled: true -service: - # -- type for netmaker server services - type: ClusterIP - # -- port for API service - restPort: 8081 - # -- port for MQTT service - mqPort: 443 - # -- port for UI service - uiPort: 80 - -ingress: - # -- attempts to configure ingress if true - enabled: true - className: "nginx" - tls: - enabled: false - issuerName: "letsencrypt-prod" - annotations: - ui: {} - rest: {} - mq: {} - base: - # -- annotation to generate ACME certs if available - kubernetes.io/ingress.allow-http: "false" - tls: - # -- use acme cert if available - kubernetes.io/tls-acme: "true" - nginx: - # -- Redirect http to https - nginx.ingress.kubernetes.io/ssl-redirect: 'true' - # -- destination addr for route - nginx.ingress.kubernetes.io/rewrite-target: / - traefik: - # -- Redirect to https - traefik.ingress.kubernetes.io/redirect-entry-point: https - # -- Redirect to https permanently - traefik.ingress.kubernetes.io/redirect-permanent: "true" - # -- rule type - traefik.ingress.kubernetes.io/rule-type: "PathPrefixStrip" - hostPrefix: - # -- ui route subdomain - ui: 'dashboard.' - # -- api (REST) route subdomain - rest: 'api.' - # -- mqtt route subdomain - broker: 'broker.' - wireguard: # -- whether or not to use WireGuard on server enabled: true service: annotations: {} - serviceType: NodePort + type: NodePort # -- whether or not to use Kernel WG (should be false unless WireGuard is installed on hosts). kernel: false # -- max number of networks that Netmaker will support if running with WireGuard enabled From 3d58e2fa2a1dca5ae74371b784fca4a190b8c959 Mon Sep 17 00:00:00 2001 From: jessebot Date: Sun, 24 Mar 2024 21:55:33 +0100 Subject: [PATCH 30/40] clean up persistence --- charts/netmaker/templates/coredns-pvc.yaml | 8 ++++---- charts/netmaker/templates/coredns.yaml | 4 ++-- charts/netmaker/templates/mq-deployment.yaml | 4 ++-- charts/netmaker/templates/mq-pvc.yaml | 8 ++++---- charts/netmaker/templates/netmaker-statefulset.yaml | 8 ++++---- 5 files changed, 16 insertions(+), 16 deletions(-) diff --git a/charts/netmaker/templates/coredns-pvc.yaml b/charts/netmaker/templates/coredns-pvc.yaml index 7b9b9cc..ccff0a9 100644 --- a/charts/netmaker/templates/coredns-pvc.yaml +++ b/charts/netmaker/templates/coredns-pvc.yaml @@ -1,12 +1,12 @@ -{{- if and .Values.dns.enabled (not .Values.dns.existingClaim) -}} +{{- if and .Values.dns.enabled (not .Values.dns.persistence.existingClaim) -}} apiVersion: v1 kind: PersistentVolumeClaim metadata: name: {{ include "netmaker.fullname" . }}-dns spec: - storageClassName: {{ required "A valid .Values.dns.storageClassName entry required! Specify an available storage class." .Values.dns.storageClassName}} - accessModes: [{{ .Values.dns.accessMode }}] + storageClassName: {{ required "A valid .Values.dns.storageClassName entry required! Specify an available storage class." .Values.dns.persistence.storageClassName}} + accessModes: [{{ .Values.dns.persistence.accessMode }}] resources: requests: - storage: {{ .Values.dns.storage }} + storage: {{ .Values.dns.persistence.storage }} {{- end }} diff --git a/charts/netmaker/templates/coredns.yaml b/charts/netmaker/templates/coredns.yaml index 1830642..86afdaf 100644 --- a/charts/netmaker/templates/coredns.yaml +++ b/charts/netmaker/templates/coredns.yaml @@ -47,8 +47,8 @@ spec: volumes: - name: dns-pvc persistentVolumeClaim: - {{- if .Values.dns.existingClaim }} - claimName: {{ .Values.dns.existingClaim }} + {{- if .Values.dns.persistence.existingClaim }} + claimName: {{ .Values.dns.persistence.existingClaim }} {{- else }} claimName: {{ include "netmaker.fullname" . }}-dns {{- end }} diff --git a/charts/netmaker/templates/mq-deployment.yaml b/charts/netmaker/templates/mq-deployment.yaml index 6a3af6e..75edc02 100644 --- a/charts/netmaker/templates/mq-deployment.yaml +++ b/charts/netmaker/templates/mq-deployment.yaml @@ -95,8 +95,8 @@ spec: - name: shared-data persistentVolumeClaim: - {{- if .Values.mq.existingClaim }} - claimName: {{ .Values.mq.existingClaim }} + {{- if .Values.mq.persistence.existingClaim }} + claimName: {{ .Values.mq.persistence.existingClaim }} {{- else }} claimName: {{ include "netmaker.fullname" . }}-shared-data {{- end }} diff --git a/charts/netmaker/templates/mq-pvc.yaml b/charts/netmaker/templates/mq-pvc.yaml index f87f67d..916b805 100644 --- a/charts/netmaker/templates/mq-pvc.yaml +++ b/charts/netmaker/templates/mq-pvc.yaml @@ -1,13 +1,13 @@ --- -{{- if not .Values.mq.existingClaim }} +{{- if not .Values.mq.persistence.existingClaim }} kind: PersistentVolumeClaim apiVersion: v1 metadata: name: {{ include "netmaker.fullname" . }}-shared-data spec: - storageClassName: {{ .Values.mq.storageClassName }} - accessModes: [{{ .Values.mq.accessMode }}] + storageClassName: {{ .Values.mq.persistence.storageClassName }} + accessModes: [{{ .Values.mq.persistence.accessMode }}] resources: requests: - storage: {{ .Values.mq.storage }} + storage: {{ .Values.mq.persistence.storage }} {{- end }} diff --git a/charts/netmaker/templates/netmaker-statefulset.yaml b/charts/netmaker/templates/netmaker-statefulset.yaml index 1fbaaab..7813fbd 100644 --- a/charts/netmaker/templates/netmaker-statefulset.yaml +++ b/charts/netmaker/templates/netmaker-statefulset.yaml @@ -145,16 +145,16 @@ spec: volumes: - name: shared-data persistentVolumeClaim: - {{- if .Values.mq.existingClaim }} - claimName: {{ .Values.mq.existingClaim }} + {{- if .Values.mq.persistence.existingClaim }} + claimName: {{ .Values.mq.persistence.existingClaim }} {{- else }} claimName: {{ include "netmaker.fullname" . }}-shared-data {{- end }} {{- if .Values.dns.enabled }} - name: dns-pvc persistentVolumeClaim: - {{- if .Values.dns.existingClaim }} - claimName: {{ .Values.dns.existingClaim }} + {{- if .Values.dns.persistence.existingClaim }} + claimName: {{ .Values.dns.persistence.existingClaim }} {{- else }} claimName: {{ include "netmaker.fullname" . }}-dns {{- end }} From e71ed3e6a64737c7e7906f44e979fb48c5aea3ec Mon Sep 17 00:00:00 2001 From: jessebot Date: Sun, 24 Mar 2024 22:13:36 +0100 Subject: [PATCH 31/40] remove space --- charts/netmaker/templates/mq-ingress.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/charts/netmaker/templates/mq-ingress.yaml b/charts/netmaker/templates/mq-ingress.yaml index 77351a7..a95a46e 100644 --- a/charts/netmaker/templates/mq-ingress.yaml +++ b/charts/netmaker/templates/mq-ingress.yaml @@ -1,7 +1,6 @@ {{- if .Values.mq.ingress.enabled -}} {{- $fullName := include "netmaker.fullname" . -}} {{- $fullMQName := printf "%s-%s" $fullName "mqtt" -}} - apiVersion: networking.k8s.io/v1 kind: Ingress metadata: From 0d5da2d9c3d9dd01c9c1bfae29051ce7160dbc1b Mon Sep 17 00:00:00 2001 From: jessebot Date: Sun, 24 Mar 2024 22:28:08 +0100 Subject: [PATCH 32/40] fix ingress tls sections --- charts/netmaker/templates/api-ingress.yaml | 2 +- charts/netmaker/templates/mq-ingress.yaml | 2 +- charts/netmaker/templates/ui-ingress.yaml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/charts/netmaker/templates/api-ingress.yaml b/charts/netmaker/templates/api-ingress.yaml index ab84924..580f358 100644 --- a/charts/netmaker/templates/api-ingress.yaml +++ b/charts/netmaker/templates/api-ingress.yaml @@ -15,7 +15,7 @@ spec: ingressClassName: {{ required "A valid .Values.ingress.className entry required!" .Values.api.ingress.className}} {{- with .Values.api.ingress.tls }} tls: - {{- toYaml . }} + {{- toYaml . | nindent 4 }} {{- end }} rules: - host: {{ .Values.api.ingress.host }} diff --git a/charts/netmaker/templates/mq-ingress.yaml b/charts/netmaker/templates/mq-ingress.yaml index a95a46e..2a41973 100644 --- a/charts/netmaker/templates/mq-ingress.yaml +++ b/charts/netmaker/templates/mq-ingress.yaml @@ -15,7 +15,7 @@ spec: ingressClassName: {{ required "A valid .Values.ingress.className entry required!" .Values.mq.ingress.className}} {{- with .Values.mq.ingress.tls }} tls: - {{- toYaml . }} + {{- toYaml . | nindent 4 }} {{- end }} rules: - host: {{ .Values.mq.ingress.host }} diff --git a/charts/netmaker/templates/ui-ingress.yaml b/charts/netmaker/templates/ui-ingress.yaml index b22bbd4..7fb12c1 100644 --- a/charts/netmaker/templates/ui-ingress.yaml +++ b/charts/netmaker/templates/ui-ingress.yaml @@ -15,7 +15,7 @@ spec: ingressClassName: {{ required "A valid .Values.ingress.className entry required!" .Values.ui.ingress.className}} {{- with .Values.ui.ingress.tls }} tls: - {{- toYaml . }} + {{- toYaml . | nindent 4 }} {{- end}} rules: - host: {{ .Values.ui.ingress.host }} From 9a23a74bc04d8f70e056c9e9d716634b8ace1785 Mon Sep 17 00:00:00 2001 From: jessebot Date: Sun, 24 Mar 2024 22:30:56 +0100 Subject: [PATCH 33/40] fix ingress annotations --- charts/netmaker/templates/mq-ingress.yaml | 2 +- charts/netmaker/templates/ui-ingress.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/charts/netmaker/templates/mq-ingress.yaml b/charts/netmaker/templates/mq-ingress.yaml index 2a41973..3b4f027 100644 --- a/charts/netmaker/templates/mq-ingress.yaml +++ b/charts/netmaker/templates/mq-ingress.yaml @@ -9,7 +9,7 @@ metadata: {{- include "netmaker.labels" . | nindent 4 }} {{- with .Values.mq.ingress.annotations }} annotations: - {{- toYaml . }} + {{- toYaml . | nindent 4 }} {{- end }} spec: ingressClassName: {{ required "A valid .Values.ingress.className entry required!" .Values.mq.ingress.className}} diff --git a/charts/netmaker/templates/ui-ingress.yaml b/charts/netmaker/templates/ui-ingress.yaml index 7fb12c1..1aaecc3 100644 --- a/charts/netmaker/templates/ui-ingress.yaml +++ b/charts/netmaker/templates/ui-ingress.yaml @@ -9,7 +9,7 @@ metadata: {{- include "netmaker.labels" . | nindent 4 }} {{- with .Values.ui.ingress.annotations }} annotations: - {{- toYaml . }} + {{- toYaml . | nindent 4 }} {{- end }} spec: ingressClassName: {{ required "A valid .Values.ingress.className entry required!" .Values.ui.ingress.className}} From ea40b67eea7b1ceab9adf4b256beab2a896a2f75 Mon Sep 17 00:00:00 2001 From: jessebot Date: Mon, 25 Mar 2024 06:59:08 +0100 Subject: [PATCH 34/40] update mq deployment to not have shared data volume --- charts/netmaker/templates/mq-deployment.yaml | 30 +++++++------------- 1 file changed, 10 insertions(+), 20 deletions(-) diff --git a/charts/netmaker/templates/mq-deployment.yaml b/charts/netmaker/templates/mq-deployment.yaml index 75edc02..47531d6 100644 --- a/charts/netmaker/templates/mq-deployment.yaml +++ b/charts/netmaker/templates/mq-deployment.yaml @@ -36,14 +36,6 @@ spec: - name: NETMAKER_SERVER_HOST value: https://{{ required "A valid .Values.mq.ingress.host entry required!" .Values.mq.ingress.host }} - livenessProbe: - failureThreshold: 3 - periodSeconds: 10 - successThreshold: 1 - tcpSocket: - port: {{ .Values.mq.service.targetPort }} - timeoutSeconds: 1 - ports: - containerPort: 1883 name: mqtt @@ -52,7 +44,7 @@ spec: name: mqtt2 protocol: TCP - readinessProbe: + livenessProbe: failureThreshold: 3 periodSeconds: 10 successThreshold: 1 @@ -60,7 +52,13 @@ spec: port: {{ .Values.mq.service.targetPort }} timeoutSeconds: 1 - resources: {} + readinessProbe: + failureThreshold: 3 + periodSeconds: 10 + successThreshold: 1 + tcpSocket: + port: {{ .Values.mq.service.targetPort }} + timeoutSeconds: 1 startupProbe: failureThreshold: 30 @@ -73,6 +71,8 @@ spec: terminationMessagePath: /dev/termination-log terminationMessagePolicy: File + resources: {} + volumeMounts: - name: mosquitto-config mountPath: /mosquitto/config/mosquitto.conf @@ -80,8 +80,6 @@ spec: - name: wait-script mountPath: /mosquitto/config/wait.sh subPath: wait.sh - - name: shared-data - mountPath: /mosquitto/data volumes: - name: mosquitto-config @@ -92,11 +90,3 @@ spec: configMap: name: {{ include "netmaker.fullname" . }}-mqtt-wait defaultMode: 0744 - - - name: shared-data - persistentVolumeClaim: - {{- if .Values.mq.persistence.existingClaim }} - claimName: {{ .Values.mq.persistence.existingClaim }} - {{- else }} - claimName: {{ include "netmaker.fullname" . }}-shared-data - {{- end }} From 531487d168339482a7b9afe49ccdae4f1b1420cd Mon Sep 17 00:00:00 2001 From: jessebot Date: Mon, 25 Mar 2024 07:04:18 +0100 Subject: [PATCH 35/40] split up the configmap for mqtt and update it to the latest upstream script --- .../templates/mq-configmap-config.yaml | 17 ++++++ .../netmaker/templates/mq-configmap-wait.yaml | 28 ++++++++++ charts/netmaker/templates/mq-configmap.yaml | 53 ------------------- 3 files changed, 45 insertions(+), 53 deletions(-) create mode 100644 charts/netmaker/templates/mq-configmap-config.yaml create mode 100644 charts/netmaker/templates/mq-configmap-wait.yaml delete mode 100644 charts/netmaker/templates/mq-configmap.yaml diff --git a/charts/netmaker/templates/mq-configmap-config.yaml b/charts/netmaker/templates/mq-configmap-config.yaml new file mode 100644 index 0000000..f72aa14 --- /dev/null +++ b/charts/netmaker/templates/mq-configmap-config.yaml @@ -0,0 +1,17 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + labels: + app.kubernetes.io/instance: {{ include "netmaker.fullname" . }}-mqtt + app.kubernetes.io/name: {{ include "netmaker.fullname" . }}-mqtt + name: {{ include "netmaker.fullname" . }}-mqtt-config +data: + mosquitto.conf: | + per_listener_settings false + listener {{ .Values.mq.service.targetPort }} + protocol websockets + allow_anonymous false + listener 1883 + protocol websockets + allow_anonymous false + password_file /mosquitto/password.txt diff --git a/charts/netmaker/templates/mq-configmap-wait.yaml b/charts/netmaker/templates/mq-configmap-wait.yaml new file mode 100644 index 0000000..450b528 --- /dev/null +++ b/charts/netmaker/templates/mq-configmap-wait.yaml @@ -0,0 +1,28 @@ +--- +apiVersion: v1 +kind: ConfigMap +metadata: + labels: + app.kubernetes.io/instance: {{ include "netmaker.fullname" . }}-mqtt + app.kubernetes.io/name: {{ include "netmaker.fullname" . }}-mqtt + name: {{ include "netmaker.fullname" . }}-mqtt-wait +data: + wait.sh: | + #!/bin/ash + + encrypt_password() { + echo "${MQ_USERNAME}:${MQ_PASSWORD}" > /mosquitto/password.txt + mosquitto_passwd -U /mosquitto/password.txt + } + + main(){ + + encrypt_password + echo "Starting MQ..." + # Run the main container command. + /docker-entrypoint.sh + /usr/sbin/mosquitto -c /mosquitto/config/mosquitto.conf + + } + + main "${@}" diff --git a/charts/netmaker/templates/mq-configmap.yaml b/charts/netmaker/templates/mq-configmap.yaml deleted file mode 100644 index 215f3c7..0000000 --- a/charts/netmaker/templates/mq-configmap.yaml +++ /dev/null @@ -1,53 +0,0 @@ ---- -apiVersion: v1 -kind: ConfigMap -metadata: - labels: - app.kubernetes.io/instance: {{ include "netmaker.fullname" . }}-mqtt - app.kubernetes.io/name: {{ include "netmaker.fullname" . }}-mqtt - name: {{ include "netmaker.fullname" . }}-mqtt-wait -data: - wait.sh: | - #!/bin/ash - - wait_for_netmaker() { - echo "SERVER: ${NETMAKER_SERVER_HOST}" - until curl --output /dev/null --silent --fail --head \ - --location "${NETMAKER_SERVER_HOST}/api/server/health"; do - echo "Waiting for netmaker server to startup" - sleep 1 - done - } - - main(){ - # wait for netmaker to startup - apk add curl - wait_for_netmaker - echo "Starting MQ..." - # Run the main container command. - /docker-entrypoint.sh - /usr/sbin/mosquitto -c /mosquitto/config/mosquitto.conf - - } - - main "${@}" - ---- -apiVersion: v1 -kind: ConfigMap -metadata: - labels: - app.kubernetes.io/instance: {{ include "netmaker.fullname" . }}-mqtt - app.kubernetes.io/name: {{ include "netmaker.fullname" . }}-mqtt - name: {{ include "netmaker.fullname" . }}-mqtt-config -data: - mosquitto.conf: | - per_listener_settings false - listener 8883 - protocol websockets - allow_anonymous false - listener 1883 - protocol websockets - allow_anonymous false - plugin /usr/lib/mosquitto_dynamic_security.so - plugin_opt_config_file /mosquitto/data/dynamic-security.json From 0d001d2422666ff230c0d01fabc89b38bc8fd208 Mon Sep 17 00:00:00 2001 From: jessebot Date: Mon, 25 Mar 2024 08:25:58 +0100 Subject: [PATCH 36/40] first pass to get new env vars via configmaps and secrets sorted --- charts/netmaker/README.md | 48 ++++-- charts/netmaker/templates/confgmap.yaml | 37 ----- charts/netmaker/templates/db-secret.yaml | 16 ++ charts/netmaker/templates/mq-pvc.yaml | 13 -- charts/netmaker/templates/mq-secret.yaml | 13 ++ .../templates/netmaker-configmap.yaml | 33 ++++ .../templates/netmaker-oauth-secret.yaml | 17 +++ .../netmaker/templates/netmaker-secret.yaml | 18 +++ .../templates/netmaker-statefulset.yaml | 142 +++--------------- charts/netmaker/templates/secrets.yaml | 17 --- .../netmaker/templates/shared-data-pvc.yaml | 13 ++ charts/netmaker/templates/turn-secret.yaml | 16 ++ charts/netmaker/values.yaml | 89 ++++++++--- 13 files changed, 252 insertions(+), 220 deletions(-) delete mode 100644 charts/netmaker/templates/confgmap.yaml create mode 100644 charts/netmaker/templates/db-secret.yaml delete mode 100644 charts/netmaker/templates/mq-pvc.yaml create mode 100644 charts/netmaker/templates/mq-secret.yaml create mode 100644 charts/netmaker/templates/netmaker-configmap.yaml create mode 100644 charts/netmaker/templates/netmaker-oauth-secret.yaml create mode 100644 charts/netmaker/templates/netmaker-secret.yaml delete mode 100644 charts/netmaker/templates/secrets.yaml create mode 100644 charts/netmaker/templates/shared-data-pvc.yaml create mode 100644 charts/netmaker/templates/turn-secret.yaml diff --git a/charts/netmaker/README.md b/charts/netmaker/README.md index e1b2e01..fd3e663 100644 --- a/charts/netmaker/README.md +++ b/charts/netmaker/README.md @@ -55,10 +55,6 @@ A Helm chart to run HA Netmaker on Kubernetes | mq.ingress.host | string | `"broker.cluster.local"` | hostname for mqtt ingress | | mq.ingress.tls | list | `[]` | ingress tls list | | mq.password | string | `""` | | -| mq.persistence.accessMode | string | `"ReadWriteMany"` | | -| mq.persistence.existingClaim | string | `""` | name of existing PVC claim to use. if set, storageClassName is ignored | -| mq.persistence.storage | string | `"128Mi"` | | -| mq.persistence.storageClassName | string | `""` | | | mq.replicas | int | `1` | how many MQTT replicas to create | | mq.secretKey | string | `""` | name of key in existing secret to grab password from. If set, ignores mq.password | | mq.service.port | int | `443` | port for MQTT service | @@ -67,13 +63,29 @@ A Helm chart to run HA Netmaker on Kubernetes | mq.tolerations | object | `{}` | optional tolerations settings for mqtt | | mq.username | string | `"netmaker"` | | | nameOverride | string | `""` | override the name for netmaker objects | -| oauth.enabled | bool | `false` | | -| oauth.existingSecret | string | `""` | | -| oauth.provider | string | `"oidc"` | | -| oauth.secretKeys.clientID | string | `nil` | | -| oauth.secretKeys.clientSecret | string | `nil` | | -| oauth.secretKeys.frontendURL | string | `nil` | | -| oauth.secretKeys.issuer | string | `nil` | | +| netmaker.enterprise | object | `{"licenseKey":"","tenantId":""}` | if using enterprise edition fill out this section | +| netmaker.enterprise.licenseKey | string | `""` | netmaker enterprise license key, ignored if netmaker.existingSecret set | +| netmaker.enterprise.tenantId | string | `""` | netmaker enterprise tenant ID, ignored if netmaker.existingSecret set | +| netmaker.existingSecret | string | `""` | if set ignores netmaker.masterKey and enterprise.* | +| netmaker.jwtDuration | int | `43200` | Duration of JWT token validity in seconds | +| netmaker.masterKey | string | `"netmaker"` | ignored if netmaker.masterKeyExistingSecret is set | +| netmaker.oauth.azureTenant | string | `""` | azureTenant if using azure for oauth - ignored if netmaker.oauth.existingSecret is set | +| netmaker.oauth.clientID | string | `""` | client id if using oidc - ignored if netmaker.oauth.existingSecret is set | +| netmaker.oauth.clientSecret | string | `""` | client secret if using oidc - ignored if netmaker.oauth.existingSecret is set | +| netmaker.oauth.enabled | bool | `false` | | +| netmaker.oauth.existingSecret | string | `""` | | +| netmaker.oauth.issuer | string | `""` | oidc issuer - ignored if netmaker.oauth.existingSecret is set | +| netmaker.oauth.provider | string | `"oidc"` | AUTH_PROVIDER: must be one of: azure-ad|github|google|oidc | +| netmaker.oauth.secretKeys.azureTenant | string | `""` | | +| netmaker.oauth.secretKeys.clientID | string | `""` | | +| netmaker.oauth.secretKeys.clientSecret | string | `""` | | +| netmaker.oauth.secretKeys.frontendURL | string | `""` | | +| netmaker.oauth.secretKeys.issuer | string | `""` | | +| netmaker.racAutoDisable | string | `"true"` | Auto disable a user's connecteds clients bassed on JWT token expiration | +| netmaker.secretKeys.licenseKey | string | `""` | | +| netmaker.secretKeys.masterKey | string | `""` | | +| netmaker.secretKeys.tenantId | string | `""` | | +| netmaker.serverName | string | `"mynemakerhostname.tld"` | | | podAnnotations | object | `{}` | pod annotations to add | | podSecurityContext | object | `{}` | pod security contect to add | | postgresql.auth.database | string | `"netmaker"` | | @@ -89,7 +101,21 @@ A Helm chart to run HA Netmaker on Kubernetes | serviceAccount.create | bool | `true` | Specifies whether a service account should be created | | serviceAccount.name | string | `""` | Name of SA to use. If not set and create is true, a name is generated using the fullname template | | setIpForwarding.enabled | bool | `true` | | +| shared_data.persistence.accessMode | string | `"ReadWriteMany"` | | +| shared_data.persistence.existingClaim | string | `""` | name of existing PVC claim to use. if set, storageClassName is ignored | +| shared_data.persistence.storage | string | `"128Mi"` | | +| shared_data.persistence.storageClassName | string | `""` | | | tolerations | object | `{}` | optional tolerations settings for netmaker | +| turn.apiHost | string | `""` | | +| turn.enabled | bool | `false` | use an external turn server | +| turn.existingSecret | string | `""` | existing secret with turn server info | +| turn.host | string | `""` | | +| turn.password | string | `""` | | +| turn.secretKeys.apiHost | string | `""` | | +| turn.secretKeys.host | string | `""` | | +| turn.secretKeys.password | string | `""` | | +| turn.secretKeys.username | string | `""` | | +| turn.username | string | `""` | | | ui.ingress.annotations | object | `{}` | annotations for the netmaker UI ingress object | | ui.ingress.className | string | `"nginx"` | UI ingress className | | ui.ingress.enabled | bool | `true` | attempts to configure ingress if true | diff --git a/charts/netmaker/templates/confgmap.yaml b/charts/netmaker/templates/confgmap.yaml deleted file mode 100644 index 2ee9d3d..0000000 --- a/charts/netmaker/templates/confgmap.yaml +++ /dev/null @@ -1,37 +0,0 @@ -apiVersion: v1 -kind: ConfigMap -metadata: - name: {{ include "netmaker.fullname" . }} - labels: - {{- include "netmaker.labels" . | nindent 4 }} -data: - SERVER_NAME: {{ required "A valid .Values.mq.ingress.host entry required!" .Values.mq.ingress.host }} - SERVER_API_CONN_STRING: {{ required "A valid .Values.api.ingress.host entry required!" .Values.api.ingress.host }}:443 - SERVER_HTTP_HOST: {{ required "A valid .Values.api.ingress.host entry required!" .Values.api.ingress.host }} - API_PORT: "8081" - {{- if not .Values.wireguard.kernel }} - WG_QUICK_USERSPACE_IMPLEMENTATION: wireguard-go - {{- end }} - {{- if .Values.dns.enabled }} - DNS_MODE: "on" - COREDNS_ADDR: {{ required "A valid .Values.dns.clusterIP entry required! Choose an IP from your k8s service IP CIDR" .Values.dns.clusterIP }} - {{- else }} - DNS_MODE: "off" - {{- end }} - CLIENT_MODE: "on" - CORS_ALLOWED_ORIGIN: '*' - DATABASE: postgres - SQL_HOST: "{{ include "netmaker.database.host" . | trim }}" - SQL_PORT: "{{ include "netmaker.database.port" . }}" - SQL_USER: "{{ include "netmaker.database.username" . }}" - SQL_DB: "{{ include "netmaker.database.database" . }}" - DISPLAY_KEYS: "on" - MQ_HOST: {{ include "netmaker.fullname" . }}-mqtt - MQ_PORT: "{{ .Values.mq.service.port }}" - MQ_USERNAME: "{{ .Values.mq.username }}" - MQ_SERVER_PORT: "1883" - PLATFORM: "Kubernetes" - VERBOSITY: "3" - {{- if .Values.oauth.enabled }} - AUTH_PROVIDER: "{{ .Values.oauth.provider }}" - {{- end }} diff --git a/charts/netmaker/templates/db-secret.yaml b/charts/netmaker/templates/db-secret.yaml new file mode 100644 index 0000000..73e4c67 --- /dev/null +++ b/charts/netmaker/templates/db-secret.yaml @@ -0,0 +1,16 @@ +{{- if not .Values.postgresql.auth.existingSecret }} +apiVersion: v1 +kind: Secret +metadata: + name: db-secret + labels: + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/managed-by: {{ .Release.Service }} +type: Opaque +data: + SQL_PASS: {{ include "netmaker.database.password" . | b64enc | quote }} + SQL_HOST: {{ include "netmaker.database.host" . | trim | b64enc | quote }} + SQL_PORT: {{ include "netmaker.database.port" . | b64enc | quote }} + SQL_USER: {{ include "netmaker.database.username" . | b64enc | quote }} + SQL_DB: {{ include "netmaker.database.database" . | b64enc | quote }} +{{- end }} diff --git a/charts/netmaker/templates/mq-pvc.yaml b/charts/netmaker/templates/mq-pvc.yaml deleted file mode 100644 index 916b805..0000000 --- a/charts/netmaker/templates/mq-pvc.yaml +++ /dev/null @@ -1,13 +0,0 @@ ---- -{{- if not .Values.mq.persistence.existingClaim }} -kind: PersistentVolumeClaim -apiVersion: v1 -metadata: - name: {{ include "netmaker.fullname" . }}-shared-data -spec: - storageClassName: {{ .Values.mq.persistence.storageClassName }} - accessModes: [{{ .Values.mq.persistence.accessMode }}] - resources: - requests: - storage: {{ .Values.mq.persistence.storage }} -{{- end }} diff --git a/charts/netmaker/templates/mq-secret.yaml b/charts/netmaker/templates/mq-secret.yaml new file mode 100644 index 0000000..f7844ac --- /dev/null +++ b/charts/netmaker/templates/mq-secret.yaml @@ -0,0 +1,13 @@ +{{- if not .Values.mq.existingSecret }} +apiVersion: v1 +kind: Secret +metadata: + name: mq-secret + labels: + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/managed-by: {{ .Release.Service }} +type: Opaque +data: + MQ_PASSWORD: {{ .Values.mq.password | b64enc | quote }} + MQ_USERNAME: {{ .Values.mq.username | b64enc | quote }} +{{- end }} diff --git a/charts/netmaker/templates/netmaker-configmap.yaml b/charts/netmaker/templates/netmaker-configmap.yaml new file mode 100644 index 0000000..a94b87b --- /dev/null +++ b/charts/netmaker/templates/netmaker-configmap.yaml @@ -0,0 +1,33 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "netmaker.fullname" . }} + labels: + {{- include "netmaker.labels" . | nindent 4 }} +data: + BROKER_ENDPOINT: "wss://{{ required "A valid .Values.mq.ingress.host entry required!" .Values.mq.ingress.host }}" + SERVER_NAME: {{ required "A valid .Values.netmaker.serverName entry required!" .Values.netmaker.serverName }} + STUN_LIST: "stun1.netmaker.io:3478,stun2.netmaker.io:3478,stun1.l.google.com:19302,stun2.l.google.com:19302" + SERVER_HOST: "SERVER_PUBLIC_IP" + SERVER_API_CONN_STRING: "{{ required "A valid .Values.api.ingress.host entry required!" .Values.api.ingress.host }}:443" + COREDNS_ADDR: "SERVER_PUBLIC_IP" + DNS_MODE: "on" + SERVER_HTTP_HOST: {{ required "A valid .Values.api.ingress.host entry required!" .Values.api.ingress.host }} + API_PORT: "{{ .Values.api.service.port }}" + MESSAGEQUEUE_BACKEND: "on" + CORS_ALLOWED_ORIGIN: "*" + DISPLAY_KEYS: "on" + DATABASE: "{{ .Values.externalDatabase.type }}" + SERVER_BROKER_ENDPOINT: "ws://{{ .Release.Name }}-mqtt.{{ .Release.Namespace }}.svc.cluster.local:1883" + VERBOSITY: "1" + K8s: "true" + JWT_VALIDITY_DURATION: "{{ .Values.netmaker.jwtDuration }}" + RAC_AUTO_DISABLE: "{{ .Values.netmaker.racAutoDisable }}" + CACHING_ENABLED: "false" # should be false for HA to work + FRONTEND_URL: "https://{{ .Values.ui.ingress.host }}" + {{- if .Values.netmaker.oauth.enabled }} + AUTH_PROVIDER: "{{ .Values.netmaker.oauth.provider }}" + {{- end }} + {{- if .Values.turn.enabled -}} + USE_TURN: "true" + {{- end -}} diff --git a/charts/netmaker/templates/netmaker-oauth-secret.yaml b/charts/netmaker/templates/netmaker-oauth-secret.yaml new file mode 100644 index 0000000..1bbc05f --- /dev/null +++ b/charts/netmaker/templates/netmaker-oauth-secret.yaml @@ -0,0 +1,17 @@ +{{- if and .Values.netmaker.oauth.enabled (not .Values.netmaker.oauth.existingSecret) }} +apiVersion: v1 +kind: Secret +metadata: + name: netmaker-oauth-secret + labels: + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/managed-by: {{ .Release.Service }} +type: Opaque +data: + CLIENT_ID: {{ .Values.netmaker.oAuthclientID | b64enc | quote }} + CLIENT_SECRET: {{ .Values.netmaker.oAuthClientSecret | b64enc | quote }} + OIDC_ISSUER: {{ .Values.netmaker.oauth.issuer | b64enc | quote }} + {{- if .Values.netmaker.oauth.azureTenant }} + AZURE_TENANT: {{ .Values.netmaker.oauth.azureTenant | b64enc | quote }} + {{- end }} +{{- end }} diff --git a/charts/netmaker/templates/netmaker-secret.yaml b/charts/netmaker/templates/netmaker-secret.yaml new file mode 100644 index 0000000..67f8f4b --- /dev/null +++ b/charts/netmaker/templates/netmaker-secret.yaml @@ -0,0 +1,18 @@ +{{- if or .Values.netmaker.enterprise (not .Values.netmaker.existingSecret) }} +apiVersion: v1 +kind: Secret +metadata: + name: netmaker-secret + labels: + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/managed-by: {{ .Release.Service }} +type: Opaque +data: + {{- if not .Values.netmaker.existingSecret }} + MASTER_KEY: {{ include "netmaker.masterKey" . | b64enc | quote }} + {{- if .Values.netmaker.enterprise.licensekey }} + LICENSE_KEY: {{ .Values.netmaker.enterprise.licensekey | b64enc | quote }} + NETMAKER_TENANT_ID: {{ .Values.netmaker.enterprise.tenantId | b64enc | quote }} + {{- end }} + {{- end }} +{{- end }} diff --git a/charts/netmaker/templates/netmaker-statefulset.yaml b/charts/netmaker/templates/netmaker-statefulset.yaml index 7813fbd..5957cb3 100644 --- a/charts/netmaker/templates/netmaker-statefulset.yaml +++ b/charts/netmaker/templates/netmaker-statefulset.yaml @@ -23,134 +23,32 @@ spec: tolerations: {{- toYaml . | nindent 8 }} {{- end }} - initContainers: - - name: init-sysctl - image: busybox - imagePullPolicy: IfNotPresent - command: ["/bin/sh", "-c"] - args: ["sysctl -w net.ipv4.ip_forward=1 && sysctl -w net.ipv4.conf.all.src_valid_mark=1 && sysctl -w net.ipv6.conf.all.disable_ipv6=0 && sysctl -w net.ipv6.conf.all.forwarding=1"] - securityContext: - privileged: true - dnsPolicy: ClusterFirstWithHostNet containers: - - env: - - name: NODE_ID - valueFrom: - fieldRef: - apiVersion: v1 - fieldPath: metadata.name - {{- if or .Values.postgresql.auth.existingSecret .Values.externalDatabase.existingSecret }} - - name: SQL_PASS - valueFrom: - secretKeyRef: - {{- if .Values.postgresql.auth.existingSecret }} - name: {{ .Values.postgresql.auth.existingSecret }} - {{- else }} - name: {{ .Values.externalDatabase.existingSecret }} - {{- end }} - {{- if .Values.postgresql.auth.secretKeys.userPasswordKey }} - key: {{ .Values.postgresql.auth.secretKeys.userPasswordKey }} - {{- else }} - key: {{ .Values.externalDatabase.secretKeys.passwordKey }} - {{- end }} - {{- end }} - {{- if .Values.mq.existingSecret }} - - name: MQ_PASSWORD - valueFrom: - secretKeyRef: - name: {{ .Values.mq.existingSecret }} - key: {{ .Values.mq.secretKey }} - - name: MQ_ADMIN_PASSWORD - valueFrom: - secretKeyRef: - name: {{ .Values.mq.existingSecret }} - key: {{ .Values.mq.secretKey }} - {{- end }} - {{- if .Values.oauth.enabled }} - - name: SERVER_BROKER_ENDPOINT - valueFrom: - secretKeyRef: - name: {{ .Values.oauth.existingSecret }} - key: {{ .Values.oauth.secretKeys.serverBrokerEndpoint }} - - - name: SERVER_HTTP_HOST - valueFrom: - secretKeyRef: - name: {{ .Values.oauth.existingSecret }} - key: {{ .Values.oauth.secretKeys.serverHttpHost }} - - name: AUTH_PROVIDER - valueFrom: - secretKeyRef: - name: {{ .Values.oauth.existingSecret }} - key: {{ .Values.oauth.secretKeys.auth_provider }} - - name: CLIENT_ID - valueFrom: - secretKeyRef: - name: {{ .Values.oauth.existingSecret }} - key: {{ .Values.oauth.secretKeys.clientID }} - - name: CLIENT_SECRET - valueFrom: - secretKeyRef: - name: {{ .Values.oauth.existingSecret }} - key: {{ .Values.oauth.secretKeys.clientSecret }} - - name: OIDC_ISSUER - valueFrom: - secretKeyRef: - name: {{ .Values.oauth.existingSecret }} - key: {{ .Values.oauth.secretKeys.issuer }} - - name: FRONTEND_URL - valueFrom: - secretKeyRef: - name: {{ .Values.oauth.existingSecret }} - key: {{ .Values.oauth.secretKeys.frontendURL }} - {{- if .Values.oauth.secretKeys.azureTenant }} - - name: AZURE_TENANT - valueFrom: - secretKeyRef: - name: {{ .Values.oauth.existingSecret }} - key: {{ .Values.oauth.secretKeys.azureTenant }} - {{- end }} - {{- end }} - envFrom: - - configMapRef: - name: {{ include "netmaker.fullname" . }} - - secretRef: - name: netmaker-secret - image: {{ include "netmaker.image" . }} - imagePullPolicy: {{ .Values.image.pullPolicy }} - name: {{ include "netmaker.fullname" . }} - ports: - - containerPort: {{ .Values.api.service.port }} - protocol: TCP - {{- if .Values.wireguard.enabled }} - {{ $count := (add .Values.wireguard.networkLimit 1 | int) }} - {{- range untilStep 1 $count 1 }} - - containerPort: {{ add 31820 . }} - protocol: UDP - {{- end }} - {{- end }} - securityContext: - capabilities: - add: - - NET_ADMIN - - NET_RAW - - SYS_MODULE - volumeMounts: - - mountPath: /etc/netmaker/ - name: shared-data - {{- if .Values.dns.enabled }} - - name: dns-pvc - mountPath: /root/config/dnsconfig - {{- end }} + - name: {{ include "netmaker.fullname" . }} + image: {{ include "netmaker.image" . }} + imagePullPolicy: {{ .Values.image.pullPolicy }} + envFrom: + - configMapRef: + name: {{ include "netmaker.fullname" . }}-env + ports: + - containerPort: {{ .Values.api.service.port }} + protocol: TCP + {{- if .Values.dns.enabled }} + volumeMounts: + - mountPath: /etc/netmaker/ + name: shared-data + - name: dns-pvc + mountPath: /root/config/dnsconfig + {{- end }} {{/* end dns.enabled check for volumeMounts */}} + {{- if .Values.dns.enabled }} volumes: - name: shared-data persistentVolumeClaim: - {{- if .Values.mq.persistence.existingClaim }} - claimName: {{ .Values.mq.persistence.existingClaim }} + {{- if .Values.shared_data.persistence.existingClaim }} + claimName: {{ .Values.shared_data.persistence.existingClaim }} {{- else }} claimName: {{ include "netmaker.fullname" . }}-shared-data {{- end }} - {{- if .Values.dns.enabled }} - name: dns-pvc persistentVolumeClaim: {{- if .Values.dns.persistence.existingClaim }} @@ -158,4 +56,4 @@ spec: {{- else }} claimName: {{ include "netmaker.fullname" . }}-dns {{- end }} - {{- end }} {{/* end if dns.enabled */}} + {{- end }} {{/* end if dns.enabled for volumes */}} diff --git a/charts/netmaker/templates/secrets.yaml b/charts/netmaker/templates/secrets.yaml deleted file mode 100644 index 2cb6766..0000000 --- a/charts/netmaker/templates/secrets.yaml +++ /dev/null @@ -1,17 +0,0 @@ -apiVersion: v1 -kind: Secret -metadata: - name: netmaker-secret - labels: - app.kubernetes.io/instance: {{ .Release.Name }} - app.kubernetes.io/managed-by: {{ .Release.Service }} -type: Opaque -data: - {{- if not .Values.postgresql.auth.existingSecret }} - SQL_PASS: {{ include "netmaker.database.password" . | b64enc | quote }} - {{- end }} - {{- if not .Values.mq.existingSecret }} - MQ_ADMIN_PASSWORD: {{ .Values.mq.password | b64enc | quote }} - MQ_PASSWORD: {{ .Values.mq.password | b64enc | quote }} - {{- end }} - MASTER_KEY: {{ include "netmaker.masterKey" . | b64enc | quote }} diff --git a/charts/netmaker/templates/shared-data-pvc.yaml b/charts/netmaker/templates/shared-data-pvc.yaml new file mode 100644 index 0000000..a573aab --- /dev/null +++ b/charts/netmaker/templates/shared-data-pvc.yaml @@ -0,0 +1,13 @@ +--- +{{- if and .Values.dns.enabled (not .Values.shared_data.persistence.existingClaim) }} +kind: PersistentVolumeClaim +apiVersion: v1 +metadata: + name: {{ include "netmaker.fullname" . }}-shared-data +spec: + storageClassName: {{ .Values.shared_data.persistence.storageClassName }} + accessModes: [{{ .Values.shared_data.persistence.accessMode }}] + resources: + requests: + storage: {{ .Values.shared_data.persistence.storage }} +{{- end }} diff --git a/charts/netmaker/templates/turn-secret.yaml b/charts/netmaker/templates/turn-secret.yaml new file mode 100644 index 0000000..d2c65f7 --- /dev/null +++ b/charts/netmaker/templates/turn-secret.yaml @@ -0,0 +1,16 @@ +{{- if and .Values.turn.enabled (not .Values.turn.existingSecret) -}} +apiVersion: v1 +kind: Secret +metadata: + name: turn-secret + labels: + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/managed-by: {{ .Release.Service }} +type: Opaque +data: + TURN_SERVER_HOST: {{ .Values.turn.host | b64enc | quote }} + TURN_SERVER_API_HOST: {{ .Values.turn.apiHost | b64enc | quote }} + TURN_PORT: {{ .Values.turn.port | b64enc | quote }} + TURN_USERNAME: {{ .Values.turn.username | b64enc | quote }} + TURN_PASSWORD: {{ .Values.turn.password | b64enc | quote }} +{{- end -}} diff --git a/charts/netmaker/values.yaml b/charts/netmaker/values.yaml index f9dc452..1ce299b 100644 --- a/charts/netmaker/values.yaml +++ b/charts/netmaker/values.yaml @@ -49,6 +49,55 @@ podAnnotations: {} podSecurityContext: {} # fsGroup: 2000 +netmaker: + serverName: "mynemakerhostname.tld" + # -- ignored if netmaker.masterKeyExistingSecret is set + masterKey: "netmaker" + # -- Duration of JWT token validity in seconds + jwtDuration: 43200 + # -- Auto disable a user's connecteds clients bassed on JWT token expiration + racAutoDisable: "true" + + # -- if using enterprise edition fill out this section + enterprise: + # -- netmaker enterprise license key, ignored if netmaker.existingSecret set + licenseKey: "" + # -- netmaker enterprise tenant ID, ignored if netmaker.existingSecret set + tenantId: "" + + # -- if set ignores netmaker.masterKey and enterprise.* + existingSecret: "" + secretKeys: + licenseKey: "" + tenantId: "" + masterKey: "" + + # OAuth section + oauth: + enabled: false + # -- AUTH_PROVIDER: must be one of: azure-ad|github|google|oidc + provider: "oidc" + # -- oidc issuer - ignored if netmaker.oauth.existingSecret is set + issuer: "" + # -- client id if using oidc - ignored if netmaker.oauth.existingSecret is set + clientID: "" + # -- client secret if using oidc - ignored if netmaker.oauth.existingSecret is set + clientSecret: "" + # -- azureTenant if using azure for oauth - ignored if netmaker.oauth.existingSecret is set + azureTenant: "" + existingSecret: "" + secretKeys: + # CLIENT_ID - client id of your oauth provider + clientID: "" + # CLIENT_SECRET - client secret of your oauth provider + clientSecret: "" + # FRONTEND_URL - https://dashboard. <-- untested + frontendURL: "" + # OIDC_ISSUER - https://oidc.yourprovider.com - URL of oidc provider + issuer: "" + # AZURE_TENANT - only for azure, you may optionally specify the tenant for the OAuth + azureTenant: "" + api: service: # -- type for netmaker server services @@ -122,12 +171,6 @@ mq: existingSecret: '' # -- name of key in existing secret to grab password from. If set, ignores mq.password secretKey: '' - persistence: - # -- name of existing PVC claim to use. if set, storageClassName is ignored - existingClaim: "" - accessMode: "ReadWriteMany" - storageClassName: "" - storage: 128Mi service: # -- type for netmaker server services type: ClusterIP @@ -164,6 +207,14 @@ dns: storageClassName: "" accessMode: ReadWriteOnce +shared_data: + persistence: + # -- name of existing PVC claim to use. if set, storageClassName is ignored + existingClaim: "" + accessMode: "ReadWriteMany" + storageClassName: "" + storage: 128Mi + setIpForwarding: enabled: true @@ -226,20 +277,18 @@ externalDatabase: secretKeys: passwordKey: "" -# OAuth section - untested -oauth: +turn: + # -- use an external turn server enabled: false - # AUTH_PROVIDER: must be one of: azure-ad|github|google|oidc - provider: "oidc" + host: "" + apiHost: "" + username: "" + password: "" + # -- existing secret with turn server info existingSecret: "" + # keys for the existing turn server secret secretKeys: - # CLIENT_ID - client id of your oauth provider - clientID: - # CLIENT_SECRET - client secret of your oauth provider - clientSecret: - # FRONTEND_URL - https://dashboard. <-- untested - frontendURL: - # OIDC_ISSUER - https://oidc.yourprovider.com - URL of oidc provider - issuer: - # AZURE_TENANT - only for azure, you may optionally specify the tenant for the OAuth - # azureTenant: + host: "" + apiHost: "" + username: "" + password: "" From 7c983f0616a4a0b14e12a9babe74c8457cefc279 Mon Sep 17 00:00:00 2001 From: jessebot Date: Mon, 25 Mar 2024 08:51:06 +0100 Subject: [PATCH 37/40] update secret keys mess to always just be env vars all around --- charts/netmaker/README.md | 28 +++-------- charts/netmaker/templates/_helpers.tpl | 46 +++++++++++++++++ .../templates/netmaker-configmap.yaml | 2 +- .../templates/netmaker-statefulset.yaml | 12 +++++ charts/netmaker/values.yaml | 50 +++++-------------- 5 files changed, 78 insertions(+), 60 deletions(-) diff --git a/charts/netmaker/README.md b/charts/netmaker/README.md index fd3e663..b83a52b 100644 --- a/charts/netmaker/README.md +++ b/charts/netmaker/README.md @@ -36,18 +36,17 @@ A Helm chart to run HA Netmaker on Kubernetes | dns.persistence.storage | string | `"1Gi"` | | | dns.persistence.storageClassName | string | `""` | | | externalDatabase.database | string | `"netmaker"` | postgress db | -| externalDatabase.existingSecret | string | `""` | | +| externalDatabase.existingSecret | string | `""` | use existing secret for netmaker db credentials, must have the following keys: SQL_PASS, SQL_HOST, SQL_PORT, SQL_USER, SQL_DB | | externalDatabase.host | string | `"external.postgres.url"` | postgres host | | externalDatabase.password | string | `""` | postgres pass for netmaker user. ignored if existingSecret is set | | externalDatabase.port | int | `5432` | postgres hosts port | -| externalDatabase.secretKeys.passwordKey | string | `""` | | | externalDatabase.type | string | `"postgresql"` | | | externalDatabase.username | string | `"netmaker"` | postgres username | | fullnameOverride | string | `""` | override the full name for netmaker objects | | image.pullPolicy | string | `"IfNotPresent"` | Pull Policy for images | | image.repository | string | `"gravitl/netmaker"` | The image repo to pull Netmaker image from | | mq.affinity | object | `{}` | optional affinity settings for mqtt | -| mq.existingSecret | string | `""` | name of an existing secret to use for mq password. If set, ignores mq.password | +| mq.existingSecret | string | `""` | name of an existing secret to use for mq password. If set, ignores mq.password, mq.username secret keys must be: MQ_PASSWORD, MQ_USERNAME | | mq.generateCert | bool | `false` | | | mq.ingress.annotations | object | `{}` | annotations for the mqtt ingress object | | mq.ingress.className | string | `"nginx"` | | @@ -56,7 +55,6 @@ A Helm chart to run HA Netmaker on Kubernetes | mq.ingress.tls | list | `[]` | ingress tls list | | mq.password | string | `""` | | | mq.replicas | int | `1` | how many MQTT replicas to create | -| mq.secretKey | string | `""` | name of key in existing secret to grab password from. If set, ignores mq.password | | mq.service.port | int | `443` | port for MQTT service | | mq.service.targetPort | int | `8883` | Target port for MQTT service | | mq.service.type | string | `"ClusterIP"` | type for netmaker server services | @@ -66,34 +64,24 @@ A Helm chart to run HA Netmaker on Kubernetes | netmaker.enterprise | object | `{"licenseKey":"","tenantId":""}` | if using enterprise edition fill out this section | | netmaker.enterprise.licenseKey | string | `""` | netmaker enterprise license key, ignored if netmaker.existingSecret set | | netmaker.enterprise.tenantId | string | `""` | netmaker enterprise tenant ID, ignored if netmaker.existingSecret set | -| netmaker.existingSecret | string | `""` | if set ignores netmaker.masterKey and enterprise.* | +| netmaker.existingSecret | string | `""` | if set ignores netmaker.masterKey and enterprise.* must have key called MASTER_KEY, optionally for enterprise must have key: LICENSE_KEY, NETMAKER_TENANT_ID | | netmaker.jwtDuration | int | `43200` | Duration of JWT token validity in seconds | | netmaker.masterKey | string | `"netmaker"` | ignored if netmaker.masterKeyExistingSecret is set | | netmaker.oauth.azureTenant | string | `""` | azureTenant if using azure for oauth - ignored if netmaker.oauth.existingSecret is set | | netmaker.oauth.clientID | string | `""` | client id if using oidc - ignored if netmaker.oauth.existingSecret is set | | netmaker.oauth.clientSecret | string | `""` | client secret if using oidc - ignored if netmaker.oauth.existingSecret is set | | netmaker.oauth.enabled | bool | `false` | | -| netmaker.oauth.existingSecret | string | `""` | | +| netmaker.oauth.existingSecret | string | `""` | existing secret for oauth, must have the following keys: CLIENT_ID, CLIENT_SECRET, OIDC_ISSUER, and optionally AZURE_TENANT ignores plane text values if this is set | | netmaker.oauth.issuer | string | `""` | oidc issuer - ignored if netmaker.oauth.existingSecret is set | | netmaker.oauth.provider | string | `"oidc"` | AUTH_PROVIDER: must be one of: azure-ad|github|google|oidc | -| netmaker.oauth.secretKeys.azureTenant | string | `""` | | -| netmaker.oauth.secretKeys.clientID | string | `""` | | -| netmaker.oauth.secretKeys.clientSecret | string | `""` | | -| netmaker.oauth.secretKeys.frontendURL | string | `""` | | -| netmaker.oauth.secretKeys.issuer | string | `""` | | | netmaker.racAutoDisable | string | `"true"` | Auto disable a user's connecteds clients bassed on JWT token expiration | -| netmaker.secretKeys.licenseKey | string | `""` | | -| netmaker.secretKeys.masterKey | string | `""` | | -| netmaker.secretKeys.tenantId | string | `""` | | | netmaker.serverName | string | `"mynemakerhostname.tld"` | | | podAnnotations | object | `{}` | pod annotations to add | | podSecurityContext | object | `{}` | pod security contect to add | | postgresql.auth.database | string | `"netmaker"` | | -| postgresql.auth.existingSecret | string | `""` | | +| postgresql.auth.existingSecret | string | `""` | use existing secret for netmaker db credentials, must have the following keys: SQL_PASS, SQL_HOST, SQL_PORT, SQL_USER, SQL_DB | | postgresql.auth.password | string | `""` | | | postgresql.auth.primary.persistence.enabled | bool | `true` | | -| postgresql.auth.secretKeys.adminPasswordKey | string | `""` | | -| postgresql.auth.secretKeys.userPasswordKey | string | `""` | | | postgresql.auth.username | string | `"netmaker"` | | | postgresql.enabled | bool | `true` | | | replicas | int | `1` | number of netmaker server replicas to create | @@ -108,13 +96,9 @@ A Helm chart to run HA Netmaker on Kubernetes | tolerations | object | `{}` | optional tolerations settings for netmaker | | turn.apiHost | string | `""` | | | turn.enabled | bool | `false` | use an external turn server | -| turn.existingSecret | string | `""` | existing secret with turn server info | +| turn.existingSecret | string | `""` | existing secret with turn server info. Must have the following keys: TURN_SERVER_HOST, TURN_SERVER_API_HOST, TURN_PORT, TURN_USERNAME, TURN_PASSWORD | | turn.host | string | `""` | | | turn.password | string | `""` | | -| turn.secretKeys.apiHost | string | `""` | | -| turn.secretKeys.host | string | `""` | | -| turn.secretKeys.password | string | `""` | | -| turn.secretKeys.username | string | `""` | | | turn.username | string | `""` | | | ui.ingress.annotations | object | `{}` | annotations for the netmaker UI ingress object | | ui.ingress.className | string | `"nginx"` | UI ingress className | diff --git a/charts/netmaker/templates/_helpers.tpl b/charts/netmaker/templates/_helpers.tpl index 72ce1e3..7fb9fec 100644 --- a/charts/netmaker/templates/_helpers.tpl +++ b/charts/netmaker/templates/_helpers.tpl @@ -144,3 +144,49 @@ Database for postgresql {{- index .Values "externalDatabase" "database" }} {{- end }} {{- end }} + +{{/* +netmaker db secret +*/}} +{{- define "netmaker.db.secret" -}} +{{- if .Values.postgresql.auth.existingSecret }} +{{ print "%s" .Values.postgresql.auth.existingSecret }} +{{- else if .Values.externalDatabase.existingSecret }} +{{ print "%s" .Values.externalDatabase.existingSecret }} +{{- else }} +{{ print "db-secret" }} +{{- end }} +{{- end }} + +{{/* +netmaker secret +*/}} +{{- define "netmaker.secret" -}} +{{- if .Values.netmaker.existingSecret }} +{{ print "%s" .Values.netmaker.existingSecret }} +{{- else }} +{{ print "netmaker-secret" }} +{{- end }} +{{- end }} + +{{/* +netmaker oauth secret +*/}} +{{- define "netmaker.oauth.secret" -}} +{{- if .Values.netmaker.oauth.existingSecret }} +{{ print "%s" .Values.netmaker.oauth.existingSecret }} +{{- else }} +{{ print "netmaker-oauth-secret" }} +{{- end }} +{{- end }} + +{{/* +netmaker turn secret +*/}} +{{- define "netmaker.turn.secret" -}} +{{- if .Values.netmaker.turn.existingSecret }} +{{ print "%s" .Values.netmaker.turn.existingSecret }} +{{- else }} +{{ print "turn-secret" }} +{{- end }} +{{- end }} diff --git a/charts/netmaker/templates/netmaker-configmap.yaml b/charts/netmaker/templates/netmaker-configmap.yaml index a94b87b..ac8e823 100644 --- a/charts/netmaker/templates/netmaker-configmap.yaml +++ b/charts/netmaker/templates/netmaker-configmap.yaml @@ -1,7 +1,7 @@ apiVersion: v1 kind: ConfigMap metadata: - name: {{ include "netmaker.fullname" . }} + name: {{ include "netmaker.fullname" . }}-env labels: {{- include "netmaker.labels" . | nindent 4 }} data: diff --git a/charts/netmaker/templates/netmaker-statefulset.yaml b/charts/netmaker/templates/netmaker-statefulset.yaml index 5957cb3..90b1976 100644 --- a/charts/netmaker/templates/netmaker-statefulset.yaml +++ b/charts/netmaker/templates/netmaker-statefulset.yaml @@ -30,6 +30,18 @@ spec: envFrom: - configMapRef: name: {{ include "netmaker.fullname" . }}-env + - secretRef: + name: {{ include "netmaker.secret" . }} + {{- if .Values.netmaker.oauth.enabled }} + - secretRef: + name: {{ include "netmaker.oauth.secret" . }} + {{- end -}} + - secretRef: + name: {{ include "netmaker.db.secret" . }} + {{- if .Values.turn.enabled -}} + - secretRef: + name: {{ include "netmaker.turn.secret" . }} + {{- end -}} ports: - containerPort: {{ .Values.api.service.port }} protocol: TCP diff --git a/charts/netmaker/values.yaml b/charts/netmaker/values.yaml index 1ce299b..fa5a0ba 100644 --- a/charts/netmaker/values.yaml +++ b/charts/netmaker/values.yaml @@ -66,11 +66,9 @@ netmaker: tenantId: "" # -- if set ignores netmaker.masterKey and enterprise.* + # must have key called MASTER_KEY, optionally for enterprise must have key: + # LICENSE_KEY, NETMAKER_TENANT_ID existingSecret: "" - secretKeys: - licenseKey: "" - tenantId: "" - masterKey: "" # OAuth section oauth: @@ -85,18 +83,10 @@ netmaker: clientSecret: "" # -- azureTenant if using azure for oauth - ignored if netmaker.oauth.existingSecret is set azureTenant: "" + # -- existing secret for oauth, must have the following keys: + # CLIENT_ID, CLIENT_SECRET, OIDC_ISSUER, and optionally AZURE_TENANT + # ignores plane text values if this is set existingSecret: "" - secretKeys: - # CLIENT_ID - client id of your oauth provider - clientID: "" - # CLIENT_SECRET - client secret of your oauth provider - clientSecret: "" - # FRONTEND_URL - https://dashboard. <-- untested - frontendURL: "" - # OIDC_ISSUER - https://oidc.yourprovider.com - URL of oidc provider - issuer: "" - # AZURE_TENANT - only for azure, you may optionally specify the tenant for the OAuth - azureTenant: "" api: service: @@ -167,10 +157,9 @@ mq: # - "true" username: netmaker password: '' - # -- name of an existing secret to use for mq password. If set, ignores mq.password + # -- name of an existing secret to use for mq password. If set, ignores mq.password, mq.username + # secret keys must be: MQ_PASSWORD, MQ_USERNAME existingSecret: '' - # -- name of key in existing secret to grab password from. If set, ignores mq.password - secretKey: '' service: # -- type for netmaker server services type: ClusterIP @@ -243,16 +232,9 @@ postgresql: # be rotated on each upgrade: # https://github.com/bitnami/charts/tree/main/bitnami/postgresql#upgrade password: "" - # Set the password for the "postgres" admin user - # set this to the same value as above if you've previously installed - # this chart and you're having problems getting mastodon to connect to the DB - # postgresPassword: "" - # you can also specify the name of an existing Secret - # with a key of password set to the password you want + # -- use existing secret for netmaker db credentials, must have the following keys: + # SQL_PASS, SQL_HOST, SQL_PORT, SQL_USER, SQL_DB existingSecret: "" - secretKeys: - userPasswordKey: "" - adminPasswordKey: "" primary: persistence: enabled: true @@ -272,10 +254,9 @@ externalDatabase: password: "" # -- postgress db database: netmaker - # use existing secret for netmaker user's password + # -- use existing secret for netmaker db credentials, must have the following keys: + # SQL_PASS, SQL_HOST, SQL_PORT, SQL_USER, SQL_DB existingSecret: "" - secretKeys: - passwordKey: "" turn: # -- use an external turn server @@ -284,11 +265,6 @@ turn: apiHost: "" username: "" password: "" - # -- existing secret with turn server info + # -- existing secret with turn server info. Must have the following keys: + # TURN_SERVER_HOST, TURN_SERVER_API_HOST, TURN_PORT, TURN_USERNAME, TURN_PASSWORD existingSecret: "" - # keys for the existing turn server secret - secretKeys: - host: "" - apiHost: "" - username: "" - password: "" From 5c27b3bdc4b3b43923ff286aebaa1b349cc887b3 Mon Sep 17 00:00:00 2001 From: jessebot Date: Mon, 25 Mar 2024 09:13:41 +0100 Subject: [PATCH 38/40] remove wireguard service --- charts/netmaker/README.md | 5 ---- .../netmaker/templates/wireguard-service.yaml | 25 ------------------- charts/netmaker/values.yaml | 11 -------- 3 files changed, 41 deletions(-) delete mode 100644 charts/netmaker/templates/wireguard-service.yaml diff --git a/charts/netmaker/README.md b/charts/netmaker/README.md index b83a52b..efc970d 100644 --- a/charts/netmaker/README.md +++ b/charts/netmaker/README.md @@ -109,11 +109,6 @@ A Helm chart to run HA Netmaker on Kubernetes | ui.service.port | int | `80` | port for UI service | | ui.service.targetport | int | `80` | target port for UI service | | ui.service.type | string | `"ClusterIP"` | type for netmaker server services | -| wireguard.enabled | bool | `true` | whether or not to use WireGuard on server | -| wireguard.kernel | bool | `false` | whether or not to use Kernel WG (should be false unless WireGuard is installed on hosts). | -| wireguard.networkLimit | int | `10` | max number of networks that Netmaker will support if running with WireGuard enabled | -| wireguard.service.annotations | object | `{}` | | -| wireguard.service.type | string | `"NodePort"` | | ---------------------------------------------- Autogenerated from chart metadata using [helm-docs v1.13.1](https://github.com/norwoodj/helm-docs/releases/v1.13.1) diff --git a/charts/netmaker/templates/wireguard-service.yaml b/charts/netmaker/templates/wireguard-service.yaml deleted file mode 100644 index c54304d..0000000 --- a/charts/netmaker/templates/wireguard-service.yaml +++ /dev/null @@ -1,25 +0,0 @@ ---- -apiVersion: v1 -kind: Service -metadata: - labels: - {{- include "netmaker.labels" . | nindent 4 }} - name: '{{ include "netmaker.fullname" . }}-wireguard' - {{- with .Values.wireguard.service.annotations }} - annotations: - {{- toYaml . | nindent 4 }} - {{- end }} -spec: - externalTrafficPolicy: Local - type: {{ .Values.wireguard.service.type }} - ports: - {{ $count := (add .Values.wireguard.networkLimit 1 | int) }} - {{- range untilStep 1 $count 1 }} - - port: {{ add 31820 . }} - nodePort: {{ add 31820 . }} - protocol: UDP - targetPort: {{ add 31820 . }} - name: wg-iface-{{ add 31820 . }} - {{- end }} - selector: - app: '{{ include "netmaker.fullname" . }}' diff --git a/charts/netmaker/values.yaml b/charts/netmaker/values.yaml index fa5a0ba..1cfbb81 100644 --- a/charts/netmaker/values.yaml +++ b/charts/netmaker/values.yaml @@ -207,17 +207,6 @@ shared_data: setIpForwarding: enabled: true -wireguard: - # -- whether or not to use WireGuard on server - enabled: true - service: - annotations: {} - type: NodePort - # -- whether or not to use Kernel WG (should be false unless WireGuard is installed on hosts). - kernel: false - # -- max number of networks that Netmaker will support if running with WireGuard enabled - networkLimit: 10 - # https://github.com/bitnami/charts/tree/main/bitnami/postgresql#parameters postgresql: # set to false if you want to use an existing postgres server. From 28af460bb8b12c7a1f4ba4334bc4d563495a3b23 Mon Sep 17 00:00:00 2001 From: jessebot Date: Mon, 25 Mar 2024 10:25:51 +0100 Subject: [PATCH 39/40] add better default value for netmaker and don't worry about storage class in ci --- .github/workflows/ci-helm-lint-test.yml | 15 ++++----------- charts/netmaker/README.md | 2 +- charts/netmaker/values.yaml | 2 +- 3 files changed, 6 insertions(+), 13 deletions(-) diff --git a/.github/workflows/ci-helm-lint-test.yml b/.github/workflows/ci-helm-lint-test.yml index 3ef97d2..ff841da 100644 --- a/.github/workflows/ci-helm-lint-test.yml +++ b/.github/workflows/ci-helm-lint-test.yml @@ -41,19 +41,12 @@ jobs: if: steps.list-changed.outputs.changed == 'true' run: ct lint --target-branch ${{ github.event.repository.default_branch }} - - name: Create K3s cluster - uses: debianmaster/actions-k3s@master - id: k3s - with: - version: 'latest' - - - name: Install longhorn - run: | - kubectl apply -f https://raw.githubusercontent.com/longhorn/longhorn/v1.6.0/deploy/longhorn.yaml + - name: Create kind cluster + uses: helm/kind-action@v1.9.0 + if: steps.list-changed.outputs.changed == 'true' - name: Run chart-testing (install) id: install if: steps.list-changed.outputs.changed == 'true' - # install the chart using longhorn pvcs run: | - ct install --target-branch ${{ github.event.repository.default_branch }} --helm-extra-set-args "--set=mq.storageClassName=longhorn --set=dns.storageClassName=longhorn" + ct install --target-branch ${{ github.event.repository.default_branch }} diff --git a/charts/netmaker/README.md b/charts/netmaker/README.md index efc970d..ec3fc67 100644 --- a/charts/netmaker/README.md +++ b/charts/netmaker/README.md @@ -75,7 +75,7 @@ A Helm chart to run HA Netmaker on Kubernetes | netmaker.oauth.issuer | string | `""` | oidc issuer - ignored if netmaker.oauth.existingSecret is set | | netmaker.oauth.provider | string | `"oidc"` | AUTH_PROVIDER: must be one of: azure-ad|github|google|oidc | | netmaker.racAutoDisable | string | `"true"` | Auto disable a user's connecteds clients bassed on JWT token expiration | -| netmaker.serverName | string | `"mynemakerhostname.tld"` | | +| netmaker.serverName | string | `"cluster.local"` | | | podAnnotations | object | `{}` | pod annotations to add | | podSecurityContext | object | `{}` | pod security contect to add | | postgresql.auth.database | string | `"netmaker"` | | diff --git a/charts/netmaker/values.yaml b/charts/netmaker/values.yaml index 1cfbb81..fe853c0 100644 --- a/charts/netmaker/values.yaml +++ b/charts/netmaker/values.yaml @@ -50,7 +50,7 @@ podSecurityContext: {} # fsGroup: 2000 netmaker: - serverName: "mynemakerhostname.tld" + serverName: "cluster.local" # -- ignored if netmaker.masterKeyExistingSecret is set masterKey: "netmaker" # -- Duration of JWT token validity in seconds From 3a9cf14fb60ec537de02512f15ec8fa8f42eef63 Mon Sep 17 00:00:00 2001 From: jessebot Date: Mon, 25 Mar 2024 10:45:05 +0100 Subject: [PATCH 40/40] fix secret name determination --- charts/netmaker/templates/_helpers.tpl | 47 ++++++++++++------- charts/netmaker/templates/mq-deployment.yaml | 20 ++++++-- .../templates/netmaker-statefulset.yaml | 10 ++-- 3 files changed, 52 insertions(+), 25 deletions(-) diff --git a/charts/netmaker/templates/_helpers.tpl b/charts/netmaker/templates/_helpers.tpl index 7fb9fec..b808c9c 100644 --- a/charts/netmaker/templates/_helpers.tpl +++ b/charts/netmaker/templates/_helpers.tpl @@ -149,12 +149,12 @@ Database for postgresql netmaker db secret */}} {{- define "netmaker.db.secret" -}} -{{- if .Values.postgresql.auth.existingSecret }} -{{ print "%s" .Values.postgresql.auth.existingSecret }} -{{- else if .Values.externalDatabase.existingSecret }} -{{ print "%s" .Values.externalDatabase.existingSecret }} -{{- else }} -{{ print "db-secret" }} +{{- if .Values.postgresql.auth.existingSecret -}} +{{ .Values.postgresql.auth.existingSecret }} +{{- else if .Values.externalDatabase.existingSecret -}} +{{ .Values.externalDatabase.existingSecret }} +{{- else -}} +db-secret {{- end }} {{- end }} @@ -162,10 +162,21 @@ netmaker db secret netmaker secret */}} {{- define "netmaker.secret" -}} -{{- if .Values.netmaker.existingSecret }} -{{ print "%s" .Values.netmaker.existingSecret }} -{{- else }} -{{ print "netmaker-secret" }} +{{- if .Values.netmaker.existingSecret -}} +{{ .Values.netmaker.existingSecret }} +{{- else -}} +netmaker-secret +{{- end }} +{{- end }} + +{{/* +mqtt (broker) secret +*/}} +{{- define "netmaker.mq.secret" -}} +{{- if .Values.mq.existingSecret -}} +{{ .Values.mq.existingSecret }} +{{- else -}} +mq-secret {{- end }} {{- end }} @@ -173,10 +184,10 @@ netmaker secret netmaker oauth secret */}} {{- define "netmaker.oauth.secret" -}} -{{- if .Values.netmaker.oauth.existingSecret }} -{{ print "%s" .Values.netmaker.oauth.existingSecret }} -{{- else }} -{{ print "netmaker-oauth-secret" }} +{{- if .Values.netmaker.oauth.existingSecret -}} +{{ .Values.netmaker.oauth.existingSecret }} +{{- else -}} +netmaker-oauth-secret {{- end }} {{- end }} @@ -184,9 +195,9 @@ netmaker oauth secret netmaker turn secret */}} {{- define "netmaker.turn.secret" -}} -{{- if .Values.netmaker.turn.existingSecret }} -{{ print "%s" .Values.netmaker.turn.existingSecret }} -{{- else }} -{{ print "turn-secret" }} +{{- if .Values.netmaker.turn.existingSecret -}} +{{ .Values.netmaker.turn.existingSecret }} +{{- else -}} +turn-secret {{- end }} {{- end }} diff --git a/charts/netmaker/templates/mq-deployment.yaml b/charts/netmaker/templates/mq-deployment.yaml index 47531d6..971569d 100644 --- a/charts/netmaker/templates/mq-deployment.yaml +++ b/charts/netmaker/templates/mq-deployment.yaml @@ -32,9 +32,23 @@ spec: command: ["/mosquitto/config/wait.sh"] imagePullPolicy: Always - env: - - name: NETMAKER_SERVER_HOST - value: https://{{ required "A valid .Values.mq.ingress.host entry required!" .Values.mq.ingress.host }} + envFrom: + - configMapRef: + name: {{ include "netmaker.fullname" . }}-env + - secretRef: + name: {{ include "netmaker.secret" . }} + - secretRef: + name: {{ include "netmaker.mq.secret" . }} + - secretRef: + name: {{ include "netmaker.db.secret" . }} + {{- if .Values.netmaker.oauth.enabled }} + - secretRef: + name: {{ include "netmaker.oauth.secret" . }} + {{- end }} + {{- if .Values.turn.enabled -}} + - secretRef: + name: {{ include "netmaker.turn.secret" . }} + {{- end }} ports: - containerPort: 1883 diff --git a/charts/netmaker/templates/netmaker-statefulset.yaml b/charts/netmaker/templates/netmaker-statefulset.yaml index 90b1976..e527c38 100644 --- a/charts/netmaker/templates/netmaker-statefulset.yaml +++ b/charts/netmaker/templates/netmaker-statefulset.yaml @@ -32,16 +32,18 @@ spec: name: {{ include "netmaker.fullname" . }}-env - secretRef: name: {{ include "netmaker.secret" . }} - {{- if .Values.netmaker.oauth.enabled }} - secretRef: - name: {{ include "netmaker.oauth.secret" . }} - {{- end -}} + name: {{ include "netmaker.mq.secret" . }} - secretRef: name: {{ include "netmaker.db.secret" . }} + {{- if .Values.netmaker.oauth.enabled }} + - secretRef: + name: {{ include "netmaker.oauth.secret" . }} + {{- end }} {{- if .Values.turn.enabled -}} - secretRef: name: {{ include "netmaker.turn.secret" . }} - {{- end -}} + {{- end }} ports: - containerPort: {{ .Values.api.service.port }} protocol: TCP