From 536928509b190cb30d8a840fc28f4e7db577f339 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aleksandar=20=C4=8Cekrli=C4=87?= Date: Wed, 24 Apr 2024 20:55:42 +0200 Subject: [PATCH] Configuration improvements - Environment variable support, docs, CLI flag tweaks (#138) * Support setting config options via environment variables Fix path handling for windows in config tests Add JSON tags and make field naming more consistent Generate CLI flags from struct Flat map is generated on the fly (fewer allocations) Renames and minor tweaks Add CLI flag descriptions Rename file Minor restructuring Minor tweaks First draft of a documentation generation tool Adding templ code and some styling info Style tweaks Add option to disable file embedding Promote documentation code Add a warning for using tabs in struct tags * Making config package not-internal --- .gitignore | 1 + cmd/b7s-node-docs/assets/css/style.css | 91 ++++++ cmd/b7s-node-docs/assets/favicon/favicon.ico | Bin 0 -> 29857 bytes cmd/b7s-node-docs/b7sdocs.templ | 86 +++++ cmd/b7s-node-docs/b7sdocs_templ.go | 303 ++++++++++++++++++ cmd/b7s-node-docs/main.go | 69 ++++ cmd/node/README.md | 2 +- cmd/node/internal/config/config.go | 57 ---- cmd/node/internal/config/flags.go | 213 ------------ cmd/node/internal/config/load.go | 117 ------- cmd/node/main.go | 28 +- config/config.go | 145 +++++++++ config/config_options.go | 136 ++++++++ config/documentation.go | 5 + config/flags.go | 105 ++++++ {cmd/node/internal/config => config}/group.go | 0 config/helpers.go | 43 +++ config/helpers_test.go | 40 +++ config/load.go | 124 +++++++ .../internal/config => config}/load_test.go | 154 ++++++++- go.mod | 7 +- go.sum | 15 +- models/blockless/{errors.go => params.go} | 7 + models/blockless/protocol.go | 9 - 24 files changed, 1339 insertions(+), 418 deletions(-) create mode 100644 cmd/b7s-node-docs/assets/css/style.css create mode 100644 cmd/b7s-node-docs/assets/favicon/favicon.ico create mode 100644 cmd/b7s-node-docs/b7sdocs.templ create mode 100644 cmd/b7s-node-docs/b7sdocs_templ.go create mode 100644 cmd/b7s-node-docs/main.go delete mode 100644 cmd/node/internal/config/config.go delete mode 100644 cmd/node/internal/config/flags.go delete mode 100644 cmd/node/internal/config/load.go create mode 100644 config/config.go create mode 100644 config/config_options.go create mode 100644 config/documentation.go create mode 100644 config/flags.go rename {cmd/node/internal/config => config}/group.go (100%) create mode 100644 config/helpers.go create mode 100644 config/helpers_test.go create mode 100644 config/load.go rename {cmd/node/internal/config => config}/load_test.go (56%) rename models/blockless/{errors.go => params.go} (68%) delete mode 100644 models/blockless/protocol.go diff --git a/.gitignore b/.gitignore index 638ecc2e..d232dfc9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ cmd/node/node cmd/node/.b7s_* cmd/node/*.yaml +cmd/b7s-node-docs/b7s-node-docs cmd/keygen/keygen cmd/keyforge/keyforge diff --git a/cmd/b7s-node-docs/assets/css/style.css b/cmd/b7s-node-docs/assets/css/style.css new file mode 100644 index 00000000..ba9c807b --- /dev/null +++ b/cmd/b7s-node-docs/assets/css/style.css @@ -0,0 +1,91 @@ +body { + font-family: Arial, sans-serif; + background-color: #dce4e8; + color: #333; + margin: 20px; + padding: 20px; +} + +h1 { + color: #333; + border-bottom: 1px solid #ccc; + padding-bottom: 10px; + margin-bottom: 20px; +} + +h3 { + color: #666; + margin-top: 20px; +} + +ul { + list-style-type: none; + padding: 0; +} + +li { + margin-bottom: 20px; + padding: 20px; +} + +li.cfg { + background-color: #fff; + border-radius: 15px; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); + margin-bottom: 20px; + padding: 20px; +} + +li.child-cfg { + background-color: #fefefe; + border-radius: 15px; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); + margin-bottom: 20px; + padding: 20px; +} + +li.cli { + background-color: #edf2f5; + margin-bottom: 10px; + margin-right: 30px; + margin-left: 30px; + padding: 10px; +} + +.cli-details { + margin-left: 20px; +} + +h3 { + color: #333; + margin-top: 0; +} + +p { + margin: 5px 0; +} + +dl { + margin: 0; +} + +dt { + margin-bottom: 15px; +} + +dd { + margin-left: 0; +} + +code { + background-color: #f5f5f5; + padding: 2px 5px; + border-radius: 3px; +} + +.link-icon { + margin-left: 5px; + font-size: 80%; + color: #888; + text-decoration: none; +} diff --git a/cmd/b7s-node-docs/assets/favicon/favicon.ico b/cmd/b7s-node-docs/assets/favicon/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..4d0e1a2033067c513503fb664456fd57bbadd3ee GIT binary patch literal 29857 zcmeHv1z43^*6^VbML`jjPHE{zQd&Y9q!H@*?7Iv$fD-l%0|Be~TRrRnfxyO{EtJc^*YED^K)qZXeh5Q=nHG9@cnEE-uZ3#dlZ5in-GuIJZ-0jc>e^eH zj=#luSbqgWOJ3@@?V*|?6yPz_Rfo1T)IlB0ji57AUyt$YDa#%AG1651)p%_M>Ekwo z`%1@8F#hQ12s$!205yN03H5Zafx6jQ9k&^4sr~kN^}mfD>hC-53!W))@1Z51a-nXv zmcKdvgnob@%p36jnX|(&{j<|k$Mio?kv|^)Bl^L*0sgf#)|y6eX5ZKEOL-~u0a!m6KVQG}fo^YY9n`PmuJ!jIX<`{8ukf7AO}dGP<+ zH;|J=upd9{2ju5*fyvmj<-cjoe$w*ad1w7-U6jOypuV22Q2%Eh|4}(0Bgg}-ul{l@ zyH5WDWT?trk$+l_{b)|VSI0q30!?}!eJsmQ<30QSF(VZwBxm6czh z0(WU+Z8fx`rTO^oINb>DdT_VX6J7sPbbz~kc%c6@=;QZ>nkpzaE%|rY;NB`L`t+-Q zQ5GM>9)78{J)O_da1k!-}mp+`17*Tpb+@Ag;C|2x3H zzW`nX8-N<;GBi3g__!9V2`3FHH0b%W@7@0>ez3OS%%3e_uYo-W_8yQ&ana$wkXL_Z z|D8U6;LZVZ{ItBOswg|&Ye3e3^9O8_(`9086v{+``<)-4?O!>6PFAMh!2x*-WEa>^ zfEQq1;H&~!dtytSbl%(BgT{n~Kv~EMpwa@|-)#)g{;$X%z|+mmjT5lP``E+58hY=} z?PFO0@B`cHbOHMg+(Y0T0y%klJXkAXc7}iY`~&!p40M07o+r2fT(GxJ?wYpd#_#72 z+(Y0_I$eOR`j2e0)3WdMyPC8ZbfB;2G`Qn;V2gwGIOz*y{g<+mU-biL64?1C{r<=k zoU12~ZGR=>!CJRAH9&hhJAS2qfIS2BLOKHMKZW}=y~i>>>g^BLG=O)&YjFO~7QhQdF~NTu-m`Q7nFREj=N``Ab%L{fet!S&v8Vp0*6OT3;OF;G z4*cZ6|34f6enMEt1)>f6efJZ>Vl8LO|CD_CiTCFn_Hz&X+ynovJ#hOL1r)^iKy2<` ziG%&S==o>i0z15^uI8*60`2eVI<{T^Svddce!w?WT9EfEy3baC7_q!4|9{M$`bT^K z{>+HifzaLE-Ls_r<~OiM(-Y&4ePRCy{=c*Rv4!!m5BoO>{Ra)emknY)bof{Qqw4{D z-Cb?1ze^p+JxGpy2hC1Rf_|MC|J}ahqa)BC9K#+yP<~4u}}N=Xa}(XAU6R{;Fqy_sB@f?0Q`}l?iS&-V}IXi zAKY1ZAb#Zv%e(li=mv0rO#D$??-cm9Psgi(A1Ny(@mKz>)9^rC z=&zUqkS~i1^QS?7e+Tl?+EDK}z6o+(fPWw01hKy#l@tH)30+{Gz)$(30&-eFeCed` zAAJIzo-JUFO?B@7CT!6DTjkil4)gbc{dGbU@az64CK&zZ_4hs}egD-b$Y(iQfNveZ z0lswLlLtQLupqzhxPC;-Ss16|695+Q*PSiBT^-+H{;PDK^auGxr^~m+#qaX^bQ|FL z$rwOCknLuA8ppA!-#f46#RX6xpMXDj`s?IrRLB1L6PSPWd3G-Ye>Q*x_U`F&mj3?U z?(g#fK5=jlfP42x<-`{bVj-sskO6;`?vrt6=|71RnrN$?Ms=J6cQWQ=|A2fj5HC5Y zSC+W*!}EF~x59%1egX4;ntnhGm`mcjsN=Zj$vi;(^(1Zu)v)bJ=j?R(Z=nB# zp40JMFb_sz9O#<>U+4+%xoF6K5nBS|{+sAOnaAmP@X30B`}s$C8vjZ90ewKPKYOlE z=sx>AiR*zm0QvJHIsKoe|16ECzk%EZ@n{gA0r{<GD5KKgel5oqPOSb^}-!K>z9d5-^W{ z2mK%q1mFTO!QU#t1^{`*pda{w9G~B_8&2{&QsY1T0_0yo|7jfo00(*t(BlDJXZ;Ky zf7r*(@i^8F+ChF0$aMj^$iG#94hzmNkU?i-_aIN@ga?20`6(y!SEzx$2;c!34(J0k zgay7lj^Cf)0ebaWedaVEKpU`A0PSFmznACf=Hqt|13!WHN1p)4*#hJSpzpU}p5&3A z9S6Qo=VE~IfPRpJ@gw^vD&*B~;`lAxXX^ovPjbZ0(gV&X@Ie3{8_4?yG7yXhG4mgl z(=s3Q19^ym$3H4y&;3@4#suVR{gxgN=m9xS_hrO?fgi{gdo#md$(OVA16xLni}@F0e~XqsQxE(*XXWZy zTKc*>k9`b4CqB`Y&-OX_2Kx*A?Z=;?>5q)HHhS<~mpP&3UwHzZ8t6YK{-HlY!=D`k z?x+yI=fAtB&(7&bHqD=<&pq%*_rTeFpr7AAIq;JMKRNJ|13x+NlLJ3F z@c%jop36#$qNCxU!RCoBE_O!_WJ`k|Dl+V${wV1o__CH06@+B8;LX84IP_J-A4*9< zsA2D@5IAom*nfWWw?R0tCj5-q%cLr zUQM(Pi5Dt90%-7ZB*S#W#OSYtS}!mPHMOA&dE1Z<$BU76pi-kE6A`~6nJpikWaoRM zGqLEjeY>z{y@{EeuzQ8?Y8=JRXK?;2IA5cLFc`N zaUn-bD1DLBa{J+;u4 zCJ2YGb}zJPSvYZ`x7BWOFWf62`i|2!;g$)vdLa}8wu*eFhicaSa@CY^GRmu_G4+{C zv&qh#JyBh5ZIoPx!<@J0)AWd8KvfKxHf)e^ACDhCNm6)T>DYzd`S@Umv3bXt7@dIE z<7j6xRA=dtP#H07!?7P+po!wymF!Br?_}JaCXma$<7G?c?`1x6AjAyMJVQ%z9SPe6 zY|Vjnp;kU9kIO(3+vZ&NTe)}%qty}KIRXL{&wcdwQCi;~uMwB$;K6noEjzgkmqMH@ z91agd*hs7Kk@ZoqW__vJCo9~|CBBy}S_PEKfqCcxu)Ryn(8&7gsJYijR!k;EE;&tI zz}J`$E7~hKq4_=LMAcY}#ui&{2TL|wR+}lA)T^gOHpWu;18i5!i_Xz@F|%qE?(MVA z6SBR)rqxVN5X`6sXJ_Za`*5`R?hFay(qg$H<&Ko(w#qzbrwuZsE%ew!lmL~2H{WJ) za3R4noXg&vMs@GB8-n){f}M1o*Bg~G;sZTMXM0C8u8(qjN&*~;J5M}JL+`6~V6O0~ zhXOHx_ftcu)3(uK)f~$$U1Z|x&#P5nNU!^T+A<9=aOPQb>xhj{DofMyeyA+k*6 zV-PVT2JE~dduqhX-N-H;_04K*y?h(qX$$}IKEk-oLDk&s)gAeG*a?fIBv`asq6IgJcI2^4TgFu53|JhmHtauW&11R2CL{Egsvt0r9xhp zKU`GpBB?;;r;A3^_0MYK_j8rB>dweCAM_u_!ea`fYQWMwX9~gc*c~apk&9byI;Z=> z_^a^;AxHpx=R>-8?@1{-((k*8Bv!_V6T!g1r_Cy8IWpeZTpaLM`(*f4nL1;3z;4}u z-Fzr6OJ~w>Yl!@*lo0kZ*wFaq;p@_q$azT?+K_nh;D$!Eg0M_f_csh#GS`wdk4 z{N)4+=@O5h%cU7(!M~%4VR($~N5CEu965^^h3;UU<*`-q9h-##HZq?#$$uQM@Z@rK7cCedk2uKQYH8E-o7+1gy=x ze^rgve?+;y5~Qc=MihC`#k$KeSTBem8_q|E@=d3>a9GXJTqxG|lCd7(R7#-MMp?wW3F&uxbw8_0?8B`ffJg*}4>LM5N@Zj1mwSv9yQ{{RW$?ZYn* zm*)01BSH&U1Zpo>a4-SECWYNj!`5&O_@Ks*gOJRi-HXWM-eo&QsdS<)x5G$Vs~GgBqS zjP^VA9a!egH-ulTzNUI5PZH!Z+Y|GNRr4t3))C$&f$SPV?#!WQ@jyrL<<}3=c^9N3 zJ}t?4KD$a|l?LZ-ve1W#kaX`bT&YSUMP`!ppMyaGc7D_f$$~}=;k&o# zCbMR?9sK+l@ch!%OM`mM`#yB3B8yNkO}!n~r5e-j&Qo^Va~^q>6v#PIN{S9?NievJ zBd(bG`r{$l_?trQRcg(`jfX8885~QqWUpR^2C2ajhdqpB;9{=&xbnr@jphqR`)dxm ztjUs!$A%MM`x~;dB!pKllS^JP4rxi6ul>3zl>9L7MM?^D9>JmnA>U(>luIgyW9w?4PHNc{SA@JtsIvzy*Kz5gK1Y~FP^=OJeWAN;ZRss~O3GFcwd8a! zAyu{Vt!VM;t4?f{!bL`tTpN$VyGU}k!s^CnT%NwCU=t09>WWUk;&c7U5a-AHBUI-_ z&U;;r@Z<=w^5l4k6sSQ<^(kHJ?zzikmX#$KKHN-hvMTfaZarP?t_C!FlL7Zx9#kP( zDU}6Ua=n)raiKtXwtb0=JBwORD?u#TcmOXMr(AWDgG56Lrk3PN@g)u&6kJ!z9450} z|MI0BH6;gYVh7_0s%4=iOpa*eLoKitP~$c?|IEyJHsDM7)<%qY6ib2xy zmgY5$5p`e+E+LaMj|=n*l16vgMu%^%>1PF@xrUB7>G*iI){u3V%9=i@Y$jRa8G7|N zRcmaM&8dBc#P@oy=Aio|qof3_3TA-~x#@DfTLBQ2z2%|YrdO>2ZqUO|K0W*XEv`_~ z@oV22x=lQNmkEZi(B2lWNP&s4&g_TepNBzB03b^w2_w zva}5+7N)8k+XLVJ3kdaouz>aIWF&dAlcXJ_J)bwuDk=+|||1Q^ZyC~$R z0SUXlN@7v!Rjjg4@h@SsEDEDt^^xpeS@!mO@$R#3-b3Z=Jh%$g%^ajoT94Z#bvLEo zO5w6EGqA+v%6@jvwX4RH)W+z4u2(B_?;X)#Uoq73tEj}0a)yc*@-r2h#p?=sh38+B zU+3d~kOkzl4sX{f?`V4npsQ8-WT3?gv=Y$mOPZw zZMihUo;t@dn0_RVGyt9AGIN8cV5LUPsE@6ci7z2keuw0%OzqOOlJ6#k+j|ucRlJ1G z+?aC^$6cuzHyjyHjKlYJhIDX(tN9+%j#^P}!uY#HouQq+IGzG0gzgkgaT!bvF6BFq zg16v44>X*|O?xQu$O=tfd4;g=o~aSUa#1na*+_mxv(OPiF=a$$u$w&|;T}$PVF~`% z&EAf77R-n&qh@%)79>S@$K1+FDhk2H^02q$1p-m?DP9i@Z|Ac>6lvZtPdPiNS6wh3 zNl5KL<IL2S6cDSp3S{5ijaO{6{=$j$LQS@|X;{9!0%6CFE z<7J0}HpU$>W&=g?q|%IyGUbAWmpfe8irts|;jFUNIjr3k%&6zyx=&50m&a>+WfwuN z49ODBuv{k8vMOBZB&W;UE<(TWk}g4WNqN{Wbp|)O<$_L&qOohrg9{>xy2V!^1=BA} z3N8^Gg$~$WWHRP;Hb$YiVkzdf0Y&yO8l>D(JBn4%Qb^~?T`X(562F3@P_+bun1~Cp>3^rwv>Ww`De9(^gbKiy^H0>8i=B<=%4S#QS=fW z9KA_bk<`0+mo#f7=~dm#lIv#;)aNowB&gg3)URysUa9ADKF3{CP;6sr)|XYhh1Tu) z_JT=Ut(WOGYM^l3I+I{lA)Ef5`dq?_?uAdw ziJN!0M9a3)+$x_HIkp=?EX_b55m5gU{yN_Q6w^*Ur(j#BcI=jH{R6CP*s6$gDN$8+ z$V6Z6*Tf}W#N7R=pH#pwr#*UTyddHC>cTyj5!7hn`|K8XM7PW@LvckK#}=eY?Z1t5 zN;9JQ%;wqRetInbj7p#1w8(kfMva4_lID}H!^KLSI!Z#13v){$+C56hW5NPT=SEH3 z&hs3W?Kze!Fh73e#vjp3izPB!V-#!1Of=c57kY`Zup(IIe-v-kvEJnw@T(+zg=nP zr-F3d){mE&pPnx(1Wt@LI`fcDEHK+V_GsO3hrB6*b!53-BvpYr6{%+f9ZfbLtrFdX z$AQpQJvyN&%)o&x_LFsgp^Kyuse5pw-+epB!MJaaMAT!8xMPLdm5_A%pQS6Y{^*HlBM;#O>DEabgy9=B(kG8d-lLe1dl;Zm6-Tc46^R%`C^>~4=IraSxl zmrYUS3L?Wa!%xp0LP@6-Yjr6f&>&ze?>aDE5E`zH_%z>&;LUt!m+rTH;CacVs_%2I z%NIG!%Tbd3W3AEq_eiJGM!zyQB63%=aRdgJt?s2}C-ZC$hc0Ph`d>SwTAk>h4<&Kh z?1^FCE-%_%t)jt1Ffi zf5*7A;{ei|G5XG(fLI~TREjP!rZCny-}bA3sjM>8)hBAmCI&x!Po@vzpiMpL86g>$ zlk>s-$)y{f74<0Qyo!$mEyqYZ&Ot=1I)M@jcZP$Uth#`*`3DGxh>#;7Sf1 zk-CPJLOqez+Wh?_jfUzx)F7`?$}-eGiutLQ||}oU6;0M`Sd3td?sNlG|vr zSm(HHwq>enbFIW#7|I*)O~Ndt4!J^af!Zw0f4wQ`je8u^(pLzrh9}V7`faFnO;Fn|?f}#Il7< zE!&Vwzdr7k@e<5>3db}Y%)WMLx0^zxQbPGMQu8sT5(6z180*iJ;dAy}^UUHqvh*qn z9QuuyvLQyz{9Q(Ibv$J^>M8ZfkVkT>kt=Y!@MMw48&J8AL>4dQ8u!TFFli~>-=;F1 zRWXZQ@nywD7n+}zIwWwde~N(F=mxX-EO$c!ne@?cut!AiV`9LEAz9mAR!0b0L248QtU!y$P$B z%pu)3@<_<0yB5B{y~zDED#u=yma#B$AbHn^V9Ou`5~Qe6!m-n7;p1OX?~->DVRd z%IZd?xo?`%&qjiZaPzWAa3SYq{QET!d{=hvJz)tDlzq8R-J3ZY=YmqUnirnfFo`LY zAgzTe5@Xy)Y|#hdctCQ@-G~EZNcQ&@G%EC<{i`@1J z8+g(zrA2wYJ1EAAnPlB4D-_rYE0i80pU`vz;KpO)2P|Of`0Fy<*Zq(JozO#unqDnQ z(M}Q(AF{~vHi-l#ogcCNM$2Wh(Ya57vCm6pR7q<4>%b{ za^hJ-2L~-RGS5KgbD}lVI#D8KEOsPqA@!)y`w?zxS_wWTHl>YPQ#cMygp@7KR!z@v zA?C#nUP>YCvYWGG99`+wxJ#ElbFNNQlghr`C#h?D8R?}*Ax%-TB_eWRZpNCg#Hjb8 zKFnF?5IRk#F8yAI^xVu$kf&C=5{ZGfPo3j=Z~Rm=4m6jLkW_Zra=0(7lQU)-buqFT zHdYqC++}#U5MOD=>-PM`R~0J^t}*Jfx!7+xsqxt7;B&=+@NaT1ZFkNuN*lOGl_1W43Pj(5GO!jqmBmWxp(1-PpahJ6$_@ zc?-Y7tLifF8Xu3~jVZo!t#zcSzl8zPHxtHSggr&2;y>-Pp;5-ftV6H6IwbzIf-tlX zb4=8|Zl%ZtoZK zcv>j1Z7IDP^T}f2TldnS{Zlxx`1_;OFAU1|qdkSZ+Wa-NXqGx?j5^zJEoNJ%m?c(#HEb zat8ex9x8Ute&U$@BcATG-dEm=1E7R-*ua?OTMudf;k|~!}R(5G4H*g z<6Drvn#%eiwpNP z#Z)<}=AzpkZ40^*LJo)2OH?N!z=bo|On7cT(QjTh0^5&dbbb$7=)EW3iMoZD1Eu6zp(r_nm}KIp;7@-+>rVq+>T;g;;BN-{({FSpRWW>or5tX zTKjXRF8^3V_h_=vn-P*)ExG}>6|oCyYlJf`AyI7Us_%k)Js&72Y10Zi*>`JP81dmo zdQbWHTi#PkSOB6^1k++;ODbLLk3L+zSGIYyL<75!#O}cCbHUy9>8&!7dpVD)nGh!~ zmFBo44vdkOE~zO7pt~y?)O>YKzjBes^6MPT3H@!TG%=37?mR}@`;a=$0uP+7^bE2Y zB7d}n?u?XTH}8vt-RTR+!pAG|oH3fqIy9xnT*U9``Q>LZx5Qt+)VNyS)i-t?Q*!Hp zc=rQ*f78`q34Kznnq8>(`n3ZbA1nhD+JuT)ckBp^k44s#HBxAVywMMvRns;~@J5`T zpdiZ!2_hY^v6v!y!rzs7*A8>Mbjnr%a{qFWjOcH4sK>HII*?6(#D`k zGih;JCWIQv5AU*Iaems$LNnoW4Orad80oeN`$U(OFMK9sh^e#vrS*QxFjkjEmdFRp z=3Y$~EIwCfK9DcFny6TY-h+$i-uy}hTMxn~hgMPoRF*?ZSj^Kd|k*CzgizcMRPttDlFtLYXW=|-6JLPNq8_llJ zJ(acnSKBheZrVc|XqCQy1790LH0kBI&=W&Io_=__Yg~K!p6xp6<>gM1ykz^3LmF61OZo`R!K-I+w477jhW5f zjZ9M>Zr`qfmBHnX>^yP{*A3fYuk@!_Mvn0-wY!@sMS`#$T+5=jc`;v_G3lJ59LpsZc z?KE#(k3t?cYosX;v)q1}tUfnkl@A?A&1~y>{zdrZk)U8&S+{=0Si4+A#Mq6h>xWd( zgY`_lRAtRiFc=e^hjbwgZ$i=J@iExc&fi+KFLzERB>pIW^J46)>hr?)rC>gK)&aAu zwXTeNoND-~E*$osnZjw6w3#{z1n6$qX4*ja7U(k@-YV89MYwMh?hvXobYjuS1#Wzc z{6d^0-q@0BK7}`>lR-`Y;;XKOUcaZ&0P{MN6|KWjCAs5V^#g?@ZC>Mn0{{3od-tqV zvK+{5=WgI^PO6NSN8Zf4mGN1HrhRql5ap7Mp{i&#%=#-rRb>@aW3@;cguLUc^^f^* zla8yiO~h**_|v;m?IR+IeR|@XtGy~?IQJs2L;v=9Sj4AqJv~+Hi_49EFA2poAqr`i znXA~7qUcOtdkLe89p;oX_Y9SYVdgzE$vpW1 zZUV?&z}-xd!dQQTk(e!lof?{3kJFHG*vw>n=#$j5b8B294i9NJa)>g-s5w@@`CKUK zy>qd2lA_g6VbFx#^gS+{lk3ZAN4YgjRPp_%7dPJqW6%m5?%OGMP?Ux^6ic+QJW!@Nsc=H_WlI_oQl?9I=p$dwC?Avukhf zH5caK-&xzdKyc@oq>}uj_3|)UD!R9P-E9$sSGLo|{EZVM2wn1{;2@%hyxFwua-L3h zBsB+QmS7mYrPu?xfyV{PGDBflSVw1J9mrOgu!bc_e5$J0Jz~ z$hj6fhuAHyMST^t%NZkdZ(`$6mVU*NZE96MJ6dErNWYnqu^}T+jn8C>=%SE@W`?2a zsCdrPDfGq6;kno^sI5m2&kY4HQNsLk5XYKDq=fO|Bv+|{kJfq9q84ftN4;6p(R^-x z2(6nOwJ;|Qo}X{nds=54nw%}Oh>j5r6Y-m#orIIlM7j#o^qS)VDTkVgX6O|)blf2~ za|4BOy5vkk-K1l^H?JA&C+qvtbn@99TX$;e0Z zZGYn<>oy>L{W-X;L`peDB|Qk6GCoBr4o<7g5{E9FyPm1=5aS(UfN=4PB|Hxvj~euy z?tFUGbCl^s0fc-vzs6|gKcX|k78M*>G$*wrpF!z2Z02&g^XA-@oA0bCw!-!faG&31 z`lkIh02jVdSJ4woPvAC}oUhCRrd)ukctT60XsM&OYOd_Hy5=xvg)@NER=QSf{yU8%$g&*V#lKK^A^iHuI>~< z@WR%vCL0b;@r`Ad!c`Di@+hYk-3~Kt)4DB}XUDwvXq)$T3Qlu3yAwMu_Ud#$RTU2+ zELe`GfuQOYa9d1O3ktTdQrDXSW(*@lSMM(qIy=iaK;9(0_bq0SGIBg;j&S`m) zO+SX{AxyPva9S>K^6g>+kBdBtaw$8U#{jo zde-rpS|3eA|3OH_=TZKr9GNg*$Y5w;zccHE0UQF@|r6B!tFOW zMMkapgLYAF`k$jHCR<#@XSyR^^?~Bz!!o}iXal_iU&@QD zm}+LL>4lfq*|+B6dOpo_YJ*VAr4)Tb7r*V2cmK$vj?wQegCEQ{cWt)^6N>*4WsDPd z<(w9=KXOzc%C`v8+GLyfBF%fc9z5|{ve&GtcFHJttjdtPcbocs4dX0x<=!IW@1j(+ z463G?E{49lGTtyoYZ=bb0nvUH*!5^U5_L{XmPXnV=I8AYXW*%Y6LNn_c(pNvjHCMw zMW0Yb&`1L-E~*Dz%3OVlo18%GOpczh`N7)uo4W$o_%%ugP1(p0Z3?j!c)uIG6l%qb~F|iw={7@*m+jVNb(3bX{bA3aR!Amr#;Z)^V8ZJ#ph5Z z`R}m}S5+{(=}j9VV4Tw|W|N{)c)CjB*^~iaR>E|30rRB?J|di)3sN4}#Kw5L0sF27 zM!x6ywbab*!Sn^WL)&L?5E6%r(FBZw)_Eao06&CTYwNd16`?anxKQ%6JY_x9r17huC7=jyTP%F?bQ|g74lKC3k3eEATu4 zC3Q{zMQ3FzX_83Zi-ZF5g*iP_F3XiU>^?1DBYy2*+$vTYNNau5>s5Rl9wHwJZ2HCB zY%@)ISbUz#W-k4u`j=i0(!19jbU(KH!_P#+VrKGTE2OJ1k?61Izxa^`o&W1t(5`{3 zA$?<2sOXkts~ZC;yHR_6m%cGtcF^^{(kJ9!Y&Nw~@xKyPh!Ae63{k=Y=3$oHd>@b2 z7WDnhP&}waUUY*lR^y41UH2als#(B)$&Eg`5kKh&<7IA?eY%x-fN#Iglu0%TQXYeb ztmWW6n59Ak|29Fks;hrvWqi~Q&$EN~Bb*O>qDE`FRW#qjN}KRoJUs_i(NLWXyslzp ziotIY747GSTo5Wnxk+BoH|Q^H-e-d_@_WD57Na%7Zgsve{9=9bzS~H`uIDrRk;<#Q ze7%*n*V>=KB20Op6IUJP@y#JS>67$U6L(}>47}OJl(LN$ zr1u_ze3xeNzv*Ml9xr4j=3&vTmMk`@LI@&z&! zS>GTwTNj?jO;R++wNEAv+)_9xJkzc99zJdXs zl~ZyESoOHEi7fn_$WXhT!bBI&W{``sj=|wN!9r}@L8Xo%LR^nrAsP3Xvn^JV+qg3) zh%~fWvr^lUk*DVZ4p-=Olh*uI1J5}GhC&2Wh;yX-C!|C-6)3-_GO@1}DiLXG?%Q>v z*}CUXt72(KfIE|WD4Gh1Z^u#x6~m-;LJN`2VwfB+#;a|cPkqHg=vERep`}v1hEc6C zVUrc=uu~6HLB?RQaVm}n48-bhgwbKvao5<60v#0+oqblFes7w%hF{7-GX1U}3;fC> zJT7X%`)g0)y@l~XN1fffQY&u=nJB{or(XyYc(@(ydh^D7Qg}M&pJ#h?dp8JI*%RFz zPDT0hw_Gv8i%gfWT~HTCtofhLd38RQZxO3_Z_!m_k`2dzYK`$a-(>i85mIh;o>O}H zc#*^0wdNp$&4K14^A`)(V2otgVd6)QgawRP5_A*4GAh6k*GbpDj7cb5A%PP4GJ+n+ zGX`q)Q8dNVN|AgP-wevEJ5f+Ok*ZapSwS0vQ>o@SMdURN$Q56W_$&R}c!$r;qsWgKUWoDe={ycvElY+q5Q~Lzw5+nRTkq#g+Ve7E3_>}zbEMYck9A-9>4e8WeH!x z>;D+ucRo*wG+8|6=*w)PFJEeUn92G%m=lBd^Gtaq2eDyC)^V0xN1XUnxz~aEdVQxs z-nw&QV&59`OV?DG$8q$XqC8`V$>w}dG|ZSVOdaxqWk_{hHfxG5uIfl`>S}uBVxohf z9bD+NJbVEQ8%GRgczv`97*FMM*%HKI(uM!*9aB93V^r4G0Z(Uf2e~iIcS+Q+Gcwrf zY_NqgXt&t&VY4NAzkM;oa-Rz}(RWuUX_Bi02xHXPO!#lZ56Gn`P)RN0^mz**`?p>9 zky^fB&yNP1ft;nPsw3~Nef^N8$|M(h zclq9YSN8Iav1>;&TCSu#y)hiNUKjCr1ox{(n5D#DLx5>~dC%$Z;dF93vf3ATJjS zb`IjkboE6lRAHR8>~UT*ybebN>nirSy5Xh!N079Zw)?f60bUN)9_Z;Cas$o)98}fy z`dA&rI9NLo(lr`p_!dE!Q{&@wu;>?5F<3i4+AXDrh>;)_&!CwizzZ`Fv}38KHKB5x zhw;26T!sLhlnAs_6{x($w8VxH63H6FgCp|yHOXco9ixR2!ePSunB{*dp$CMemR23@ TJ4W9iup%xjeJ4Xu+w=bbM`2au literal 0 HcmV?d00001 diff --git a/cmd/b7s-node-docs/b7sdocs.templ b/cmd/b7s-node-docs/b7sdocs.templ new file mode 100644 index 00000000..495449bd --- /dev/null +++ b/cmd/b7s-node-docs/b7sdocs.templ @@ -0,0 +1,86 @@ +package main + +import "fmt" +import "github.com/blocklessnetwork/b7s/config" + +templ page(configs []config.ConfigOption) { + + + Blockless B7S Node Configuration + + + + +

Blockless B7S Node Configuration

+

+ This page lists all of the configuration options supported by the b7s daemon. + It showcases the configuration structure, as accepted in a YAML config file, environment variables that can be used to set those options and, where applicable, the CLI flags and their default values. +

+ + @b7sdocs(configs) + + +} + + +templ b7sdocs(configs []config.ConfigOption) { + +
    + for _, cfg := range configs { +
  • + @configOption(cfg) +
  • + } +
+} + +func formatCLIDefault(def any) string { + str := fmt.Sprint(def) + if str != "" { + return str + } + + return "N/A" +} + +templ configOption(cfg config.ConfigOption) { + +

{cfg.Name} 🔗

+ + if cfg.Type() != "" { +

Type: {cfg.Type()}

+ } + +

Path: {cfg.FullPath}

+ if cfg.Env != "" { +

Environment variable: {cfg.Env}

+ } + + if cfg.CLI.Flag != "" { + +
+
CLI flag:
+
+
    +
  • Flag: --{cfg.CLI.Flag}
  • + if cfg.CLI.Shorthand != "" { +
  • Shorthand: -{cfg.CLI.Shorthand}
  • + } + +
  • Default: {formatCLIDefault(cfg.CLI.Default)}
  • +
  • Description: {cfg.CLI.Description}
  • +
+
+
+ } + + if len(cfg.Children) > 0 { +
    + for _, child := range cfg.Children { +
  • + @configOption(child) +
  • + } +
+ } +} \ No newline at end of file diff --git a/cmd/b7s-node-docs/b7sdocs_templ.go b/cmd/b7s-node-docs/b7sdocs_templ.go new file mode 100644 index 00000000..0fc04375 --- /dev/null +++ b/cmd/b7s-node-docs/b7sdocs_templ.go @@ -0,0 +1,303 @@ +// Code generated by templ - DO NOT EDIT. + +// templ: version: v0.2.648 +package main + +//lint:file-ignore SA4006 This context is only used if a nested component is present. + +import "github.com/a-h/templ" +import "context" +import "io" +import "bytes" + +import "fmt" +import "github.com/blocklessnetwork/b7s/config" + +func page(configs []config.ConfigOption) templ.Component { + return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) { + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer) + if !templ_7745c5c3_IsBuffer { + templ_7745c5c3_Buffer = templ.GetBuffer() + defer templ.ReleaseBuffer(templ_7745c5c3_Buffer) + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var1 := templ.GetChildren(ctx) + if templ_7745c5c3_Var1 == nil { + templ_7745c5c3_Var1 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("Blockless B7S Node Configuration

