diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 3c3af63..cec95ad 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -4,6 +4,6 @@ Before raising or requesting a review of the pull request, please check and conf - [ ] I have performed a self-review of my code and run any tests locally to check - [ ] I have added tests that prove that any changes are effective and work correctly -- [ ] I have any made corresponding changes, as needed, to the repository documentation +- [ ] I have made corresponding changes, as needed, to the repository documentation - [ ] Each commit in, and this pull request, have meaningful subjects and bodies for context - [ ] I have added `release/...`, `type/...`, and `changes/...` labels, as needed, to this pull request diff --git a/.taskfiles/go.yaml b/.taskfiles/go.yaml index eb14508..934ab88 100644 --- a/.taskfiles/go.yaml +++ b/.taskfiles/go.yaml @@ -188,6 +188,7 @@ tasks: - '.golangci.yaml' deps: - task: fmt + - task: modules cmds: - cmd: golangci-lint --config '{{ .root }}/.golangci.yaml' run - cmd: echo -e '{{ .cg }}Passed{{ .cc }}' diff --git a/config/serve.yaml b/config/serve.yaml index 2561e6b..95733bf 100644 --- a/config/serve.yaml +++ b/config/serve.yaml @@ -3,6 +3,9 @@ web: bind: address: localhost port: 8080 + proxies: + - '::1' + - '172.27.4.188' logging: json: true diff --git a/go.mod b/go.mod index 55fbc00..f4b2639 100644 --- a/go.mod +++ b/go.mod @@ -4,18 +4,37 @@ go 1.22.4 require ( github.com/MakeNowJust/heredoc/v2 v2.0.1 + github.com/gin-gonic/gin v1.10.0 + github.com/samber/slog-gin v1.13.3 github.com/spf13/cobra v1.8.1 github.com/spf13/viper v1.19.0 github.com/stretchr/testify v1.9.0 ) require ( + github.com/bytedance/sonic v1.11.6 // indirect + github.com/bytedance/sonic/loader v0.1.1 // indirect + github.com/cloudwego/base64x v0.1.4 // indirect + github.com/cloudwego/iasm v0.2.0 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect + github.com/gabriel-vasile/mimetype v1.4.3 // indirect + github.com/gin-contrib/sse v0.1.0 // indirect + github.com/go-playground/locales v0.14.1 // indirect + github.com/go-playground/universal-translator v0.18.1 // indirect + github.com/go-playground/validator/v10 v10.20.0 // indirect + github.com/goccy/go-json v0.10.2 // indirect + github.com/google/uuid v1.4.0 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/klauspost/cpuid/v2 v2.2.7 // indirect + github.com/leodido/go-urn v1.4.0 // indirect github.com/magiconair/properties v1.8.7 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect github.com/pelletier/go-toml/v2 v2.2.2 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/sagikazarmark/locafero v0.4.0 // indirect @@ -25,11 +44,19 @@ require ( github.com/spf13/cast v1.6.0 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/subosito/gotenv v1.6.0 // indirect + github.com/twitchyliquid64/golang-asm v0.15.1 // indirect + github.com/ugorji/go/codec v1.2.12 // indirect + go.opentelemetry.io/otel v1.24.0 // indirect + go.opentelemetry.io/otel/trace v1.24.0 // indirect go.uber.org/atomic v1.9.0 // indirect go.uber.org/multierr v1.9.0 // indirect + golang.org/x/arch v0.8.0 // indirect + golang.org/x/crypto v0.23.0 // indirect golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect - golang.org/x/sys v0.18.0 // indirect - golang.org/x/text v0.14.0 // indirect + golang.org/x/net v0.25.0 // indirect + golang.org/x/sys v0.22.0 // indirect + golang.org/x/text v0.16.0 // indirect + google.golang.org/protobuf v1.34.1 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 785e8e4..302ccd0 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,13 @@ github.com/MakeNowJust/heredoc/v2 v2.0.1 h1:rlCHh70XXXv7toz95ajQWOWQnN4WNLt0TdpZYIR/J6A= github.com/MakeNowJust/heredoc/v2 v2.0.1/go.mod h1:6/2Abh5s+hc3g9nbWLe9ObDIOhaRrqsyY9MWy+4JdRM= +github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0= +github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4= +github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM= +github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= +github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y= +github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= +github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg= +github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY= github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -9,20 +17,54 @@ github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHk github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= 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/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= -github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= +github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= +github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= +github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= +github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU= +github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y= +github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= +github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= +github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= +github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= +github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= +github.com/go-playground/validator/v10 v10.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBExVwjEviJTixqxL8= +github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= +github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= +github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4= +github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM= +github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= +github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= +github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -35,6 +77,8 @@ github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6ke github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4= github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= +github.com/samber/slog-gin v1.13.3 h1:BXVMDktx27zrr/PMYLvrEAOeIylBFtuemlQjgDUT3fc= +github.com/samber/slog-gin v1.13.3/go.mod h1:7+YTBV20co5pQ+802hgAncESKtcZMAOKFUBpuT8IhXo= github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= @@ -52,23 +96,44 @@ github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSS github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= +github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= +github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= +github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= +github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= +go.opentelemetry.io/otel v1.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo= +go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo= +go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI= +go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU= go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI= go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ= +golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= +golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc= +golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= +golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI= +golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g= golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k= -golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= -golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= +golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= +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.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= +golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= +golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= +google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg= +google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -77,3 +142,5 @@ gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= +rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= diff --git a/internal/cmd/serve.go b/internal/cmd/serve.go index c3cc54b..11f00bf 100644 --- a/internal/cmd/serve.go +++ b/internal/cmd/serve.go @@ -81,7 +81,7 @@ func runServe(_ *cobra.Command, _ []string) error { serve.Prepare() logger.Start(attrs) - err = serve.Run() + serve.Run() - return err + return nil } diff --git a/internal/serve/alive/main.go b/internal/serve/alive/main.go new file mode 100644 index 0000000..0549fee --- /dev/null +++ b/internal/serve/alive/main.go @@ -0,0 +1,23 @@ +package alive + +import ( + "net/http" + + "github.com/gin-gonic/gin" +) + +// Attach takes a reference to the Gin engine and attaches all the expected +// endpoints which cam be used by clients through this package. +func Attach(r *gin.Engine) { + r.GET("/alive", alive) +} + +// alive provides a basic endpoint that just returns a 200 OK response with a +// {"status":"alive"} JSON response, without processing, allowing to test if the +// web service it up and responding to requests, regardless of any other +// downstream service. +func alive(c *gin.Context) { + c.JSON(http.StatusOK, gin.H{ + "status": "alive", + }) +} diff --git a/internal/serve/alive/main_test.go b/internal/serve/alive/main_test.go new file mode 100644 index 0000000..0c9e125 --- /dev/null +++ b/internal/serve/alive/main_test.go @@ -0,0 +1 @@ +package alive_test diff --git a/internal/serve/config.go b/internal/serve/config.go index 7166aea..fc06032 100644 --- a/internal/serve/config.go +++ b/internal/serve/config.go @@ -1,3 +1,4 @@ +//nolint:mnd // defaults expect hard-coded numberic values in this file package serve import ( @@ -9,9 +10,25 @@ const ( port = 8080 ) +// Prepare pre-configured the expected configuration values inside Viper with +// the default values, which can be used through the rest of the code, and can +// be overridden by the YAML files provided to the application. func Prepare() { + // Configure the bindings for the web service, setting both the address and + // port to listen on, and which endpoints should be trusted for processing the + // X-Forwarded-For headers received from upstream connections viper.SetDefault("web.bind.hostname", host) viper.SetDefault("web.bind.port", port) + viper.SetDefault("web.bind.proxies", []string{"127.0.0.1", "::1"}) + + // Configure the basic timeouts for the http.Server resource + viper.SetDefault("web.timeouts.read", 5) + viper.SetDefault("web.timeouts.write", 10) + viper.SetDefault("web.timeouts.idle", 30) + viper.SetDefault("web.timeouts.headers", 2) + + // Provide configuration for the logger, including setting JSON, structured + // output, and the level of logging output by default viper.SetDefault("logging.json", true) viper.SetDefault("logging.level", "info") } diff --git a/internal/serve/healthz/main.go b/internal/serve/healthz/main.go new file mode 100644 index 0000000..2683076 --- /dev/null +++ b/internal/serve/healthz/main.go @@ -0,0 +1,29 @@ +package healthz + +import ( + "log/slog" + "net/http" + + "github.com/gin-gonic/gin" + + slogg "github.com/samber/slog-gin" +) + +// Attach takes a reference to the Gin engine and attaches all the expected +// endpoints which cam be used by clients through this package. +func Attach(r *gin.Engine) { + r.GET("/healthz", healthz) +} + +// healthz provides an endpoint for checking on the operational health of the +// service, checking downstream services are behaving as expected and reporting +// on their overall status, allowing the service to be marked as unhealthy and +// to stop processing further requests if there are known issues. +func healthz(c *gin.Context) { + slogg.AddCustomAttributes(c, slog.Group("healthz", slog.String("status", "ok"))) + c.JSON(http.StatusOK, gin.H{ + "status": "ok", + "database": "unknown", + "queue": "unknown", + }) +} diff --git a/internal/serve/healthz/main_test.go b/internal/serve/healthz/main_test.go new file mode 100644 index 0000000..47e6ad5 --- /dev/null +++ b/internal/serve/healthz/main_test.go @@ -0,0 +1 @@ +package healthz_test diff --git a/internal/serve/main.go b/internal/serve/main.go index c529563..2c4ccc9 100644 --- a/internal/serve/main.go +++ b/internal/serve/main.go @@ -1,12 +1,153 @@ package serve import ( + "context" + "errors" "log/slog" + "net" + "net/http" + "os/signal" + "syscall" + "time" + + "github.com/gin-gonic/gin" + "github.com/spf13/viper" + + "github.com/n3tuk/dashboard/internal/serve/alive" + "github.com/n3tuk/dashboard/internal/serve/healthz" + + slogg "github.com/samber/slog-gin" ) -func Run() error { - slog.Info("Starting dashboard web service") - slog.Debug("Debugging enabled") +// timeout provides the time allowed to gracefully shut down the service. +const timeout = 30 * time.Second + +// Run initiates the setup and startup of the web service, attaching the +// endpoints avoidable in each of the packages for dashboard, as well as +// monitoring for system signals and handling a graceful shutdown of the server +// when called. +func Run() { + // Create a context that listens for the interrupt signal from the Operating + // System so we can capture it and then trigger a graceful shutdown + ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM) + defer stop() + + router := SetupGin() + server := SetupServer(router) + + alive.Attach(router) + healthz.Attach(router) + + // Initialising the server in a goroutine so that it won't block the capture + // and processing of the system interrupt + go RunServer(ctx, server) + + // Restore default behaviour on the interrupt signal and notify user of shutdown. + <-ctx.Done() + stop() + slog.InfoContext(ctx, + "Shutting down dashboard gracefully", + slog.Group("server", + slog.String("address", server.Addr), + slog.String("timeout", timeout.String()), + ), + ) + + // Create a context that is used to inform the server it has only a set time + // to finish the request it is currently handling before being shut down + ctx, cancel := context.WithTimeout(context.Background(), timeout) + defer cancel() + + if err := server.Shutdown(ctx); err != nil { + slog.ErrorContext(ctx, + "Forced to shut down dashboard ungracefully", + slog.Group("server", + slog.String("address", server.Addr), + slog.String("timeout", timeout.String()), + ), + slog.Group("error", + slog.String("message", err.Error()), + ), + ) + } +} + +// SetupGin sets up the Gin package, adding in the standard middleware to +// process log entires and recorder from panics and errors which are unhandled +// by individual handlers. +func SetupGin() *gin.Engine { + gin.SetMode(gin.ReleaseMode) + + r := gin.New() + + r.Use(Logger()) + r.Use(gin.Recovery()) + + proxies := viper.GetStringSlice("web.bind.proxies") + if len(proxies) > 0 { + err := r.SetTrustedProxies(proxies) + if err != nil { + slog.Error( + "Unable to configure trusted proxies", + slog.Group( + "error", + slog.String("message", err.Error()), + ), + ) + } + } + + return r +} + +// Logger provides a structured logging logger which can be used by Gin using +// the new slog package, allowing for easy processing of log data. +func Logger() gin.HandlerFunc { + return slogg.NewWithConfig( + slog.Default().WithGroup("gin"), + slogg.Config{ + WithRequestID: true, + }, + ) +} + +// SetupServer sets up the http package web service, configuring the bindings +// and timeouts for processing requests, as well as attaching the Gin framework. +func SetupServer(router *gin.Engine) *http.Server { + return &http.Server{ + Addr: net.JoinHostPort( + viper.GetString("web.bind.hostname"), + viper.GetString("web.bind.port"), + ), + + ReadTimeout: time.Duration(viper.GetInt("web.timeouts.read")) * time.Second, + WriteTimeout: time.Duration(viper.GetInt("web.timeouts.write")) * time.Second, + IdleTimeout: time.Duration(viper.GetInt("web.timeouts.idle")) * time.Second, + ReadHeaderTimeout: time.Duration(viper.GetInt("web.timeouts.header")) * time.Second, + + Handler: router, + } +} + +// RunServer provides a function to be called as a goroutine which starts up and +// runs the web service in a dedicated thread, allowing the main thread to +// handle startup and shutdown independently. +func RunServer(ctx context.Context, server *http.Server) { + slog.InfoContext(ctx, + "Starting dashboard", + slog.Group( + "server", + slog.String("address", server.Addr), + ), + ) - return nil + err := server.ListenAndServe() + if err != nil && !errors.Is(err, http.ErrServerClosed) { + slog.ErrorContext(ctx, + "Failed to start web service", + slog.Group("error", + slog.String("message", err.Error()), + ), + ) + } } diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index 988a5d6..0000000 --- a/requirements.txt +++ /dev/null @@ -1 +0,0 @@ -codecov-cli==0.7.3 diff --git a/schemas/serve.json b/schemas/serve.json index 9a888ca..5c6ae1a 100644 --- a/schemas/serve.json +++ b/schemas/serve.json @@ -11,6 +11,9 @@ "properties": { "bind": { "$ref": "#/$defs/bind" + }, + "timeouts": { + "$ref": "#/$defs/timeouts" } } }, @@ -25,6 +28,9 @@ }, "port": { "$ref": "#/$defs/port" + }, + "proxies": { + "$ref": "#/$defs/proxies" } } }, @@ -54,6 +60,80 @@ "minimum": 1025, "maximum": 65535 }, + "proxies": { + "title": "Proxy Addresses", + "description": "A list of IPv4 and IPv6 addresses or CIDRs which should be trusted for processing the remote Client address", + "type": "array", + "default": ["127.0.0.1", "::1"], + "items": { + "anyOf": [ + { + "type": "string", + "format": "hostname" + }, + { + "type": "string", + "format": "ipv4" + }, + { + "type": "string", + "format": "ipv6" + } + ] + } + }, + "timeouts": { + "title": "Web Server Timeouts", + "description": "Configure timeouts for the web server", + "type": "object", + "additionalProperties": false, + "properties": { + "read": { + "$ref": "#/$defs/timeout-read" + }, + "write": { + "$ref": "#/$defs/timeout-write" + }, + "idle": { + "$ref": "#/$defs/timeout-idle" + }, + "headers": { + "$ref": "#/$defs/timeout-headers" + } + } + }, + "timeout-read": { + "title": "Request Read Timeout", + "description": "The maximum time to read the full request, including the body, from the client", + "type": "number", + "default": 5, + "minimum": 0, + "maximum": 60 + }, + "timeout-write": { + "title": "Request Read Timeout", + "description": "The maximum time to write the full response, including the body, to the client", + "type": "number", + "default": 10, + "minimum": 0, + "maximum": 60 + }, + "timeout-idle": { + "title": "Request Read Timeout", + "description": "The maximum time to keep a connection open between requests", + "type": "number", + "default": 30, + "minimum": 0, + "maximum": 60 + }, + "timeout-headers": { + "title": "Request Headers Timeout", + "description": "The maximum time to read the headers for the request from the client", + "type": "number", + "default": 2, + "minimum": 0, + "maximum": 60 + }, "logging": { "title": "Logging Configuration", "description": "Configure the logging output from the dashboard send command",