Blockless B7S Node Configuration

This page lists all of the configuration options supported by the b7s daemon. It showcases the configuration structure, as accepted in a YAML config file, environment variables that can be used to set those options and, where applicable, the CLI flags and their default values.

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = b7sdocs(configs).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if !templ_7745c5c3_IsBuffer { + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W) + } + return templ_7745c5c3_Err + }) +} + +func b7sdocs(configs []config.ConfigOption) templ.Component { + return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) { + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer) + if !templ_7745c5c3_IsBuffer { + templ_7745c5c3_Buffer = templ.GetBuffer() + defer templ.ReleaseBuffer(templ_7745c5c3_Buffer) + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var2 := templ.GetChildren(ctx) + if templ_7745c5c3_Var2 == nil { + templ_7745c5c3_Var2 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
    ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + for _, cfg := range configs { + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
  • ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = configOption(cfg).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
  • ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if !templ_7745c5c3_IsBuffer { + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W) + } + return templ_7745c5c3_Err + }) +} + +func formatCLIDefault(def any) string { + str := fmt.Sprint(def) + if str != "" { + return str + } + + return "N/A" +} + +func configOption(cfg config.ConfigOption) templ.Component { + return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) { + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer) + if !templ_7745c5c3_IsBuffer { + templ_7745c5c3_Buffer = templ.GetBuffer() + defer templ.ReleaseBuffer(templ_7745c5c3_Buffer) + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var3 := templ.GetChildren(ctx) + if templ_7745c5c3_Var3 == nil { + templ_7745c5c3_Var3 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var5 string + templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(cfg.Name) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `b7sdocs.templ`, Line: 48, Col: 35} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" 🔗

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if cfg.Type() != "" { + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("

Type: ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var7 string + templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(cfg.Type()) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `b7sdocs.templ`, Line: 51, Col: 28} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("

Path: ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var8 string + templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(cfg.FullPath) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `b7sdocs.templ`, Line: 54, Col: 26} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if cfg.Env != "" { + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("

Environment variable: ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var9 string + templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(cfg.Env) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `b7sdocs.templ`, Line: 56, Col: 41} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + if cfg.CLI.Flag != "" { + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
CLI flag:
  • Flag: --") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var10 string + templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs(cfg.CLI.Flag) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `b7sdocs.templ`, Line: 65, Col: 63} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var10)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
  • ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if cfg.CLI.Shorthand != "" { + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
  • Shorthand: -") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var11 string + templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinStringErrs(cfg.CLI.Shorthand) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `b7sdocs.templ`, Line: 67, Col: 76} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var11)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
  • ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
  • Default: ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var12 string + templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinStringErrs(formatCLIDefault(cfg.CLI.Default)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `b7sdocs.templ`, Line: 70, Col: 79} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var12)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
  • Description: ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var13 string + templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinStringErrs(cfg.CLI.Description) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `b7sdocs.templ`, Line: 71, Col: 69} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var13)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + if len(cfg.Children) > 0 { + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
    ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + for _, child := range cfg.Children { + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
  • ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = configOption(child).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
  • ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + if !templ_7745c5c3_IsBuffer { + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W) + } + return templ_7745c5c3_Err + }) +} diff --git a/cmd/b7s-node-docs/main.go b/cmd/b7s-node-docs/main.go new file mode 100644 index 00000000..f059edd3 --- /dev/null +++ b/cmd/b7s-node-docs/main.go @@ -0,0 +1,69 @@ +package main + +import ( + "context" + "embed" + "fmt" + "log" + "net/http" + "os" + + "github.com/a-h/templ" + "github.com/spf13/pflag" + + "github.com/blocklessnetwork/b7s/config" +) + +//go:embed assets/* +var assets embed.FS + +func main() { + + var ( + flagAddress string + flagOutput string + flagEmbed bool + ) + pflag.StringVarP(&flagAddress, "address", "a", "127.0.0.1:8080", "address to serve on") + pflag.StringVarP(&flagOutput, "output", "o", "", "output file to write the documentation to") + pflag.BoolVarP(&flagEmbed, "embed", "e", true, "use embedded files for assets") + pflag.Parse() + + configs := config.GetConfigDocumentation() + component := page(configs) + + if flagOutput != "" { + + f, err := os.Create(flagOutput) + if err != nil { + log.Fatalf("could not open file: %s", err) + } + + err = component.Render(context.Background(), f) + if err != nil { + log.Fatalf("could not render component: %s", err) + } + + f.Close() + return + } + + mux := http.NewServeMux() + + var fh http.Handler + if flagEmbed { + fh = http.FileServer(http.FS(assets)) + } else { + fh = http.StripPrefix("/assets/", http.FileServer(http.Dir("assets"))) + } + + mux.Handle("/assets/", fh) + mux.Handle("/", templ.Handler(component)) + + fmt.Printf("Documentation served on http://%s/", flagAddress) + + err := http.ListenAndServe(flagAddress, mux) + if err != nil { + log.Fatalf("failed to start server: %s", err) + } +} diff --git a/cmd/node/README.md b/cmd/node/README.md index d7ffce4b..63f5cbe1 100644 --- a/cmd/node/README.md +++ b/cmd/node/README.md @@ -27,7 +27,7 @@ List of supported CLI flags is listed below. ```console Usage of b7s-node: --config string path to a config file - -r, --role string role this note will have in the Blockless protocol (head or worker) (default "worker") + -r, --role string role this node will have in the Blockless protocol (head or worker) (default "worker") -c, --concurrency uint maximum number of requests node will process in parallel (default 10) --boot-nodes strings list of addresses that this node will connect to on startup, in multiaddr format --workspace string directory that the node can use for file storage diff --git a/cmd/node/internal/config/config.go b/cmd/node/internal/config/config.go deleted file mode 100644 index c73930d5..00000000 --- a/cmd/node/internal/config/config.go +++ /dev/null @@ -1,57 +0,0 @@ -package config - -// Config type is tightly coupled with the config options defined in flags.go. -// Flag name should be the same as the value in the `koanf` tag here (flag is `--dialback-address`, the koanf tag is `dialback-address`). -// This is needed so the two ways of loading config are correctly merged. -// -// The `group` of the config option defines in which section of the config file it lives. -// Examples: -// connectivity => address, port, private-key... -// worker => runtime-path, runtime-cli, cpu-percentage-limit... -// - -// Config describes the Blockless configuration options. -type Config struct { - Role string `koanf:"role"` - Concurrency uint `koanf:"concurrency"` - BootNodes []string `koanf:"boot-nodes"` - Workspace string `koanf:"workspace"` // TODO: Check - does a head node ever use a workspace? - LoadAttributes bool `koanf:"attributes"` // TODO: Head node probably doesn't need attributes..? - Topics []string `koanf:"topics"` - - PeerDatabasePath string `koanf:"peer-db"` - FunctionDatabasePath string `koanf:"function-db"` // TODO: Head node doesn't need a function database. - - Log Log `koanf:"log"` - Connectivity Connectivity `koanf:"connectivity"` - Head Head `koanf:"head"` - Worker Worker `koanf:"worker"` -} - -// Log describes the logging configuration. -type Log struct { - Level string `koanf:"level"` -} - -// Connectivity describes the libp2p host that the node will use. -type Connectivity struct { - Address string `koanf:"address"` - Port uint `koanf:"port"` - PrivateKey string `koanf:"private-key"` - DialbackAddress string `koanf:"dialback-address"` - DialbackPort uint `koanf:"dialback-port"` - Websocket bool `koanf:"websocket"` - WebsocketPort uint `koanf:"websocket-port"` - WebsocketDialbackPort uint `koanf:"websocket-dialback-port"` -} - -type Head struct { - API string `koanf:"rest-api"` -} - -type Worker struct { - RuntimePath string `koanf:"runtime-path"` - RuntimeCLI string `koanf:"runtime-cli"` - CPUPercentageLimit float64 `koanf:"cpu-percentage-limit"` - MemoryLimitKB int64 `koanf:"memory-limit"` -} diff --git a/cmd/node/internal/config/flags.go b/cmd/node/internal/config/flags.go deleted file mode 100644 index 9000430d..00000000 --- a/cmd/node/internal/config/flags.go +++ /dev/null @@ -1,213 +0,0 @@ -package config - -import ( - "github.com/blocklessnetwork/b7s/node" - "github.com/spf13/pflag" -) - -// Default values. -const ( - DefaultPort = uint(0) - DefaultAddress = "0.0.0.0" - DefaultRole = "worker" - DefaultPeerDB = "peer-db" - DefaultFunctionDB = "function-db" - DefaultConcurrency = uint(node.DefaultConcurrency) - DefaultUseWebsocket = false - DefaultWorkspace = "workspace" -) - -type configOption struct { - flag string // long flag name - should be the same as the `koanf` tag in the Config type. - short string // shorthand - single letter alternative to the long flag name - group configGroup // group - defined in which section of the config file this option lives. - usage string // description -} - -// Config options. -var ( - // Root group. - roleCfg = configOption{ - flag: "role", - short: "r", - group: rootGroup, - usage: "role this note will have in the Blockless protocol (head or worker)", - } - concurrencyCfg = configOption{ - flag: "concurrency", - short: "c", - group: rootGroup, - usage: "maximum number of requests node will process in parallel", - } - bootNodesCfg = configOption{ - flag: "boot-nodes", - group: rootGroup, - usage: "list of addresses that this node will connect to on startup, in multiaddr format", - } - workspaceCfg = configOption{ - flag: "workspace", - group: rootGroup, - usage: "directory that the node can use for file storage", - } - attributesCfg = configOption{ - flag: "attributes", - group: rootGroup, - usage: "node should try to load its attribute data from IPFS", - } - peerDBCfg = configOption{ - flag: "peer-db", - group: rootGroup, - usage: "path to the database used for persisting peer data", - } - functionDBCfg = configOption{ - flag: "function-db", - group: rootGroup, - usage: "path to the database used for persisting function data", - } - topicsCfg = configOption{ - flag: "topics", - group: rootGroup, - usage: "topics node should subscribe to", - } - - // Log group. - logLevelCfg = configOption{ - flag: "log-level", - short: "l", - group: logGroup, - usage: "log level to use", - } - - // Connectivity group. - addressCfg = configOption{ - flag: "address", - short: "a", - group: connectivityGroup, - usage: "address that the b7s host will use", - } - portCfg = configOption{ - flag: "port", - short: "p", - group: connectivityGroup, - usage: "port that the b7s host will use", - } - privateKeyCfg = configOption{ - flag: "private-key", - group: connectivityGroup, - usage: "private key that the b7s host will use", - } - websocketCfg = configOption{ - flag: "websocket", - short: "w", - group: connectivityGroup, - usage: "should the node use websocket protocol for communication", - } - websocketPortCfg = configOption{ - flag: "websocket-port", - group: connectivityGroup, - usage: "port to use for websocket connections", - } - dialbackAddressCfg = configOption{ - flag: "dialback-address", - group: connectivityGroup, - usage: "external address that the b7s host will advertise", - } - dialbackPortCfg = configOption{ - flag: "dialback-port", - group: connectivityGroup, - usage: "external port that the b7s host will advertise", - } - websocketDialbackPortCfg = configOption{ - flag: "websocket-dialback-port", - group: connectivityGroup, - usage: "external port that the b7s host will advertise for websocket connections", - } - - // Worker flags. - runtimePathCfg = configOption{ - flag: "runtime-path", - group: workerGroup, - usage: "Blockless Runtime location (used by the worker node)", - } - runtimeCLICfg = configOption{ - flag: "runtime-cli", - group: workerGroup, - usage: "runtime CLI name (used by the worker node)", - } - cpuLimitCfg = configOption{ - flag: "cpu-percentage-limit", - group: workerGroup, - usage: "amount of CPU time allowed for Blockless Functions in the 0-1 range, 1 being unlimited", - } - memLimitCfg = configOption{ - flag: "memory-limit", - group: workerGroup, - usage: "memory limit (kB) for Blockless Functions", - } - - // Head node flags. - restAPICfg = configOption{ - flag: "rest-api", - group: headGroup, - usage: "address where the head node REST API will listen on", - } -) - -// This helper type is a thin wrapper around the pflag.FlagSet. -// Added functionality is the accounting of added flags. -// This is needed/useful when we're translating flags between the structured format (yaml file) and the flat structure (CLI flags). -type cliFlags struct { - fs *pflag.FlagSet - options []configOption -} - -func newCliFlags() *cliFlags { - - fs := pflag.NewFlagSet("b7s-node", pflag.ExitOnError) - fs.SortFlags = false - - return &cliFlags{ - fs: fs, - options: make([]configOption, 0), - } -} - -func (c *cliFlags) stringFlag(cfg configOption, defaultValue string) { - c.fs.StringP(cfg.flag, cfg.short, defaultValue, cfg.usage) - c.options = append(c.options, cfg) -} - -func (c *cliFlags) boolFlag(cfg configOption, defaultValue bool) { - c.fs.BoolP(cfg.flag, cfg.short, defaultValue, cfg.usage) - c.options = append(c.options, cfg) -} - -func (c *cliFlags) uintFlag(cfg configOption, defaultValue uint) { - c.fs.UintP(cfg.flag, cfg.short, defaultValue, cfg.usage) - c.options = append(c.options, cfg) -} - -func (c *cliFlags) int64Flag(cfg configOption, defaultValue int64) { - c.fs.Int64P(cfg.flag, cfg.short, defaultValue, cfg.usage) - c.options = append(c.options, cfg) -} - -func (c *cliFlags) float64Flag(cfg configOption, defaultValue float64) { - c.fs.Float64P(cfg.flag, cfg.short, defaultValue, cfg.usage) - c.options = append(c.options, cfg) -} - -func (c *cliFlags) stringSliceFlag(cfg configOption, defaultValue []string) { - c.fs.StringSliceP(cfg.flag, cfg.short, defaultValue, cfg.usage) - c.options = append(c.options, cfg) -} - -func (c *cliFlags) groups() map[string]configGroup { - - groups := make(map[string]configGroup) - for _, option := range c.options { - groups[option.flag] = option.group - } - - return groups -} diff --git a/cmd/node/internal/config/load.go b/cmd/node/internal/config/load.go deleted file mode 100644 index 89c8c039..00000000 --- a/cmd/node/internal/config/load.go +++ /dev/null @@ -1,117 +0,0 @@ -package config - -import ( - "fmt" - "os" - - "github.com/knadh/koanf/parsers/yaml" - "github.com/knadh/koanf/providers/file" - "github.com/knadh/koanf/providers/posflag" - "github.com/knadh/koanf/v2" - "github.com/spf13/pflag" - - "github.com/blocklessnetwork/b7s/models/blockless" -) - -func Load(args ...string) (*Config, error) { - return load(os.Args[1:]) -} - -func load(args []string) (*Config, error) { - - var configPath string - - flags := newCliFlags() - flags.fs.StringVar(&configPath, "config", "", "path to a config file") - - // General flags. - flags.stringFlag(roleCfg, DefaultRole) - flags.uintFlag(concurrencyCfg, DefaultConcurrency) - flags.stringSliceFlag(bootNodesCfg, nil) - flags.stringFlag(workspaceCfg, "") - flags.boolFlag(attributesCfg, false) - flags.stringFlag(peerDBCfg, "") - flags.stringFlag(functionDBCfg, "") - flags.stringSliceFlag(topicsCfg, nil) - - // Log. - flags.stringFlag(logLevelCfg, "info") - - // Connectivity flags. - flags.stringFlag(addressCfg, DefaultAddress) - flags.uintFlag(portCfg, DefaultPort) - flags.stringFlag(privateKeyCfg, "") - flags.boolFlag(websocketCfg, DefaultUseWebsocket) - flags.uintFlag(websocketPortCfg, DefaultPort) - flags.stringFlag(dialbackAddressCfg, DefaultAddress) - flags.uintFlag(dialbackPortCfg, DefaultPort) - flags.uintFlag(websocketDialbackPortCfg, DefaultPort) - - // Worker node flags. - flags.stringFlag(runtimePathCfg, "") - flags.stringFlag(runtimeCLICfg, blockless.RuntimeCLI()) - flags.float64Flag(cpuLimitCfg, 1) - flags.int64Flag(memLimitCfg, 0) - - // Head node flags. - flags.stringFlag(restAPICfg, "") - - flags.fs.Parse(args) - - delimiter := "." - konfig := koanf.New(delimiter) - - if configPath != "" { - err := konfig.Load(file.Provider(configPath), yaml.Parser()) - if err != nil { - return nil, fmt.Errorf("could not load config file: %w", err) - } - } - - // For readability flags have a flat structure - e.g. port or cpu-percentage-limit. - // For use in config files, we prefer a structured layout, e.g. connectivity=>port or worker=>cpu-percentage-limit. - // This callback translates the flag names from a flat layout to the structured one, so that koanf knows how to match - // analogous values. - translate := flagTranslate(flags.groups(), flags.fs, delimiter) - - err := konfig.Load(posflag.ProviderWithFlag(flags.fs, delimiter, konfig, translate), nil) - if err != nil { - return nil, fmt.Errorf("could not load config: %w", err) - } - - var cfg Config - err = konfig.Unmarshal("", &cfg) - if err != nil { - return nil, fmt.Errorf("could not unmarshal konfig: %w", err) - } - - return &cfg, nil -} - -func flagTranslate(flagGroups map[string]configGroup, fs *pflag.FlagSet, delimiter string) func(*pflag.Flag) (string, any) { - - return func(flag *pflag.Flag) (string, any) { - key := flag.Name - val := posflag.FlagVal(fs, flag) - - // Should not happen. - group, ok := flagGroups[key] - if !ok { - return key, val - } - - name := group.Name() - if name == "" { - return key, val - } - - // Log level is a special case because the CLI flag is already prefixed (--log-level). - if key == logLevelCfg.flag { - skey := "log" + delimiter + "level" - return skey, val - } - - skey := name + delimiter + key - return skey, val - } -} diff --git a/cmd/node/main.go b/cmd/node/main.go index adbb83ee..6d7ab3d6 100644 --- a/cmd/node/main.go +++ b/cmd/node/main.go @@ -15,7 +15,7 @@ import ( "github.com/ziflex/lecho/v3" "github.com/blocklessnetwork/b7s/api" - "github.com/blocklessnetwork/b7s/cmd/node/internal/config" + "github.com/blocklessnetwork/b7s/config" "github.com/blocklessnetwork/b7s/executor" "github.com/blocklessnetwork/b7s/executor/limits" "github.com/blocklessnetwork/b7s/fstore" @@ -90,8 +90,8 @@ func run() int { log.Info(). Str("workspace", cfg.Workspace). - Str("peer_db", cfg.PeerDatabasePath). - Str("function_db", cfg.FunctionDatabasePath). + Str("peer_db", cfg.PeerDB). + Str("function_db", cfg.FunctionDB). Msg("filepaths used by the node") // Convert workspace path to an absolute one. @@ -103,9 +103,9 @@ func run() int { cfg.Workspace = workspace // Open the pebble peer database. - pdb, err := pebble.Open(cfg.PeerDatabasePath, &pebble.Options{Logger: &pebbleNoopLogger{}}) + pdb, err := pebble.Open(cfg.PeerDB, &pebble.Options{Logger: &pebbleNoopLogger{}}) if err != nil { - log.Error().Err(err).Str("db", cfg.PeerDatabasePath).Msg("could not open pebble peer database") + log.Error().Err(err).Str("db", cfg.PeerDB).Msg("could not open pebble peer database") return failure } defer pdb.Close() @@ -203,9 +203,9 @@ func run() int { } // Open the pebble function database. - fdb, err := pebble.Open(cfg.FunctionDatabasePath, &pebble.Options{Logger: &pebbleNoopLogger{}}) + fdb, err := pebble.Open(cfg.FunctionDB, &pebble.Options{Logger: &pebbleNoopLogger{}}) if err != nil { - log.Error().Err(err).Str("db", cfg.FunctionDatabasePath).Msg("could not open pebble function database") + log.Error().Err(err).Str("db", cfg.FunctionDB).Msg("could not open pebble function database") return failure } defer fdb.Close() @@ -255,7 +255,7 @@ func run() int { // If we're a head node - start the REST API. if role == blockless.HeadNode { - if cfg.Head.API == "" { + if cfg.Head.RestAPI == "" { log.Error().Err(err).Msg("REST API address is required") return failure } @@ -281,8 +281,8 @@ func run() int { // Start API in a separate goroutine. go func() { - log.Info().Str("port", cfg.Head.API).Msg("Node API starting") - err := server.Start(cfg.Head.API) + log.Info().Str("port", cfg.Head.RestAPI).Msg("Node API starting") + err := server.Start(cfg.Head.RestAPI) if err != nil && !errors.Is(err, http.ErrServerClosed) { log.Warn().Err(err).Msg("Node API failed") close(failed) @@ -326,17 +326,17 @@ func updateDirPaths(root string, cfg *config.Config) { } cfg.Workspace = workspace - peerDB := cfg.PeerDatabasePath + peerDB := cfg.PeerDB if peerDB == "" { peerDB = filepath.Join(root, config.DefaultPeerDB) } - cfg.PeerDatabasePath = peerDB + cfg.PeerDB = peerDB - functionDB := cfg.FunctionDatabasePath + functionDB := cfg.FunctionDB if functionDB == "" { functionDB = filepath.Join(root, config.DefaultFunctionDB) } - cfg.FunctionDatabasePath = functionDB + cfg.FunctionDB = functionDB } func generateNodeDirName(id string) string { diff --git a/config/config.go b/config/config.go new file mode 100644 index 00000000..aeddbe8e --- /dev/null +++ b/config/config.go @@ -0,0 +1,145 @@ +package config + +import ( + "github.com/blocklessnetwork/b7s/node" +) + +// Default values. +const ( + DefaultPort = uint(0) + DefaultAddress = "0.0.0.0" + DefaultRole = "worker" + DefaultPeerDB = "peer-db" + DefaultFunctionDB = "function-db" + DefaultConcurrency = uint(node.DefaultConcurrency) + DefaultUseWebsocket = false + DefaultWorkspace = "" + DefaultLogLevel = "info" +) + +var DefaultConfig = Config{ + Role: DefaultRole, + Concurrency: DefaultConcurrency, + PeerDB: DefaultPeerDB, + FunctionDB: DefaultFunctionDB, + Workspace: DefaultWorkspace, + Log: Log{ + Level: DefaultLogLevel, + }, + Connectivity: Connectivity{ + Address: DefaultAddress, + Port: DefaultPort, + Websocket: DefaultUseWebsocket, + }, +} + +// Config describes the Blockless configuration options. +// NOTE: DO NOT use TABS in struct tags - spaces only! +// NOTE: When adding CLI flags (using the `flag` struct tag) - add the description for (for the flag long version, not the shorthand) it in getFlagDescription() below. +type Config struct { + Role string `koanf:"role" flag:"role,r"` + Concurrency uint `koanf:"concurrency" flag:"concurrency,c"` + BootNodes []string `koanf:"boot-nodes" flag:"boot-nodes"` + Workspace string `koanf:"workspace" flag:"workspace"` // TODO: Check - does a head node ever use a workspace? + LoadAttributes bool `koanf:"load-attributes" flag:"load-attributes"` // TODO: Head node probably doesn't need attributes..? + Topics []string `koanf:"topics" flag:"topics"` + + PeerDB string `koanf:"peer-db" flag:"peer-db"` + FunctionDB string `koanf:"function-db" flag:"function-db"` // TODO: Head node doesn't need a function database. + + Log Log `koanf:"log"` + Connectivity Connectivity `koanf:"connectivity"` + Head Head `koanf:"head"` + Worker Worker `koanf:"worker"` +} + +// Log describes the logging configuration. +type Log struct { + Level string `koanf:"level" flag:"log-level,l"` +} + +// Connectivity describes the libp2p host that the node will use. +type Connectivity struct { + Address string `koanf:"address" flag:"address,a"` + Port uint `koanf:"port" flag:"port,p"` + PrivateKey string `koanf:"private-key" flag:"private-key"` + DialbackAddress string `koanf:"dialback-address" flag:"dialback-address"` + DialbackPort uint `koanf:"dialback-port" flag:"dialback-port"` + Websocket bool `koanf:"websocket" flag:"websocket,w"` + WebsocketPort uint `koanf:"websocket-port" flag:"websocket-port"` + WebsocketDialbackPort uint `koanf:"websocket-dialback-port" flag:"websocket-dialback-port"` +} + +type Head struct { + RestAPI string `koanf:"rest-api" flag:"rest-api"` +} + +type Worker struct { + RuntimePath string `koanf:"runtime-path" flag:"runtime-path"` + RuntimeCLI string `koanf:"runtime-cli" flag:"runtime-cli"` + CPUPercentageLimit float64 `koanf:"cpu-percentage-limit" flag:"cpu-percentage-limit"` + MemoryLimitKB int64 `koanf:"memory-limit" flag:"memory-limit"` +} + +// ConfigOptionInfo describes a specific configuration option, it's location in the config file and +// corresponding CLI flags and environment variables. It can be used to generate documentation for the b7s node. +type ConfigOptionInfo struct { + Name string `json:"name,omitempty" yaml:"name,omitempty"` + FullPath string `json:"full_path,omitempty" yaml:"full_path,omitempty"` + CLI CLIFlag `json:"cli,omitempty" yaml:"cli,omitempty"` + Env string `json:"env-var,omitempty" yaml:"env-var,omitempty"` + Children []ConfigOption `json:"children,omitempty" yaml:"children,omitempty"` + Type string `json:"type,omitempty" yaml:"type,omitempty"` +} + +func getFlagDescription(flag string) string { + + switch flag { + case "role": + return "role this node will have in the Blockless protocol (head or worker)" + case "concurrency": + return "maximum number of requests node will process in parallel" + case "boot-nodes": + return "list of addresses that this node will connect to on startup, in multiaddr format" + case "workspace": + return "directory that the node can use for file storage" + case "load-attributes": + return "node should try to load its attribute data from IPFS" + case "topics": + return "topics node should subscribe to" + case "peer-db": + return "path to the database used for persisting peer data" + case "function-db": + return "path to the database used for persisting function data" + case "log-level": + return "log level to use" + case "address": + return "address that the b7s host will use" + case "port": + return "port that the b7s host will use" + case "private-key": + return "private key that the b7s host will use" + case "websocket": + return "should the node use websocket protocol for communication" + case "dialback-address": + return "external address that the b7s host will advertise" + case "dialback-port": + return "external port that the b7s host will advertise" + case "websocket-port": + return "port to use for websocket connections" + case "websocket-dialback-port": + return "external port that the b7s host will advertise for websocket connections" + case "rest-api": + return "address where the head node REST API will listen on" + case "runtime-path": + return "Blockless Runtime location (used by the worker node)" + case "runtime-cli": + return "runtime CLI name (used by the worker node)" + case "cpu-percentage-limit": + return "amount of CPU time allowed for Blockless Functions in the 0-1 range, 1 being unlimited" + case "memory-limit": + return "memory limit (kB) for Blockless Functions" + default: + return "" + } +} diff --git a/config/config_options.go b/config/config_options.go new file mode 100644 index 00000000..d2c90c79 --- /dev/null +++ b/config/config_options.go @@ -0,0 +1,136 @@ +package config + +import ( + "fmt" + "reflect" + "strings" + + "github.com/blocklessnetwork/b7s/models/blockless" +) + +type ConfigOption struct { + Name string `yaml:"name,omitempty"` + FullPath string `yaml:"full_path,omitempty"` + CLI CLIFlag `yaml:"cli,omitempty"` + Env string `yaml:"env-var,omitempty"` + Children []ConfigOption `yaml:"children,omitempty"` + + kind reflect.Kind + elementKind reflect.Kind // if kind is a slice, tell us what the elements of the slice are. +} + +func (c ConfigOption) Type() string { + + // For slices say something like "list (string)", for primitive types print the type, and skip structs. + if c.kind == reflect.Slice { + return fmt.Sprintf("list (%s)", c.elementKind.String()) + } + + if c.kind != reflect.Struct { + return c.kind.String() + } + + return "" +} + +func (c ConfigOption) Info() ConfigOptionInfo { + + info := ConfigOptionInfo{ + FullPath: c.FullPath, + Name: c.Name, + CLI: c.CLI, + Env: c.Env, + Children: c.Children, + Type: c.Type(), + } + + return info +} + +type CLIFlag struct { + Flag string `yaml:"flag,omitempty"` + Shorthand string `yaml:"shorthand,omitempty"` + Default any `yaml:"default,omitempty"` + Description string `yaml:"description,omitempty"` +} + +func getConfigOptions() []ConfigOption { + cliDefaults := getDefaultFlagValues() + return getStructInfo(reflect.TypeOf(Config{}), cliDefaults) +} + +func getStructInfo(typ reflect.Type, cliDefaults map[string]any, parents ...string) []ConfigOption { + + out := make([]ConfigOption, 0) + for _, field := range reflect.VisibleFields(typ) { + + var ( + kind = field.Type.Kind() + koanfTag = field.Tag.Get("koanf") + parts = fullPath(koanfTag, parents...) + fullPath = strings.Join(parts, ".") + ) + + fi := ConfigOption{ + FullPath: fullPath, + kind: kind, + Name: koanfTag, + // Env variable is set later, after we determine the type + } + + ft := field.Tag.Get("flag") + if ft != "" { + flag, shorthand := getFlagFromTag(ft) + + cli := CLIFlag{ + Flag: flag, + Shorthand: shorthand, + Default: cliDefaults[fullPath], + Description: getFlagDescription(flag), + } + + fi.CLI = cli + } + + switch kind { + + case reflect.Struct: + children := getStructInfo(field.Type, cliDefaults, parts...) + fi.Children = children + + case reflect.Slice, reflect.Array: + fi.elementKind = field.Type.Elem().Kind() + fi.Env = envName(koanfTag, parents...) + + default: + fi.Env = envName(koanfTag, parents...) + } + + out = append(out, fi) + } + + return out +} + +func envName(name string, parents ...string) string { + + parts := make([]string, 0) + for i := len(parents) - 1; i >= 0; i-- { + title := strings.Title(parents[i]) + parts = append(parts, title) + } + + nameFields := strings.Split(name, "-") + var formattedName string + for _, field := range nameFields { + titled := strings.Title(field) + formattedName += titled + } + + var components []string + components = append(components, strings.TrimSuffix(blockless.EnvPrefix, EnvDelimiter)) // Trim trailing underscore so we don't repeat it. + components = append(components, parts...) + components = append(components, formattedName) + + return strings.Join(components, EnvDelimiter) +} diff --git a/config/documentation.go b/config/documentation.go new file mode 100644 index 00000000..dfa1bf39 --- /dev/null +++ b/config/documentation.go @@ -0,0 +1,5 @@ +package config + +func GetConfigDocumentation() []ConfigOption { + return getConfigOptions() +} diff --git a/config/flags.go b/config/flags.go new file mode 100644 index 00000000..b091c4a5 --- /dev/null +++ b/config/flags.go @@ -0,0 +1,105 @@ +package config + +import ( + "errors" + "fmt" + "strings" + + "github.com/knadh/koanf/providers/structs" + "github.com/spf13/pflag" +) + +func createFlags(fields []ConfigOption) (*pflag.FlagSet, map[string]string, error) { + + fs := pflag.NewFlagSet("b7s-node", pflag.ExitOnError) + fs.SortFlags = false + + for _, field := range fields { + err := addFlag(fs, field.CLI) + if err != nil { + return nil, nil, fmt.Errorf("could not add flag for config (name: %v, flag: %v, type: %v)", field.FullPath, field.CLI.Flag, field.kind.String()) + } + } + + mapping, err := mapCLIFlagsToConfig(fields) + if err != nil { + return nil, nil, fmt.Errorf("could not get mapping of CLI flags to config: %w", err) + } + + return fs, mapping, nil +} + +func addFlag(fs *pflag.FlagSet, fc CLIFlag) error { + + if fc.Flag == "" { + return nil + } + + switch def := fc.Default.(type) { + case uint: + fs.UintP(fc.Flag, fc.Shorthand, def, fc.Description) + + case string: + fs.StringP(fc.Flag, fc.Shorthand, def, fc.Description) + + case float64: + fs.Float64P(fc.Flag, fc.Shorthand, def, fc.Description) + + case int64: + fs.Int64P(fc.Flag, fc.Shorthand, def, fc.Description) + + case bool: + fs.BoolP(fc.Flag, fc.Shorthand, def, fc.Description) + + case []string: + fs.StringSliceP(fc.Flag, fc.Shorthand, nil, fc.Description) + + default: + return errors.New("unsupported type for a CLI flag. Extend support by adding handling for the new flag type") + } + + return nil +} + +func getFlagFromTag(tag string) (string, string) { + + tag = strings.TrimSpace(tag) + + fields := strings.Split(tag, ",") + switch len(fields) { + case 0: + return "", "" + case 1: + return fields[0], "" + default: + return fields[0], fields[1] + } +} + +// return mapping of CLI flag to the config path used by koanf. E.g. address => connectivity.address. +// We don't have to enfore uniqueness of CLI flags as pflag does that for us. +func mapCLIFlagsToConfig(fields []ConfigOption) (map[string]string, error) { + + flags := make(map[string]string) + for _, field := range fields { + if field.CLI.Flag == "" { + continue + } + flags[field.CLI.Flag] = field.FullPath + } + + return flags, nil +} + +func getDefaultFlagValues() map[string]any { + + cfg := structs.Provider(DefaultConfig, "koanf") + defaults, err := cfg.Read() + if err != nil { + return nil + } + + flat := make(map[string]any) + flattenMap("", defaults, flat) + return flat +} diff --git a/cmd/node/internal/config/group.go b/config/group.go similarity index 100% rename from cmd/node/internal/config/group.go rename to config/group.go diff --git a/config/helpers.go b/config/helpers.go new file mode 100644 index 00000000..67c50f88 --- /dev/null +++ b/config/helpers.go @@ -0,0 +1,43 @@ +package config + +// By default we have flags in a tree structure, but most of the time we just want the leaves. +func flattenConfigOptions(cfgOptions []ConfigOption) []ConfigOption { + out := make([]ConfigOption, 0, len(cfgOptions)) + for _, cfg := range cfgOptions { + expandConfigOption(cfg, &out) + } + return out +} + +func expandConfigOption(cfg ConfigOption, out *[]ConfigOption) { + if len(cfg.Children) == 0 { + *out = append(*out, cfg) + return + } + for _, fc := range cfg.Children { + expandConfigOption(fc, out) + } +} + +func flattenMap(prefix string, in map[string]any, flat map[string]any) { + for k, v := range in { + key := k + if prefix != "" { + key = prefix + "." + k + } + switch cv := v.(type) { + default: + flat[key] = v + + case map[string]any: + flattenMap(key, cv, flat) + } + } +} + +func fullPath(name string, parents ...string) []string { + var full []string + full = append(full, parents...) + full = append(full, name) + return full +} diff --git a/config/helpers_test.go b/config/helpers_test.go new file mode 100644 index 00000000..5cd14da9 --- /dev/null +++ b/config/helpers_test.go @@ -0,0 +1,40 @@ +package config + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestFlattenMap(t *testing.T) { + + in := map[string]any{ + "k1": "v1", + "k2": "v2", + "k3": map[string]any{ + "k3-1": "v3-1", + "k3-2": "v3-2", + "k3-3": map[string]any{ + "k3-3-1": "v3-3-1", + "k3-3-2": "v3-3-2", + }, + }, + "k4": map[string]any{ + "k4-1": map[string]any{ + "k4-1-1": "v4-1-1", + }, + }, + } + + flat := make(map[string]any) + flattenMap("", in, flat) + + require.Len(t, flat, 7) + require.Equal(t, flat["k1"], "v1") + require.Equal(t, flat["k2"], "v2") + require.Equal(t, flat["k3.k3-1"], "v3-1") + require.Equal(t, flat["k3.k3-2"], "v3-2") + require.Equal(t, flat["k3.k3-3.k3-3-1"], "v3-3-1") + require.Equal(t, flat["k3.k3-3.k3-3-2"], "v3-3-2") + require.Equal(t, flat["k4.k4-1.k4-1-1"], "v4-1-1") +} diff --git a/config/load.go b/config/load.go new file mode 100644 index 00000000..fe62e638 --- /dev/null +++ b/config/load.go @@ -0,0 +1,124 @@ +package config + +import ( + "fmt" + "os" + "strings" + + "github.com/fatih/camelcase" + "github.com/knadh/koanf/parsers/yaml" + "github.com/knadh/koanf/providers/env" + "github.com/knadh/koanf/providers/file" + "github.com/knadh/koanf/providers/posflag" + "github.com/knadh/koanf/v2" + "github.com/spf13/pflag" + + "github.com/blocklessnetwork/b7s/models/blockless" +) + +const ( + defaultDelimiter = "." + EnvDelimiter = "_" +) + +func Load() (*Config, error) { + return load(os.Args[1:]) +} + +func load(args []string) (*Config, error) { + + var configPath string + + configOptions := flattenConfigOptions(getConfigOptions()) + flags, mapping, err := createFlags(configOptions) + if err != nil { + return nil, fmt.Errorf("could not create CLI flags for config: %w", err) + } + + flags.StringVar(&configPath, "config", "", "path to a config file") + + // General flags. + flags.Parse(args) + + delimiter := defaultDelimiter + konfig := koanf.New(delimiter) + + err = konfig.Load(env.ProviderWithValue(blockless.EnvPrefix, EnvDelimiter, envClean), nil) + if err != nil { + return nil, fmt.Errorf("could not load configuration from env: %w", err) + } + + if configPath != "" { + err = konfig.Load(file.Provider(configPath), yaml.Parser()) + if err != nil { + return nil, fmt.Errorf("could not load config file: %w", err) + } + } + + // For the sake of usability flags have a flat structure - e.g. port or cpu-percentage-limit. + // For use in config files, we prefer a structured layout, e.g. connectivity=>port or worker=>cpu-percentage-limit. + // This callback translates the flag names from a flat layout to the structured one, so that koanf knows how to match + // analogous values. + translate := cliFlagTranslate(mapping, flags) + + err = konfig.Load(posflag.ProviderWithFlag(flags, delimiter, konfig, translate), nil) + if err != nil { + return nil, fmt.Errorf("could not load config: %w", err) + } + + var cfg Config + err = konfig.Unmarshal("", &cfg) + if err != nil { + return nil, fmt.Errorf("could not unmarshal konfig: %w", err) + } + + return &cfg, nil +} + +func cliFlagTranslate(mapping map[string]string, fs *pflag.FlagSet) func(*pflag.Flag) (string, any) { + + return func(flag *pflag.Flag) (string, any) { + key := flag.Name + val := posflag.FlagVal(fs, flag) + + // Should not happen. + skey, ok := mapping[key] + if !ok { + return key, val + } + + return skey, val + } +} + +// envClean will do the following: +// +// - split environment variables to parts ("B7S_Connectivity_DialbackAddress" => [ "Connectivity", "DialbackAddress"]) +// - translate individual parts from CamelCase to Kebab-Case ("DialbackAddress" => "Dialback-Address") +// - lowercase parts ("Dialback-Address" => "dialback-address") +// - join back parts using the environment variable delimiter (underscore) ("Connectivity_DialbackAddress" => "connectivity_dialback-address") +// +// Koanf then uses the underscore to determine structure and in which section the config option belongs. +func envClean(key string, value string) (string, any) { + + key = strings.TrimPrefix(key, blockless.EnvPrefix) + + sections := strings.Split(key, EnvDelimiter) + cleaned := make([]string, 0, len(sections)) + for _, part := range sections { + p := strings.ToLower(strings.Join(camelcase.Split(part), "-")) + cleaned = append(cleaned, p) + } + + ss := strings.Join(cleaned, EnvDelimiter) + + switch ss { + + default: + return ss, value + + // Kludge: For boot nodes and topics, return type should be a string slice. + case "boot-nodes", "topics": + return ss, strings.Split(value, ",") + } +} diff --git a/cmd/node/internal/config/load_test.go b/config/load_test.go similarity index 56% rename from cmd/node/internal/config/load_test.go rename to config/load_test.go index 7957c810..4b7edfd7 100644 --- a/cmd/node/internal/config/load_test.go +++ b/config/load_test.go @@ -173,8 +173,9 @@ func TestConfig_CLIArgsWithConfigFile(t *testing.T) { filepath := writeConfigFile(t, cfgMap) + // NOTE: For compatiblity with Windows we will manually append config param later because `shlex.Split` doesn't jive with Windows paths. cmdline := fmt.Sprintf( - "--role %v --runtime-path %v --concurrency %v --workspace %v --boot-nodes %v --log-level %v --address %v --port %v --cpu-percentage-limit %v --rest-api %v --config %v", + "--role %v --runtime-path %v --concurrency %v --workspace %v --boot-nodes %v --log-level %v --address %v --port %v --cpu-percentage-limit %v --rest-api %v", role, runtimePathCLI, concurrencyCLI, @@ -185,12 +186,13 @@ func TestConfig_CLIArgsWithConfigFile(t *testing.T) { portCLI, cpuPercentageLimitCLI, restAPICLI, - filepath, ) args, err := shlex.Split(cmdline) require.NoError(t, err) + args = append(args, "--config", fmt.Sprintf("%v", filepath)) + cfg, err := load(args) require.NoError(t, err) @@ -210,7 +212,153 @@ func TestConfig_CLIArgsWithConfigFile(t *testing.T) { require.Equal(t, runtimePathCLI, cfg.Worker.RuntimePath) require.Equal(t, websocketFile, cfg.Connectivity.Websocket) require.Equal(t, websocketPortFile, cfg.Connectivity.WebsocketPort) - require.Equal(t, restAPICLI, cfg.Head.API) + require.Equal(t, restAPICLI, cfg.Head.RestAPI) +} + +func TestConfig_Environment(t *testing.T) { + + const ( + role = "worker" + concurrency = uint(45) + bootNodes = "a,b,c,d" + topics = "topic1,topic2,topic3" + + peerDB = "/tmp/db/peer-db" + functionDB = "/tmp/db/function-db" + + logLevel = "trace" + + address = "127.0.0.1" + port = uint(9000) + dialbackPort = uint(9001) + websocket = true + websocketPort = uint(10000) + websocketDialbackPort = uint(10001) + + runtimePath = "/tmp/runtime" + cpuPercentageLimit = float64(0.97) + memoryLimit = int64(512_000) + ) + + t.Setenv("B7S_Role", role) + t.Setenv("B7S_Concurrency", fmt.Sprint(concurrency)) + t.Setenv("B7S_BootNodes", bootNodes) + t.Setenv("B7S_Topics", topics) + t.Setenv("B7S_PeerDB", peerDB) + t.Setenv("B7S_FunctionDB", functionDB) + t.Setenv("B7S_Log_Level", logLevel) + t.Setenv("B7S_Connectivity_Address", address) + t.Setenv("B7S_Connectivity_Port", fmt.Sprint(port)) + t.Setenv("B7S_Connectivity_DialbackPort", fmt.Sprint(dialbackPort)) + t.Setenv("B7S_Connectivity_Websocket", fmt.Sprint(websocket)) + t.Setenv("B7S_Connectivity_WebsocketPort", fmt.Sprint(websocketPort)) + t.Setenv("B7S_Connectivity_WebsocketDialbackPort", fmt.Sprint(websocketDialbackPort)) + t.Setenv("B7S_Worker_RuntimePath", runtimePath) + t.Setenv("B7S_Worker_CPUPercentageLimit", fmt.Sprint(cpuPercentageLimit)) + t.Setenv("B7S_Worker_MemoryLimit", fmt.Sprint(memoryLimit)) + + cfg, err := Load() + require.NoError(t, err) + + require.Equal(t, role, cfg.Role) + require.Equal(t, concurrency, cfg.Concurrency) + + nodeList := strings.Split(bootNodes, ",") + require.Equal(t, nodeList, cfg.BootNodes) + + topicList := strings.Split(topics, ",") + require.Equal(t, topicList, cfg.Topics) + + require.Equal(t, peerDB, cfg.PeerDB) + require.Equal(t, functionDB, cfg.FunctionDB) + require.Equal(t, logLevel, cfg.Log.Level) + require.Equal(t, address, cfg.Connectivity.Address) + require.Equal(t, port, cfg.Connectivity.Port) + require.Equal(t, dialbackPort, cfg.Connectivity.DialbackPort) + require.Equal(t, websocket, cfg.Connectivity.Websocket) + require.Equal(t, websocketPort, cfg.Connectivity.WebsocketPort) + require.Equal(t, websocketDialbackPort, cfg.Connectivity.WebsocketDialbackPort) + + require.Equal(t, runtimePath, cfg.Worker.RuntimePath) + require.Equal(t, cpuPercentageLimit, cfg.Worker.CPUPercentageLimit) + require.Equal(t, memoryLimit, cfg.Worker.MemoryLimitKB) +} + +func TestConfig_Priority(t *testing.T) { + + const ( + envWorkspace = "/tmp/env/workspace" + envAddress = "1.1.1.1" + envPort = uint(1) + envRuntimePath = "/tmp/env/runtime/path" + envLogLevel = "error" + + cfgWorkspace = "/tmp/cfg/workspace" + cfgAddress = "2.2.2.2" + cfgPort = uint(2) + cfgDialbackPort = uint(12) + + cliWorkspace = "/tmp/cli/workspace" + cliAddress = "3.3.3.3" + cliLogLevel = "debug" + ) + + var ( + cfgMap = map[string]any{ + "workspace": cfgWorkspace, + "connectivity": map[string]any{ + "address": cfgAddress, + "port": cfgPort, + "dialback-port": cfgDialbackPort, + }, + } + ) + + filepath := writeConfigFile(t, cfgMap) + + t.Setenv("B7S_Workspace", envWorkspace) + t.Setenv("B7S_Connectivity_Address", envAddress) + t.Setenv("B7S_Connectivity_Port", fmt.Sprint(envPort)) + t.Setenv("B7S_Worker_RuntimePath", envRuntimePath) + t.Setenv("B7S_Log_Level", envLogLevel) + + // NOTE: For compatiblity with Windows we will manually append config param later because `shlex.Split` doesn't jive with Windows paths. + cmdline := fmt.Sprintf( + "--workspace %v --address %v --log-level %v", + cliWorkspace, + cliAddress, + cliLogLevel, + ) + + args, err := shlex.Split(cmdline) + require.NoError(t, err) + + args = append(args, "--config", fmt.Sprintf("%v", filepath)) + + cfg, err := load(args) + require.NoError(t, err) + + // Verify resulting config. + // + // 1. CLI flags override everything + // 2. Config file overrides environment variables + // 3. Environment variables + // + // Any config option set via lower priority methods persists if it's not overwritten. + + // This is set only via env. + require.Equal(t, envRuntimePath, cfg.Worker.RuntimePath) + + // This is set in config file and not overwritten by CLI flags, so it should remain active. + require.Equal(t, cfgPort, cfg.Connectivity.Port) + // This is only set in config file. + require.Equal(t, cfgDialbackPort, cfg.Connectivity.DialbackPort) + + // CLI flags rule everything. + require.Equal(t, cliWorkspace, cfg.Workspace) + require.Equal(t, cliAddress, cfg.Connectivity.Address) + require.Equal(t, cliLogLevel, cfg.Log.Level) + } func writeConfigFile(t *testing.T, m map[string]any) string { diff --git a/go.mod b/go.mod index 5cad79a8..7e3d6c72 100644 --- a/go.mod +++ b/go.mod @@ -9,6 +9,7 @@ require ( github.com/cavaliergopher/grab/v3 v3.0.1 github.com/cockroachdb/pebble v1.0.0 github.com/containerd/cgroups/v3 v3.0.3 + github.com/fatih/camelcase v1.0.0 github.com/fatih/color v1.16.0 github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 github.com/hashicorp/go-hclog v1.3.0 @@ -16,8 +17,10 @@ require ( github.com/hashicorp/raft-boltdb/v2 v2.2.2 github.com/ipfs/boxo v0.17.0 github.com/knadh/koanf/parsers/yaml v0.1.0 + github.com/knadh/koanf/providers/env v0.1.0 github.com/knadh/koanf/providers/file v0.1.0 github.com/knadh/koanf/providers/posflag v0.1.0 + github.com/knadh/koanf/providers/structs v0.1.0 github.com/knadh/koanf/v2 v2.1.0 github.com/labstack/echo/v4 v4.11.4 github.com/libp2p/go-libp2p v0.33.2 @@ -33,11 +36,13 @@ require ( ) require ( + github.com/a-h/templ v0.2.648 // indirect github.com/armon/go-metrics v0.4.1 // indirect github.com/boltdb/bolt v1.3.1 // indirect github.com/cilium/ebpf v0.12.3 // indirect github.com/davecgh/go-spew v1.1.1 // indirect - github.com/fsnotify/fsnotify v1.6.0 // indirect + github.com/fatih/structs v1.1.0 // indirect + github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/getsentry/sentry-go v0.26.0 // indirect github.com/go-logr/logr v1.4.1 // indirect github.com/go-logr/stdr v1.2.2 // indirect diff --git a/go.sum b/go.sum index ec1937e8..d0ca8f88 100644 --- a/go.sum +++ b/go.sum @@ -14,6 +14,8 @@ github.com/DataDog/zstd v1.5.5 h1:oWf5W7GtOLgp6bciQYDmhHHjdhYkALu6S/5Ni9ZgSvQ= github.com/DataDog/zstd v1.5.5/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw= github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= +github.com/a-h/templ v0.2.648 h1:A1ggHGIE7AONOHrFaDTM8SrqgqHL6fWgWCijQ21Zy9I= +github.com/a-h/templ v0.2.648/go.mod h1:SA7mtYwVEajbIXFRh3vKdYm/4FYyLQAtPH1+KxzGPA8= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= @@ -93,9 +95,13 @@ github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymF github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/fatih/camelcase v1.0.0 h1:hxNvNX/xYBp0ovncs8WyWZrOrpBNub/JfaMvbURyft8= +github.com/fatih/camelcase v1.0.0/go.mod h1:yN2Sb0lFhZJUdVvtELVWefmrXpuZESvPmqwoZc+/fpc= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= +github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo= +github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= github.com/flynn/noise v1.1.0 h1:KjPQoQCEFdZDiP03phOvGi11+SVVhBG2wOWAorLsstg= github.com/flynn/noise v1.1.0/go.mod h1:xbMo+0i6+IGbYdJhF31t2eR1BIU0CYc12+BNAKwUTag= @@ -104,8 +110,8 @@ github.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiD github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= -github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= -github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= +github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= +github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= github.com/getsentry/sentry-go v0.26.0 h1:IX3++sF6/4B5JcevhdZfdKIHfyvMmAq/UnqcyT2H6mA= github.com/getsentry/sentry-go v0.26.0/go.mod h1:lc76E2QywIyW8WuBnwl8Lc4bkmQH4+w1gwTf25trprY= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= @@ -264,10 +270,14 @@ github.com/knadh/koanf/maps v0.1.1 h1:G5TjmUh2D7G2YWf5SQQqSiHRJEjaicvU0KpypqB3NI github.com/knadh/koanf/maps v0.1.1/go.mod h1:npD/QZY3V6ghQDdcQzl1W4ICNVTkohC8E73eI2xW4yI= github.com/knadh/koanf/parsers/yaml v0.1.0 h1:ZZ8/iGfRLvKSaMEECEBPM1HQslrZADk8fP1XFUxVI5w= github.com/knadh/koanf/parsers/yaml v0.1.0/go.mod h1:cvbUDC7AL23pImuQP0oRw/hPuccrNBS2bps8asS0CwY= +github.com/knadh/koanf/providers/env v0.1.0 h1:LqKteXqfOWyx5Ab9VfGHmjY9BvRXi+clwyZozgVRiKg= +github.com/knadh/koanf/providers/env v0.1.0/go.mod h1:RE8K9GbACJkeEnkl8L/Qcj8p4ZyPXZIQ191HJi44ZaQ= github.com/knadh/koanf/providers/file v0.1.0 h1:fs6U7nrV58d3CFAFh8VTde8TM262ObYf3ODrc//Lp+c= github.com/knadh/koanf/providers/file v0.1.0/go.mod h1:rjJ/nHQl64iYCtAW2QQnF0eSmDEX/YZ/eNFj5yR6BvA= github.com/knadh/koanf/providers/posflag v0.1.0 h1:mKJlLrKPcAP7Ootf4pBZWJ6J+4wHYujwipe7Ie3qW6U= github.com/knadh/koanf/providers/posflag v0.1.0/go.mod h1:SYg03v/t8ISBNrMBRMlojH8OsKowbkXV7giIbBVgbz0= +github.com/knadh/koanf/providers/structs v0.1.0 h1:wJRteCNn1qvLtE5h8KQBvLJovidSdntfdyIbbCzEyE0= +github.com/knadh/koanf/providers/structs v0.1.0/go.mod h1:sw2YZ3txUcqA3Z27gPlmmBzWn1h8Nt9O6EP/91MkcWE= github.com/knadh/koanf/v2 v2.1.0 h1:eh4QmHHBuU8BybfIJ8mB8K8gsGCD/AUQTdwGq/GzId8= github.com/knadh/koanf/v2 v2.1.0/go.mod h1:4mnTRbZCK+ALuBXHZMjDfG9y714L7TykVnZkXbMU3Es= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= @@ -669,7 +679,6 @@ golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/models/blockless/errors.go b/models/blockless/params.go similarity index 68% rename from models/blockless/errors.go rename to models/blockless/params.go index 6ee070d3..bda19e06 100644 --- a/models/blockless/errors.go +++ b/models/blockless/params.go @@ -2,6 +2,8 @@ package blockless import ( "errors" + + "github.com/libp2p/go-libp2p/core/protocol" ) // Sentinel errors. @@ -10,3 +12,8 @@ var ( ErrRollCallTimeout = errors.New("roll call timed out - not enough nodes responded") ErrExecutionNotEnoughNodes = errors.New("not enough execution results received") ) + +const ( + ProtocolID protocol.ID = "/b7s/work/1.0.0" + EnvPrefix string = "B7S_" +) diff --git a/models/blockless/protocol.go b/models/blockless/protocol.go deleted file mode 100644 index 7976e2e5..00000000 --- a/models/blockless/protocol.go +++ /dev/null @@ -1,9 +0,0 @@ -package blockless - -import ( - "github.com/libp2p/go-libp2p/core/protocol" -) - -const ( - ProtocolID protocol.ID = "/b7s/work/1.0.0" -)