From 83ec2fbdce2e91025ed064eea7f8932a770352ca Mon Sep 17 00:00:00 2001 From: Gert Drapers <1533850+gertd@users.noreply.github.com> Date: Thu, 7 Nov 2024 11:55:59 -0800 Subject: [PATCH 1/6] use plaintext --- go.mod | 20 +++++++++++--------- go.sum | 42 ++++++++++++++--------------------------- pkg/cc/config/config.go | 6 ++++++ pkg/cc/wire_gen.go | 4 ++-- 4 files changed, 33 insertions(+), 39 deletions(-) diff --git a/go.mod b/go.mod index 7ded0070..a8f4d6de 100644 --- a/go.mod +++ b/go.mod @@ -1,13 +1,15 @@ module github.com/aserto-dev/topaz -go 1.22.0 +go 1.23 + +toolchain go1.23.2 // replace github.com/aserto-dev/azm => ../azm // replace github.com/aserto-dev/go-directory => ../go-directory // replace github.com/aserto-dev/go-edge-ds => ../go-edge-ds // replace github.com/aserto-dev/go-topaz-ui => ../go-topaz-ui // replace github.com/aserto-dev/runtime => ../runtime -// replace github.com/aserto-dev/service-host => ../service-host +replace github.com/aserto-dev/service-host => ../service-host replace github.com/adrg/xdg => ./pkg/cli/xdg @@ -129,7 +131,7 @@ require ( github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/itchyny/timefmt-go v0.1.6 // indirect github.com/jhump/protoreflect v1.16.0 // indirect - github.com/klauspost/compress v1.17.9 // indirect + github.com/klauspost/compress v1.17.11 // indirect github.com/lestrrat-go/blackmagic v1.0.2 // indirect github.com/lestrrat-go/httpcc v1.0.1 // indirect github.com/lestrrat-go/httprc v1.0.6 // indirect @@ -159,25 +161,25 @@ require ( github.com/nats-io/nuid v1.0.1 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/panmari/cuckoofilter v1.0.6 // indirect - github.com/pelletier/go-toml/v2 v2.2.2 // indirect + github.com/pelletier/go-toml/v2 v2.2.3 // indirect github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect - github.com/prometheus/client_golang v1.20.4 // indirect + github.com/prometheus/client_golang v1.20.5 // indirect github.com/prometheus/client_model v0.6.1 // indirect - github.com/prometheus/common v0.55.0 // indirect + github.com/prometheus/common v0.60.1 // indirect github.com/prometheus/procfs v0.15.1 // indirect - github.com/prometheus/statsd_exporter v0.26.1 // indirect + github.com/prometheus/statsd_exporter v0.27.1 // indirect github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 // indirect github.com/rivo/uniseg v0.4.7 // indirect - github.com/rs/cors v1.11.0 // indirect + github.com/rs/cors v1.11.1 // indirect github.com/sagikazarmark/locafero v0.4.0 // indirect github.com/sagikazarmark/slog-shim v0.1.0 // indirect github.com/segmentio/asm v1.2.0 // indirect github.com/shirou/gopsutil/v3 v3.23.12 // indirect github.com/shoenig/go-m1cpu v0.1.6 // indirect github.com/sirupsen/logrus v1.9.3 // indirect - github.com/slok/go-http-metrics v0.12.0 // indirect + github.com/slok/go-http-metrics v0.13.0 // indirect github.com/sourcegraph/conc v0.3.0 // indirect github.com/spf13/afero v1.11.0 // indirect github.com/spf13/cast v1.6.0 // indirect diff --git a/go.sum b/go.sum index e00682c8..b22be4fe 100644 --- a/go.sum +++ b/go.sum @@ -421,8 +421,6 @@ github.com/aserto-dev/aserto-grpc v0.2.6 h1:h64MYALF5zLm2sSKcLEtyXyrJvZSxfqTOmQ1 github.com/aserto-dev/aserto-grpc v0.2.6/go.mod h1:Vki74KINVfnwtJ8QGzRm+xHNjsJ2KUWFtXhezJK9DEg= github.com/aserto-dev/aserto-management v0.9.6 h1:c03a106uABQIlY4PshlaABwj2RscqsxRa+XDYLp1HQY= github.com/aserto-dev/aserto-management v0.9.6/go.mod h1:Bs2AEKP/JGd01usokqq7C4iI6/0smcE6sjm3k3nOfIA= -github.com/aserto-dev/azm v0.1.19-0.20241106213342-b8df93866e1b h1:TVhe1ZIeYtSe4AGV0wyAsfKZ9ldMdG+qB1EhaIWJwYQ= -github.com/aserto-dev/azm v0.1.19-0.20241106213342-b8df93866e1b/go.mod h1:uxUKXGA6d41ZbJBfEu+/PwXGLuI3tErKkH7VPZHDcX4= github.com/aserto-dev/azm v0.1.19 h1:pFHNzUhNeg5vKjpXsFnQGfKBbIsBra5Jb+hJt5ohpaQ= github.com/aserto-dev/azm v0.1.19/go.mod h1:uxUKXGA6d41ZbJBfEu+/PwXGLuI3tErKkH7VPZHDcX4= github.com/aserto-dev/certs v0.0.7 h1:WsFrHywNE88tgUeDJ2FsV+mgy46QFQvCIYm7c5TNIKE= @@ -431,22 +429,12 @@ github.com/aserto-dev/errors v0.0.11 h1:CXo+Uwmh09doG2HvL1SC8Fnne8f9VPrGyEQPtogA github.com/aserto-dev/errors v0.0.11/go.mod h1:T1YQOtcxpgBriPTn5HXJkD/QukYz5YojYOIzGMo0ybM= github.com/aserto-dev/go-aserto v0.32.1 h1:cavSweinomGBIGx6PZxyY1aicfnTG5nnRoeLUa6cgLU= github.com/aserto-dev/go-aserto v0.32.1/go.mod h1:62sSMN5NvzoRJL9HjmR5x4mhVPs/7CX3/3Q/MuJQGIA= -github.com/aserto-dev/go-authorizer v0.20.11-0.20241106220059-15f480b0e1ba h1:srKf7DW5YWW7hdu+fs2t/qyenoBOMd8vVhI96/eZMmU= -github.com/aserto-dev/go-authorizer v0.20.11-0.20241106220059-15f480b0e1ba/go.mod h1:iwVdTU2xOrNW0TZ+UWX+Mn2hgR2Lj1XmKgei0tt5pbY= github.com/aserto-dev/go-authorizer v0.20.11 h1:OaYJwyljt2yBuDtIiMl1mqjyMU0dUuv1eZsBNHo4+O4= github.com/aserto-dev/go-authorizer v0.20.11/go.mod h1:iwVdTU2xOrNW0TZ+UWX+Mn2hgR2Lj1XmKgei0tt5pbY= github.com/aserto-dev/go-decision-logs v0.0.4 h1:beu/mhqZ92ovhSIPOv2f4q0Ci7HWNLla/j/x+ZD5eHw= github.com/aserto-dev/go-decision-logs v0.0.4/go.mod h1:W50DNu4HPCk+iyI39cP3+KBytdrQYVieSPXh9StuRzA= -github.com/aserto-dev/go-directory v0.31.14-0.20241106210228-2398d57c8f5f h1:V06S/fWkDyEEg2F7r25IAgL/wHYUiEQIJawukytmWvY= -github.com/aserto-dev/go-directory v0.31.14-0.20241106210228-2398d57c8f5f/go.mod h1:KCByUcsikTzGgcsSlXxUWEwUmDJw1BikI/pC6826J7k= github.com/aserto-dev/go-directory v0.31.14 h1:3r6PcjSru68a9319Qv6NkwtuG6z/YFVu4VWIOa1wclY= github.com/aserto-dev/go-directory v0.31.14/go.mod h1:KCByUcsikTzGgcsSlXxUWEwUmDJw1BikI/pC6826J7k= -github.com/aserto-dev/go-edge-ds v0.32.10-0.20241106214545-b211bb71ee4a h1:Xn5x15p3aPw8iUuWL5V7WC+296IMggHSz0e619jFN0I= -github.com/aserto-dev/go-edge-ds v0.32.10-0.20241106214545-b211bb71ee4a/go.mod h1:S5e5TqhXdvgCR2GJjVYtFV1a05vSydYiBG04JY0dTKY= -github.com/aserto-dev/go-edge-ds v0.32.11-0.20241107003713-018607a80926 h1:nfuzrcegKgQBJbZx+cwcoUHfeQsqPLzyajf8QLXuFxA= -github.com/aserto-dev/go-edge-ds v0.32.11-0.20241107003713-018607a80926/go.mod h1:S5e5TqhXdvgCR2GJjVYtFV1a05vSydYiBG04JY0dTKY= -github.com/aserto-dev/go-edge-ds v0.32.11 h1:aIHu1848fmJGyIFQdKNujtmR03hc/VFpigUaDEj6zy0= -github.com/aserto-dev/go-edge-ds v0.32.11/go.mod h1:S5e5TqhXdvgCR2GJjVYtFV1a05vSydYiBG04JY0dTKY= github.com/aserto-dev/go-edge-ds v0.32.12 h1:yAUteGpPUpJDEss/ztQLRb7bqByF+YavrqYk8RNFPXA= github.com/aserto-dev/go-edge-ds v0.32.12/go.mod h1:eGHfZCyDUSQqvzbGRM+tm+cjH72uw6kdqa3NrSA1mT0= github.com/aserto-dev/go-grpc v0.8.69 h1:a0V+lbgkc7XRQb5SS2CZYDUBTxr/aCEGLUubqhX9qbA= @@ -465,8 +453,6 @@ github.com/aserto-dev/runtime v0.69.0 h1:B1G1117xNctaD35hZKlULgDD8eAC7tN15OXleji github.com/aserto-dev/runtime v0.69.0/go.mod h1:fJUkusCQiLfBZ4oV7xiUcXAmq5q/d+zvpEvP6i5zpIA= github.com/aserto-dev/self-decision-logger v0.0.7 h1:Ubv7KDGMJIzWR2jQekAnhb0+HzY724+uRzDkjN2g2YQ= github.com/aserto-dev/self-decision-logger v0.0.7/go.mod h1:lCuqXg/vr5gt3SvMoZGfOQuKCBP8ar9mih1DOxn0QLI= -github.com/aserto-dev/service-host v0.0.16 h1:M0Ur9esiFw9gmtiIfW+8mx2wgYOAn4b25G0KLcEKFEE= -github.com/aserto-dev/service-host v0.0.16/go.mod h1:M3Mn8Zg7zp5U4sK32kclokPX1gi9xoOxJF1EYM67SEY= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= @@ -769,8 +755,8 @@ github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7V github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= -github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= +github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc= +github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= @@ -879,8 +865,8 @@ github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2sz github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/panmari/cuckoofilter v1.0.6 h1:WKb1aSj16h22x0CKVtTCaRkJiCnVGPLEMGbNY8xwXf8= github.com/panmari/cuckoofilter v1.0.6/go.mod h1:bKADbQPGbN6TxUvo/IbMEIUbKuASnpsOvrLTgpSX0aU= -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/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= +github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= @@ -899,8 +885,8 @@ github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqr github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= github.com/prometheus/client_golang v1.12.2/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= github.com/prometheus/client_golang v1.13.0/go.mod h1:vTeo+zgvILHsnnj/39Ou/1fPN5nJFOEMgftOUOmlvYQ= -github.com/prometheus/client_golang v1.20.4 h1:Tgh3Yr67PaOv/uTqloMsCEdeuFTatm5zIq5+qNN23vI= -github.com/prometheus/client_golang v1.20.4/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= +github.com/prometheus/client_golang v1.20.5 h1:cxppBPuYhUnsO6yo/aoRol4L7q7UFfdm+bR9r+8l63Y= +github.com/prometheus/client_golang v1.20.5/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= @@ -913,8 +899,8 @@ github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9 github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= github.com/prometheus/common v0.35.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA= github.com/prometheus/common v0.37.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA= -github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc= -github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8= +github.com/prometheus/common v0.60.1 h1:FUas6GcOw66yB/73KC+BOZoFJmbo/1pojoILArPAaSc= +github.com/prometheus/common v0.60.1/go.mod h1:h0LYf1R1deLSKtD4Vdg8gy4RuOvENW2J/h19V5NADQw= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= @@ -924,8 +910,8 @@ github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0ua github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= github.com/prometheus/statsd_exporter v0.22.7/go.mod h1:N/TevpjkIh9ccs6nuzY3jQn9dFqnUakOjnEuMPJJJnI= -github.com/prometheus/statsd_exporter v0.26.1 h1:ucbIAdPmwAUcA+dU+Opok8Qt81Aw8HanlO+2N/Wjv7w= -github.com/prometheus/statsd_exporter v0.26.1/go.mod h1:XlDdjAmRmx3JVvPPYuFNUg+Ynyb5kR69iPPkQjxXFMk= +github.com/prometheus/statsd_exporter v0.27.1 h1:tcRJOmwlA83HPfWzosAgr2+zEN5XDFv+M2mn/uYkn5Y= +github.com/prometheus/statsd_exporter v0.27.1/go.mod h1:vA6ryDfsN7py/3JApEst6nLTJboq66XsNcJGNmC88NQ= github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 h1:N/ElC8H3+5XpJzTSTfLsJV/mx9Q9g7kxmchpfZyxgzM= github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/rivo/tview v0.0.0-20240921122403-a64fc48d7654 h1:oa+fljZiaJUVyiT7WgIM3OhirtwBm0LJA97LvWUlBu8= @@ -938,8 +924,8 @@ github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6L github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= -github.com/rs/cors v1.11.0 h1:0B9GE/r9Bc2UxRMMtymBkHTenPkHDv0CW4Y98GBY+po= -github.com/rs/cors v1.11.0/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= +github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA= +github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8= github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= @@ -963,8 +949,8 @@ github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6Mwd github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= -github.com/slok/go-http-metrics v0.12.0 h1:mAb7hrX4gB4ItU6NkFoKYdBslafg3o60/HbGBRsKaG8= -github.com/slok/go-http-metrics v0.12.0/go.mod h1:Ee/mdT9BYvGrlGzlClkK05pP2hRHmVbRF9dtUVS8LNA= +github.com/slok/go-http-metrics v0.13.0 h1:lQDyJJx9wKhmbliyUsZ2l6peGnXRHjsjoqPt5VYzcP8= +github.com/slok/go-http-metrics v0.13.0/go.mod h1:HIr7t/HbN2sJaunvnt9wKP9xoBBVZFo1/KiHU3b0w+4= 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/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= diff --git a/pkg/cc/config/config.go b/pkg/cc/config/config.go index d304e4f3..850a6f80 100644 --- a/pkg/cc/config/config.go +++ b/pkg/cc/config/config.go @@ -249,6 +249,12 @@ func setDefaultCerts(cfg *Config) error { if config.GRPC.ListenAddress == "" { return errors.New(fmt.Sprintf("%s must have a grpc listen address specified", srvName)) } + + if config.GRPC.Certs.TLSCertPath == "" { + fmt.Fprintf(os.Stderr, "SKIPPED setDefaultCerts for %q\n", srvName) + continue + } + if config.GRPC.Certs.TLSCACertPath == "" || config.GRPC.Certs.TLSCertPath == "" || config.GRPC.Certs.TLSKeyPath == "" { config.GRPC.Certs.TLSKeyPath = filepath.Join(DefaultTLSGenDir, fmt.Sprintf("%s_grpc.key", srvName)) config.GRPC.Certs.TLSCertPath = filepath.Join(DefaultTLSGenDir, fmt.Sprintf("%s_grpc.crt", srvName)) diff --git a/pkg/cc/wire_gen.go b/pkg/cc/wire_gen.go index 5164daae..f93dd17c 100644 --- a/pkg/cc/wire_gen.go +++ b/pkg/cc/wire_gen.go @@ -30,8 +30,8 @@ func buildCC(logOutput logger.Writer, errOutput logger.ErrWriter, configPath con if err != nil { return nil, nil, err } - generator := certs.NewGenerator(zerologLogger) - configConfig, err := config.NewConfig(configPath, zerologLogger, overrides, generator) + // generator := certs.NewGenerator(zerologLogger) + configConfig, err := config.NewConfig(configPath, zerologLogger, overrides, nil) if err != nil { return nil, nil, err } From 04d25f09768bdfecf9e96651d5b6d04eed985bb6 Mon Sep 17 00:00:00 2001 From: Gert Drapers Date: Thu, 14 Nov 2024 21:08:17 -0800 Subject: [PATCH 2/6] implement NoTLS / --plaintext support ensure test-snapshot produces a valid semver container tag Revert "ensure test-snapshot produces a valid semver container tag" This reverts commit cdcb17661a731654854a4a2f0c556be4eb81cf28. use valid semver for test image use unique db file name for templates-no-tls cleanup tests (testifylint) cleanup linter issues rm go.opencensus.io (deprecated) dependencies update test db master adopt go-aserto@v0.33.1 TLSConfig & certs@v0.1.0 --- .gitignore | 3 + .golangci.yaml | 5 + .goreleaser-test.yml | 4 +- .vscode/tasks.json | 19 -- assets/assets.go | 7 + assets/config/config-no-tls.yaml | 272 ++++++++++++++++ assets/db/acmecorp.db | Bin 4194304 -> 4194304 bytes cmd/topaz/main.go | 1 + docs/config.md | 9 +- go.mod | 28 +- go.sum | 112 +------ go.work | 7 + internal/pkg/service/builder/defaults.go | 39 +++ internal/pkg/service/builder/go.mod | 46 +++ internal/pkg/service/builder/go.sum | 90 ++++++ internal/pkg/service/builder/health.go | 37 +++ internal/pkg/service/builder/service.go | 66 ++++ .../pkg/service/builder/service_factory.go | 302 ++++++++++++++++++ .../pkg/service/builder/service_manager.go | 257 +++++++++++++++ internal/pkg/service/builder/tls.go | 11 + {pkg/cli => internal/pkg}/xdg/LICENSE | 0 {pkg/cli => internal/pkg}/xdg/README.md | 0 {pkg/cli => internal/pkg}/xdg/base_dirs.go | 0 {pkg/cli => internal/pkg}/xdg/example_test.go | 0 internal/pkg/xdg/go.mod | 18 ++ internal/pkg/xdg/go.sum | 27 ++ .../pkg}/xdg/internal/pathutil/pathutil.go | 0 .../xdg/internal/pathutil/pathutil_test.go | 0 .../xdg/internal/pathutil/pathutil_unix.go | 0 .../internal/pathutil/pathutil_unix_test.go | 0 .../xdg/internal/pathutil/pathutil_windows.go | 0 .../pathutil/pathutil_windows_test.go | 0 {pkg/cli => internal/pkg}/xdg/paths_darwin.go | 0 .../pkg}/xdg/paths_darwin_test.go | 0 {pkg/cli => internal/pkg}/xdg/paths_unix.go | 0 .../pkg}/xdg/paths_unix_test.go | 0 .../cli => internal/pkg}/xdg/paths_windows.go | 0 .../pkg}/xdg/paths_windows_test.go | 0 {pkg/cli => internal/pkg}/xdg/user_dirs.go | 0 {pkg/cli => internal/pkg}/xdg/xdg.go | 0 {pkg/cli => internal/pkg}/xdg/xdg_test.go | 0 makefile | 16 +- pkg/app/authorizer.go | 11 +- pkg/app/console.go | 19 +- pkg/app/handlers/config.go | 3 +- pkg/app/tests/authz/authz_test.go | 17 +- pkg/app/tests/builtin/builtin_test.go | 3 +- pkg/app/tests/common/common.go | 2 +- pkg/app/tests/manifest/manifest_test.go | 10 +- pkg/app/tests/policy/policy_test.go | 13 +- pkg/app/tests/query/query_test.go | 21 +- .../template-no-tls/template-no-tls_test.go | 190 +++++++++++ pkg/app/tests/template/template_test.go | 15 +- pkg/app/topaz.go | 30 +- pkg/cc/cc.go | 10 +- pkg/cc/config/config.go | 101 +++--- pkg/cc/config/helper.go | 8 - pkg/cc/config/loader.go | 49 ++- pkg/cc/config/schema/config.json | 6 +- pkg/cc/config/schema/config.yaml | 1 - pkg/cc/config/templates.go | 82 +---- pkg/cc/wire_gen.go | 4 +- pkg/cli/cc/cc.go | 32 +- pkg/cli/cc/client.go | 10 + pkg/cli/cc/health.go | 7 +- pkg/cli/certs/generator.go | 11 +- pkg/cli/clients/authorizer/client.go | 41 +-- pkg/cli/clients/directory/backup.go | 3 +- pkg/cli/clients/directory/client.go | 45 +-- pkg/cli/clients/directory/export.go | 3 +- pkg/cli/cmd/cli.go | 2 +- pkg/cli/cmd/directory/backup.go | 2 +- pkg/cli/cmd/directory/exporter.go | 2 +- pkg/cli/cmd/directory/importer.go | 2 +- pkg/cli/cmd/directory/manifest.go | 6 +- pkg/cli/cmd/directory/restore.go | 2 +- pkg/cli/cmd/directory/stats.go | 2 +- pkg/cli/cmd/templates/install.go | 15 +- pkg/cli/cmd/topaz/stop.go | 1 + pkg/cli/fflag/fflag_test.go | 24 +- pkg/cli/xdg/go.mod | 14 - pkg/cli/xdg/go.sum | 12 - pkg/debug/debug.go | 22 +- pkg/version/version.go | 6 + scripts/container-tag.sh | 2 + scripts/templates-test.sh | 59 ++++ 86 files changed, 1753 insertions(+), 543 deletions(-) delete mode 100644 .vscode/tasks.json create mode 100644 assets/config/config-no-tls.yaml create mode 100644 go.work create mode 100644 internal/pkg/service/builder/defaults.go create mode 100644 internal/pkg/service/builder/go.mod create mode 100644 internal/pkg/service/builder/go.sum create mode 100644 internal/pkg/service/builder/health.go create mode 100644 internal/pkg/service/builder/service.go create mode 100644 internal/pkg/service/builder/service_factory.go create mode 100644 internal/pkg/service/builder/service_manager.go create mode 100644 internal/pkg/service/builder/tls.go rename {pkg/cli => internal/pkg}/xdg/LICENSE (100%) rename {pkg/cli => internal/pkg}/xdg/README.md (100%) rename {pkg/cli => internal/pkg}/xdg/base_dirs.go (100%) rename {pkg/cli => internal/pkg}/xdg/example_test.go (100%) create mode 100644 internal/pkg/xdg/go.mod create mode 100644 internal/pkg/xdg/go.sum rename {pkg/cli => internal/pkg}/xdg/internal/pathutil/pathutil.go (100%) rename {pkg/cli => internal/pkg}/xdg/internal/pathutil/pathutil_test.go (100%) rename {pkg/cli => internal/pkg}/xdg/internal/pathutil/pathutil_unix.go (100%) rename {pkg/cli => internal/pkg}/xdg/internal/pathutil/pathutil_unix_test.go (100%) rename {pkg/cli => internal/pkg}/xdg/internal/pathutil/pathutil_windows.go (100%) rename {pkg/cli => internal/pkg}/xdg/internal/pathutil/pathutil_windows_test.go (100%) rename {pkg/cli => internal/pkg}/xdg/paths_darwin.go (100%) rename {pkg/cli => internal/pkg}/xdg/paths_darwin_test.go (100%) rename {pkg/cli => internal/pkg}/xdg/paths_unix.go (100%) rename {pkg/cli => internal/pkg}/xdg/paths_unix_test.go (100%) rename {pkg/cli => internal/pkg}/xdg/paths_windows.go (100%) rename {pkg/cli => internal/pkg}/xdg/paths_windows_test.go (100%) rename {pkg/cli => internal/pkg}/xdg/user_dirs.go (100%) rename {pkg/cli => internal/pkg}/xdg/xdg.go (100%) rename {pkg/cli => internal/pkg}/xdg/xdg_test.go (100%) create mode 100644 pkg/app/tests/template-no-tls/template-no-tls_test.go delete mode 100644 pkg/cli/xdg/go.mod delete mode 100644 pkg/cli/xdg/go.sum create mode 100755 scripts/container-tag.sh create mode 100755 scripts/templates-test.sh diff --git a/.gitignore b/.gitignore index b8232b6b..757c60e1 100644 --- a/.gitignore +++ b/.gitignore @@ -59,3 +59,6 @@ go.work.sum # exclude DLV debugger generated binaries __debug_bin* + +# latest container test-snapshot +.container-tag.env diff --git a/.golangci.yaml b/.golangci.yaml index 1207cefa..8c357132 100644 --- a/.golangci.yaml +++ b/.golangci.yaml @@ -74,6 +74,7 @@ linters: - copyloopvar - dogsled - errcheck + - errname - exhaustive - funlen - gochecknoinits @@ -81,6 +82,7 @@ linters: - gocritic - gocyclo - godot + - gosimple - err113 - gofmt - goimports @@ -88,6 +90,7 @@ linters: - gosec - gosimple - govet + - importas - ineffassign - misspell - nakedret @@ -95,11 +98,13 @@ linters: - rowserrcheck - staticcheck - stylecheck + - testifylint - testpackage - typecheck - unconvert - unparam - unused + - wastedassign # don't enable: # - depguard diff --git a/.goreleaser-test.yml b/.goreleaser-test.yml index 42085224..05fc7331 100644 --- a/.goreleaser-test.yml +++ b/.goreleaser-test.yml @@ -36,6 +36,8 @@ builds: - -X github.com/{{ .Env.ORG }}/{{ .Env.REPO }}/pkg/version.commit={{.ShortCommit}} - -X github.com/{{ .Env.ORG }}/{{ .Env.REPO }}/pkg/version.date={{.Date}} mod_timestamp: "{{ .CommitTimestamp }}" + hooks: + post: make container-tag - id: topaz main: ./cmd/topaz @@ -57,7 +59,7 @@ builds: snapshot: # https://goreleaser.com/customization/snapshots/ - version_template: "test-{{ .ShortCommit }}" + version_template: "0.0.0-test-{{ .ShortCommit }}" dockers: # https://goreleaser.com/customization/docker/ diff --git a/.vscode/tasks.json b/.vscode/tasks.json deleted file mode 100644 index cb195a7e..00000000 --- a/.vscode/tasks.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - // See https://go.microsoft.com/fwlink/?LinkId=733558 - // for the documentation about the tasks.json format - "version": "2.0.0", - "tasks": [ - { - "label": "build", - "command": "mage", - "args": [ - "build" - ], - "type": "shell", - "group": { - "kind": "build", - "isDefault": true - } - } - ] -} \ No newline at end of file diff --git a/assets/assets.go b/assets/assets.go index 74aa77e3..eea9e6e2 100644 --- a/assets/assets.go +++ b/assets/assets.go @@ -13,6 +13,13 @@ func ConfigReader() io.Reader { return bytes.NewReader(config) } +//go:embed config/config-no-tls.yaml +var configNoTLS []byte + +func ConfigNoTLSReader() io.Reader { + return bytes.NewReader(configNoTLS) +} + //go:embed config/peoplefinder.yaml var configOnline []byte diff --git a/assets/config/config-no-tls.yaml b/assets/config/config-no-tls.yaml new file mode 100644 index 00000000..8818dd39 --- /dev/null +++ b/assets/config/config-no-tls.yaml @@ -0,0 +1,272 @@ +# yaml-language-server: $schema=https://topaz.sh/schema/config.json +--- +# config schema version +version: 2 + +# logger settings. +logging: + prod: true + log_level: info + grpc_log_level: info + +# edge directory configuration. +directory: + db_path: '${TOPAZ_DB_DIR}/test-no-tls.db' + request_timeout: 5s # set as default, 5 secs. + +# remote directory is used to resolve the identity for the authorizer. +remote_directory: + address: "0.0.0.0:9292" # set as default, it should be the same as the reader as we resolve the identity from the local directory service. + insecure: false + no_tls: true + tenant_id: "" + api_key: "" + token: "" + client_cert_path: "" + client_key_path: "" + ca_cert_path: "" + timeout_in_seconds: 5 + headers: + +# default jwt validation configuration +jwt: + acceptable_time_skew_seconds: 5 # set as default, 5 secs + +# authentication configuration +auth: + keys: + # - "" + # - "" + options: + default: + enable_api_key: false + enable_anonymous: true + overrides: + paths: + - /aserto.authorizer.v2.Authorizer/Info + - /grpc.reflection.v1.ServerReflection/ServerReflectionInfo + - /grpc.reflection.v1alpha.ServerReflection/ServerReflectionInfo + override: + enable_api_key: false + enable_anonymous: true + +api: + health: + listen_address: "0.0.0.0:9494" + + metrics: + listen_address: "0.0.0.0:9696" + + services: + console: + grpc: + listen_address: "0.0.0.0:9292" + fqdn: "" + gateway: + listen_address: "0.0.0.0:9393" + fqdn: "" + allowed_headers: + - "Authorization" + - "Content-Type" + - "If-Match" + - "If-None-Match" + - "Depth" + allowed_methods: + - "GET" + - "POST" + - "HEAD" + - "DELETE" + - "PUT" + - "PATCH" + - "PROFIND" + - "MKCOL" + - "COPY" + - "MOVE" + allowed_origins: + - http://localhost + - http://localhost:* + - https://*.aserto.com + - https://*aserto-console.netlify.app + read_timeout: 2s + read_header_timeout: 2s + write_timeout: 2s + idle_timeout: 30s + + model: + grpc: + listen_address: "0.0.0.0:9292" + fqdn: "" + gateway: + listen_address: "0.0.0.0:9393" + fqdn: "" + allowed_headers: + - "Authorization" + - "Content-Type" + - "If-Match" + - "If-None-Match" + - "Depth" + allowed_methods: + - "GET" + - "POST" + - "HEAD" + - "DELETE" + - "PUT" + - "PATCH" + - "PROFIND" + - "MKCOL" + - "COPY" + - "MOVE" + allowed_origins: + - http://localhost + - http://localhost:* + - https://*.aserto.com + - https://*aserto-console.netlify.app + read_timeout: 2s + read_header_timeout: 2s + write_timeout: 2s + idle_timeout: 30s + + reader: + needs: + - model + grpc: + listen_address: "0.0.0.0:9292" + fqdn: "" + certs: + gateway: + listen_address: "0.0.0.0:9393" + fqdn: "" + allowed_headers: + - "Authorization" + - "Content-Type" + - "If-Match" + - "If-None-Match" + - "Depth" + allowed_methods: + - "GET" + - "POST" + - "HEAD" + - "DELETE" + - "PUT" + - "PATCH" + - "PROFIND" + - "MKCOL" + - "COPY" + - "MOVE" + allowed_origins: + - http://localhost + - http://localhost:* + - https://*.aserto.com + - https://*aserto-console.netlify.app + read_timeout: 2s # default 2 seconds + read_header_timeout: 2s + write_timeout: 2s + idle_timeout: 30s # default 30 seconds + + writer: + needs: + - model + grpc: + listen_address: "0.0.0.0:9292" + fqdn: "" + gateway: + listen_address: "0.0.0.0:9393" + fqdn: "" + allowed_headers: + - "Authorization" + - "Content-Type" + - "If-Match" + - "If-None-Match" + - "Depth" + allowed_methods: + - "GET" + - "POST" + - "HEAD" + - "DELETE" + - "PUT" + - "PATCH" + - "PROFIND" + - "MKCOL" + - "COPY" + - "MOVE" + allowed_origins: + - http://localhost + - http://localhost:* + - https://*.aserto.com + - https://*aserto-console.netlify.app + read_timeout: 2s + read_header_timeout: 2s + write_timeout: 2s + idle_timeout: 30s + + exporter: + grpc: + listen_address: "0.0.0.0:9292" + fqdn: "" + + importer: + needs: + - model + grpc: + listen_address: "0.0.0.0:9292" + fqdn: "" + + authorizer: + needs: + - reader + grpc: + connection_timeout_seconds: 2 + listen_address: "0.0.0.0:9292" + fqdn: "" + gateway: + listen_address: "0.0.0.0:9393" + fqdn: "" + allowed_headers: + - "Authorization" + - "Content-Type" + - "If-Match" + - "If-None-Match" + - "Depth" + allowed_methods: + - "GET" + - "POST" + - "HEAD" + - "DELETE" + - "PUT" + - "PATCH" + - "PROFIND" + - "MKCOL" + - "COPY" + - "MOVE" + allowed_origins: + - http://localhost + - http://localhost:* + - https://*.aserto.com + - https://*aserto-console.netlify.app + read_timeout: 2s + read_header_timeout: 2s + write_timeout: 2s + idle_timeout: 30s + +opa: + instance_id: "-" + graceful_shutdown_period_seconds: 2 + # max_plugin_wait_time_seconds: 30 set as default + local_bundles: + paths: [] + skip_verification: true + config: + services: + ghcr: + url: https://ghcr.io + type: "oci" + response_header_timeout_seconds: 5 + bundles: + test: + service: ghcr + resource: "ghcr.io/aserto-policies/policy-rebac:latest" + persist: false + config: + polling: + min_delay_seconds: 60 + max_delay_seconds: 120 diff --git a/assets/db/acmecorp.db b/assets/db/acmecorp.db index 3938e175c3711048ffe0c4c207a29d7c2ec022e0..d203f67e33fb88721139d2b7f273c53871240890 100644 GIT binary patch delta 303354 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 qjPEbLxAh(l{|ygD9P-~P;bNQs delta 304050 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΃g8(Jt3pDLOWKA@(W`g+-zkp`>*SJlmEovuy(Vq< z@hrGgb94*6`Uy@%zGkVv-5L!_aZH2zewj;A+t2Xr)eXKK97E4{ooRqX0K-w!A&C47 z@_XPQ`LqPAm-&VEz=U%}T>1L=W6WVm>E}2g*_-RGrzaq~j=*-6I-ia>s_KnAu!4uxSp$-#bZZRt}b{jG0%KX}`3MOvSCK3qUyWDXUgEbtjtK_V&m+ZL~M&v5jIYSXgeaj*F=`Z`cGVdCF zqd7&yvMM2$qQzT$B?^Q^L3fiGF4(ZuAA)V^&Bn6Hw@=4_?*#W&pYcW<_5K!)RsD9~ zPDDbi*p<16)-xP^*QPyka^CT+YzefBGrkgW!G?EzB}~mOxoe;ij-IH*CYzgfDs6`I zFI>I7OgMUEmnwCb7>5&zWJ%_NruTg0B@d01buY|iX47K%mG@EW#!5K4b)RpqXd_ze zN{mKT1;f#UpZH384I=Z785BfqIJ##A?9~2GwZI0ddby%1D8y_y%9)NW@e4jzCLUEK zDW_W?ToCN{&qmNR659d)IIE8%W{T4Z^gb?JYRQY6i|C$%s*!^O;v8BGM<-51^1{o9 zd~5F?gzKztE?k>;=2BG9CZUv&?FZ*p_u(LJ=2Ag*c48y;qerUNC7}g5$`A5AZ}B?Ww_-aDHw=$;w7R%rV=+CS~T*bi)+RvdK&A$sAEI z8pD&;7o>zs)4ll#&9u0WjX}W{u0I((*FAv~6c#m)D20jcmT`la5v$RozY#|{tDx~q zixSF`m~lqnjB-f*?O6Q5;r5NyhMGGoX= zrc)vGKlDl14iwVPaTc#`&xNB$H^T*buWv$$F!2IKsO*{d1!Xtf?!5L0y~h#=y)TRO zwj^PPNx0U9YlMopV0>vpS*ziiS1yegj-FVL^6b6J5>}uDvQ4|r#EdvPw>+^=u?4Ur zj-ik%FoUC4u7lv0RMExYT*bR0j&2{8uxHFjp>$O##TN}nuMAJv zF&WPHbJ%x$kvYuKha)~wJTkF8Q`ztdEbi+4G~(#odHD8~qY`i0-^O7&4poJBU^u#E zbi&$$$ECfa(JZrr3l{`q5@)02X0y0!A#UZfZvO{M`S6Xfm+vyWcfz{C8`lFA5l4TY zhWPo8u?hRkj`Fno*u;=a(e-Iqx7Onm%7Ppgz8pR?`j+9S$Fc2US0*Tq0O|>0xYrk! zLM|QjnV3+vlwjKn{44dF$V6i<9c;Wjfnrj7o25QEVedOYpF%pM?(|0-)lW%St1gf% zjy-dEoFPXWrY63~Y-enD!Hy{X)`d&c%>y9t_R|vUGu0FYlQEsu?L)-T)kT<-#il3h z8C?ZV2Xwf}uxN%0Vlxs-#2c;YS}n(B1H%Q6uF5$B;nHS-&r}7D$(ci2VQmaYuMN+| zq;gimnp8sSaNP9fia6RZ+jbqx%W=rIL)C;!()o;f)u?gI8!Sf3egWA>iYYvs_qBg}=v^#*P4M$x@vZxoUqUH&V zT5B4jHXQcOC+x(EO8D@uo*^9FFbwBQ$holilWv|kh_Rp()tf;#3kkT-MK<*?kA82ljKF{FB! z&0Ogg)TvsLP*TV_Nae66CP;;Z{|9FyzL)oK!cIp?Ve7f$ry*{`(QCb7Px`G)*ryy| z#nNfOLoOW@1sy)d65yu|g^}>qYI8EIr@4|d%7hE{tV`GlDG)7k&>iExaI|JLPspCq zCS>Rdj&&g9p);4JZF6|_`RRle;zPSx;6`n7fF)imHo5GL;ezp-p#Pb@0|O>zN4klNIBMF2JjK2*CG3S~EO5Eb ze1!ZAhjR#FW$L#lVzg|0!A!bzQ)laV6DYbfuPEYZ(Uyd=4h=WMWpCI80T(>CmFKqA z&Gxb>p=^y!l_TfS6_+qviaN*87w{x@Vm@kDIrpOoNB3<;5bpVRG-F+XY;Q+L#J)4b z(N%{LsQ$$c+XsZ8oz(#&jy@QRKJd^^yBkB#k@FGiq2;a!cien*f!B7~J_X{)82=sP zgm6fq$Sw-LdM}ZW*lDQ5j}+`pSUxb69=?IILok=7OZFw~k`#z>!V;;26$V?w1+zZU zrfR%(z>(;Kh=k#SlRi_YYT>Clj^Wjus6&%J*Jj04<*tkiG=<@UyY?p#_N$G0{DFj> z+=MwW=2CiSA>!^hsCXuva_yL%q6G{`zfD2U-#Vm>7$_p_I=dR8HeAqj7^2QpCB%~9 z66qtAaOvPzM|4qFxg-YlHXL0&0SSz|k0xS>mP$WwEJ$h@qwx40Gd+mkaL5=`(`%igZ8AzbiQ+awBy%xpHST_EwyhKE#}3%1#jjkz6~+X-29}n zTNyjqU0=s=YB>6B_vDY6Ps%x@&LKcJnmYwrK7K(fKAt@?4uwfORtT9Ri_{@D;ezo+ z$u^cd0Mh|aOpnn<9Ife@d_VL4A#jXTdSpD}=*?b9J35W1w`*1hRjA6)de=bW}d&?nMR1nwOIpU~Yg{tM{&R77{l(`^L znH+6(XDr1-2Kq9La4C9Yd&i!^U((5AaWZ?Pr`1X27zE`1L>ZWw&kb=KZoz=0wH5?n zVn(G8Cc>p@O-<6ucf=|Kk`r}-MI23-hxkk0prp0W86rhess8N3rRbUgu;OKdRV$9B z)NdM!&Rm+-zYEJUdPwpyq?d@q!R!7CM4f4+Q{uXh;*Ta%plsSd%ntE&kLN^>; zHXZKbm=UUur{LDB*%%N#b1AxbWYWqJ#2DGnxtm`&x_6Xn%G2mr>Mug-%;5m-8c6+v z(Mfxbi|291%!CU*9Fy#g5N)%-Yawt;A5l9Zs`-&={E*zsTna}cLEzRmk>BbZghMV( zkIjHlxo}+4np!}pPz-&77IAdR_~c(QZwiquEH>yG5^?m~397(R7gjfd5IA#ibtWe5 zy#hR!bl3)HICH7s?aPy;2+TIida|nAFhP!49qP&$4re3jZVOT#T=9UE;pnzmkoB1B zR9V9V*1HB|Z8+@6Pkx)(#87UW9T!>#7k9_3>|XTzbk#Yma&1h9ybVXM4}wen;S5#5 zok45}-EcwCdmT{5@IbTBXC`k~ED8=la)ei5^bJRUo|XJ(rlP@UnEG$}Tqxw|wb_vQ zxpVCMhg_jhPW?6DMEh6}bYNS@lRzA7pNkW@1QQAtpLYy=AphtuJcc0MHLISYm8^qSFkEm^eX^yc8WIOd^bXZxE~ILFGNDUdgFX__(* zXLm(YP>? zEAcYoDA=6bm-(|0D&mz6n-p>=p}mY_1VywP47l zg6&(Ary_t_bxd$fQ}RlyrBOKu8?_u>wQ#gyyFGHiLn(3E72)Wu**N0kgm+ar^K4ap zXvo=cbkYu0&RCyt%9*(|o<1RfxNNiulKBoqx+x4kU4I*He_N_?6OE)%n}^EI3G)Nt3OCS zo%!1kEL7BCfrX=0e^STIw97Mt;u|hF<%4JOWo$(oh&&7Kh_$Pv=;=Ld6PX*tL_fvtZ1wV`TV&DOScj2A?UX9=%TV~an z4-AwGC$s{I4-69gGvF(M2Mc}_nE&7q!CM5MDfkm$AN*N@e*ium_-w)P*5M7FBlyhL zb>Rp2$@q00KEOYCp5Qlu`3KJz{3UP#_-BIKv<7;?t0S%e}s3I=XAmCTS1sCuNiV7js~d)zFzQ!z^p+x2%aeo_G`hrfw@6%6kG~< zvtr*Q_&i|lnl}rc2+TKdi{QJtE3yP`l?!X}fkD7^f~=75NV5%Y|$-DF46$!L)9`amUetBgr1LYEh?d^x!ab z%7rtJ>qghkD{ezaA8MaR8;2iX5S-t#_^D0}Gc&qp4gS<^@~1XSYyL*?33%t$;#T7q zw=TE#9fFtPom+dM;1}@DxB6Sb@8O-5|4zZJ(HLdiJ@1kWFI z1=0>|Z}>F}m^HUi@B_g7_tp#MUdU!55WE+d|Nb+Azvo`bn)$3;=qtT)gJ2qPw0!}+ z-H@9@9iA-i9(-NIt+&i{z5;v?2186K3!h(4NauTAa67#7Epn&gS0B9dE&Wk&HQw0{ zyeOE5uxj9qg1L{e3^xh>3*Pbf>asS=g=}ermjow(`59jpJQ$ce;}*d@HgcnF6}$+T zZ;%IFemxG%TJ(zG*Ma#4Ulsf%u<3lS$ps!vxp%%U_#DxKHw51dd^z|x1-}c-w)`!@ z3A6#@w*}7u=Fc|?{y_Ncf>YerI^YBE$OYP7b8-P~Jal|s@O)|Upqz%arhQJpM<7SE z2mkeV<--Md=T5alFb{EzcM9gG;y$oT@P53rIN1oA7apJa_XV?4$>QbC#;+P+NTV+6 z1Gz967pg)0N$@hk9|~>)=3e}f;O~I>2loi>25~X|Sa1@U+hecba|Q1cd?m1HpHJjM z)ayPKd;}lhC-_WoSICz&@^isSVE#dVa()dF{Dt6J;5e@TS?~?OtU#=p{8|d^4Z{C? zDHopL3&39q-UiG+@U`H5!v9t9_k#ZxygHybozt+*a|Z?g2+R+6NbqrZXG48h@Ib-e2)-KctS(0cb1%A*oAan#I0?E<2 z=inLHJa-b@UvQ4#iNO5qJRjf}I}nV!2xc2u4cxU(F0dMK^L7(_4&E8(3SNPCZk;^A zui~9`J74gAyz>uq7n~=;KUOf0c>MPY1m6qH!mR5d7Y^eBcfR8UcZRT7osJjm3ud#z zuff3FS|kqchrAz9M&8PdTd*{-}dHvhaWAS72=+b)p=l-F+bAz9aEq=07!`>=nFLTqP zdt~*JzxF*Eka2IpBO!0T-9CcZ+A{7dm|aSKeD)yu#hwUbo*(dwyB~Wfo?y0F{C!_A zl)TP-fk!=lvD~;jB?VXFy#}~g@FcwRA2>zuJmF6j%-xY6phR$2v=`q{sbIFg+jGF9YV@!8gXQ*MNEaNDDpy%s1Rma8I-s-w@jwe%&k$ z++XlL(H#FYx$rbTz~jm3f@whRu|25slCB-8dglop>71vEe;O1{E#BLy;Q(6iKVA&$ z?HTf=_tB1weuLb4RWg%q}MP3!aGci$_x)!+A917dHq0J)RlxYcAed zYJ;MV`Jh~2Z-rkQ@XqZxMDPy0^Bm+%!7U&rzJap@pDbcKTktSoz9H`5{Hg=y2RK*o z`-1DvlMAH~8#nCvg3kx$8{i-jzb*%6O}s$x0$?77hYDT=Tn)_jpI^Ix*>nvT+!9_3 z;}L>+LSM~Bdt|HN_F2U@Sj_-Ape6&4;$+tFIaO6Uc5nK&E4xT5`{JIc$ z5Z-?-cp)&$>KB69z2{r`rC@e-YJe|_8iOr4x8B9_flKkuI(UiTXYtORh_CZ&7v8xw zYXu*~JIm@a!Q6@1h>aE8ADCr3PVh*rp2HMqccJVEe6U><@d3SI}yH*mS&w*+(V z=GRxi{6tp>K2aLyO2OQ?Ea9sJbK^n6Wm#9th0WoGtX~P<1I$lwjo`NIg92ZRF>W!B zab5bu8ghT)t~Xgebpr&;pPC|=CweSL_6hjKJ^(+)b%J@+U^z|`%r=F;KV9%ecxPKN zL-1_8^J88w_-VF={9HH4g-(zIYtF9)dxCEiJQ&ymf0N)#fO+`4S@1l;w+LPh%$jqn zVD3eXBgJXm2G)=-%#;t5KyG}8vjmR?<{RWY=ht&0=Q)Dk17-=Zec;z2V7@^%)BNfX z_L{8uf=>fx2`&(PVVzvKO)g9aW(oX8@M2)L3%3h?QScpt{|d~8YN6o4ZA1K9!S@Tk zQ}7YsiTM0of`^{lE^OmPa$zMf-{E4xn}PX;?iRcUco6Us!QI-2cBNi0EkDrtSgNk~ zbLcliJLJ+y1^u&w2UZl1?bJ{Ri_r;R#Z(FZvRIeOmpqtn{*ea3gMqm}k>GK_+$_Hn zycC$*{T{*31GALw75pXeB;fl5*FbT(S$-dxpkLww|G@q7fnNi&ravHn^=^?3L^!{?FHc w$m^z$T@jR4_ix#`p}PMu$A-U!1~;!` ./internal/pkg/service/builder -replace github.com/aserto-dev/service-host => ../service-host - -replace github.com/adrg/xdg => ./pkg/cli/xdg +replace github.com/adrg/xdg => ./internal/pkg/xdg require ( github.com/Masterminds/semver/v3 v3.3.0 @@ -15,9 +13,9 @@ require ( github.com/aserto-dev/aserto-grpc v0.2.6 github.com/aserto-dev/aserto-management v0.9.7 github.com/aserto-dev/azm v0.1.19 - github.com/aserto-dev/certs v0.0.7 + github.com/aserto-dev/certs v0.1.0 github.com/aserto-dev/errors v0.0.11 - github.com/aserto-dev/go-aserto v0.33.0 + github.com/aserto-dev/go-aserto v0.33.1 github.com/aserto-dev/go-authorizer v0.20.11 github.com/aserto-dev/go-directory v0.31.14 github.com/aserto-dev/go-edge-ds v0.32.12 @@ -29,11 +27,11 @@ require ( github.com/aserto-dev/openapi-directory v0.31.2 github.com/aserto-dev/runtime v0.69.1 github.com/aserto-dev/self-decision-logger v0.0.8 - github.com/aserto-dev/service-host v0.0.17 + github.com/aserto-dev/service-host v0.0.0-00010101000000-000000000000 github.com/cli/browser v1.3.0 github.com/docker/docker v27.1.1+incompatible github.com/docker/go-connections v0.5.0 - github.com/fatih/color v1.17.0 + github.com/fatih/color v1.18.0 github.com/fullstorydev/grpcurl v1.9.1 github.com/gdamore/tcell/v2 v2.7.4 github.com/google/uuid v1.6.0 @@ -59,17 +57,15 @@ require ( github.com/spf13/viper v1.19.0 github.com/stretchr/testify v1.9.0 github.com/testcontainers/testcontainers-go v0.34.0 - go.opencensus.io v0.24.0 golang.org/x/sync v0.9.0 golang.org/x/sys v0.27.0 - google.golang.org/grpc v1.67.1 - google.golang.org/protobuf v1.35.1 + google.golang.org/grpc v1.68.0 + google.golang.org/protobuf v1.35.2 gopkg.in/natefinch/lumberjack.v2 v2.2.1 ) require ( buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.35.1-20240920164238-5a7b106cbb87.1 // indirect - contrib.go.opencensus.io/exporter/prometheus v0.4.2 // indirect dario.cat/mergo v1.0.0 // indirect github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 // indirect github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect @@ -84,7 +80,7 @@ require ( github.com/bytecodealliance/wasmtime-go/v3 v3.0.2 // indirect github.com/cenkalti/backoff/v4 v4.3.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect - github.com/cncf/xds/go v0.0.0-20240723142845-024c85f92f20 // indirect + github.com/cncf/xds/go v0.0.0-20240905190251-b4127c9b8d78 // indirect github.com/containerd/containerd v1.7.22 // indirect github.com/containerd/errdefs v0.2.0 // indirect github.com/containerd/log v0.1.0 // indirect @@ -104,15 +100,12 @@ require ( github.com/gdamore/encoding v1.0.1 // indirect github.com/go-http-utils/headers v0.0.0-20181008091004-fed159eddc2a // indirect github.com/go-ini/ini v1.67.0 // indirect - github.com/go-kit/log v0.2.1 // indirect - github.com/go-logfmt/logfmt v0.6.0 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-ole/go-ole v1.2.6 // indirect github.com/gobwas/glob v0.2.3 // indirect github.com/goccy/go-json v0.10.3 // indirect github.com/gogo/protobuf v1.3.2 // indirect - github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.4 // indirect github.com/google/cel-go v0.21.0 // indirect github.com/google/subcommands v1.2.0 // indirect @@ -164,7 +157,6 @@ require ( github.com/prometheus/client_model v0.6.1 // indirect github.com/prometheus/common v0.60.1 // indirect github.com/prometheus/procfs v0.15.1 // indirect - github.com/prometheus/statsd_exporter v0.27.1 // indirect github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 // indirect github.com/rivo/uniseg v0.4.7 // indirect github.com/rs/cors v1.11.1 // indirect diff --git a/go.sum b/go.sum index c21db6fa..14be9479 100644 --- a/go.sum +++ b/go.sum @@ -378,8 +378,6 @@ cloud.google.com/go/workflows v1.6.0/go.mod h1:6t9F5h/unJz41YqfBmqSASJSXccBLtD1V cloud.google.com/go/workflows v1.7.0/go.mod h1:JhSrZuVZWuiDfKEFxU0/F1PQjmpnpcoISEXH2bcHC3M= cloud.google.com/go/workflows v1.8.0/go.mod h1:ysGhmEajwZxGn1OhGOGKsTXc5PyxOc0vfKf5Af+to4M= cloud.google.com/go/workflows v1.9.0/go.mod h1:ZGkj1aFIOd9c8Gerkjjq7OW7I5+l6cSvT3ujaO/WwSA= -contrib.go.opencensus.io/exporter/prometheus v0.4.2 h1:sqfsYl5GIY/L570iT+l93ehxaWJs2/OwXtiWwew3oAg= -contrib.go.opencensus.io/exporter/prometheus v0.4.2/go.mod h1:dvEHbiKmgvbr5pjaF9fpw1KeYcjrnC1J8B+JKjsZyRQ= dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= @@ -406,12 +404,6 @@ github.com/alecthomas/kong v1.4.0 h1:UL7tzGMnnY0YRMMvJyITIRX1EpO6RbBRZDNcCevy3HA github.com/alecthomas/kong v1.4.0/go.mod h1:p2vqieVMeTAnaC83txKtXe8FLke2X07aruPWXyMPQrU= github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc= github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= -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= -github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= -github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ= github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw= @@ -423,12 +415,12 @@ github.com/aserto-dev/aserto-management v0.9.7 h1:nT7hu1BUEz9LMZ9vthEf9ML2blO+Ak github.com/aserto-dev/aserto-management v0.9.7/go.mod h1:ErZ4BGsYkAt1IhQVEgs6Cuhzn+UGA0tuNVUmKeA+twk= github.com/aserto-dev/azm v0.1.19 h1:pFHNzUhNeg5vKjpXsFnQGfKBbIsBra5Jb+hJt5ohpaQ= github.com/aserto-dev/azm v0.1.19/go.mod h1:uxUKXGA6d41ZbJBfEu+/PwXGLuI3tErKkH7VPZHDcX4= -github.com/aserto-dev/certs v0.0.7 h1:WsFrHywNE88tgUeDJ2FsV+mgy46QFQvCIYm7c5TNIKE= -github.com/aserto-dev/certs v0.0.7/go.mod h1:aguR/vIf6cag3O7vaYpJ4Jt1+1DJehBC2Uzto9gxXv8= +github.com/aserto-dev/certs v0.1.0 h1:eklyoGdondx0uowVpY3+Oifz+Bhe615Ls5I6oWJrq34= +github.com/aserto-dev/certs v0.1.0/go.mod h1:xWtPdSkBGgXnBXUUDUg5OD4SdFKiEOtVwkDlDIWMtTM= github.com/aserto-dev/errors v0.0.11 h1:CXo+Uwmh09doG2HvL1SC8Fnne8f9VPrGyEQPtogAfyY= github.com/aserto-dev/errors v0.0.11/go.mod h1:T1YQOtcxpgBriPTn5HXJkD/QukYz5YojYOIzGMo0ybM= -github.com/aserto-dev/go-aserto v0.33.0 h1:seloEPmdLY53pxykbenFZV7K+BumP1KVj5ZGrc1GjeU= -github.com/aserto-dev/go-aserto v0.33.0/go.mod h1:t2qs6Q7pXlMkKh5MhbssKDbGb62f36Q6Bj3QDXww31s= +github.com/aserto-dev/go-aserto v0.33.1 h1:oFTlatM7+mq2grD/ut+ff0v7CjCV6YenKd6XWJbonbE= +github.com/aserto-dev/go-aserto v0.33.1/go.mod h1:t2qs6Q7pXlMkKh5MhbssKDbGb62f36Q6Bj3QDXww31s= github.com/aserto-dev/go-authorizer v0.20.11 h1:OaYJwyljt2yBuDtIiMl1mqjyMU0dUuv1eZsBNHo4+O4= github.com/aserto-dev/go-authorizer v0.20.11/go.mod h1:iwVdTU2xOrNW0TZ+UWX+Mn2hgR2Lj1XmKgei0tt5pbY= github.com/aserto-dev/go-decision-logs v0.1.2 h1:f26bgKDIroNeN71+Ot2AXfCAtausNcBykF94RWP5I0I= @@ -454,8 +446,6 @@ github.com/aserto-dev/runtime v0.69.1/go.mod h1:WmUDXXtutid6NWUe4PtBs3RzhJbPjD5v github.com/aserto-dev/self-decision-logger v0.0.8 h1:N1HjJOwZmx+MGiRCzTXhsgh86lw9iWC2UHlOaQsW5gw= github.com/aserto-dev/self-decision-logger v0.0.8/go.mod h1:e+NSdmKFBnN0SYWSCYUi1uzCM4LDyu27+ILQvk6oLAo= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= -github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= -github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bufbuild/protocompile v0.13.0 h1:6cwUB0Y2tSvmNxsbunwzmIto3xOlJOV7ALALuVOs92M= @@ -471,7 +461,6 @@ github.com/census-instrumentation/opencensus-proto v0.3.0/go.mod h1:f6KPmirojxKA github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= @@ -489,8 +478,8 @@ github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWH github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20240723142845-024c85f92f20 h1:N+3sFI5GUjRKBi+i0TxYVST9h4Ie192jJWpHvthBBgg= -github.com/cncf/xds/go v0.0.0-20240723142845-024c85f92f20/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8= +github.com/cncf/xds/go v0.0.0-20240905190251-b4127c9b8d78 h1:QVw89YDxXxEe+l8gU8ETbOasdwEV+avkR75ZzsVV9WI= +github.com/cncf/xds/go v0.0.0-20240905190251-b4127c9b8d78/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8= github.com/containerd/cgroups v1.1.0 h1:v8rEWFl6EoqHB+swVNjVoCJE8o3jX7e8nqBGPLaDFBM= github.com/containerd/cgroups/v3 v3.0.2 h1:f5WFqIVSgo5IZmtTT3qVBo6TzI1ON6sycSBKkymb9L0= github.com/containerd/cgroups/v3 v3.0.2/go.mod h1:JUgITrzdFqp42uI2ryGA+ge0ap/nxzYgkGmIcetmErE= @@ -551,8 +540,8 @@ github.com/envoyproxy/go-control-plane v0.13.0/go.mod h1:GRaKG3dwvFoTg4nj7aXdZnv github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/envoyproxy/protoc-gen-validate v1.1.0 h1:tntQDh69XqOCOZsDz0lVJQez/2L6Uu2PdjCQwWCJ3bM= github.com/envoyproxy/protoc-gen-validate v1.1.0/go.mod h1:sXRDRVmzEbkM7CVcM06s9shE/m23dg3wzjl0UWqJ2q4= -github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4= -github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI= +github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= +github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= @@ -578,18 +567,8 @@ github.com/go-http-utils/headers v0.0.0-20181008091004-fed159eddc2a h1:v6zMvHuY9 github.com/go-http-utils/headers v0.0.0-20181008091004-fed159eddc2a/go.mod h1:I79BieaU4fxrw4LMXby6q5OS9XnoR9UIKLOzDFjUmuw= github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A= github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= -github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= -github.com/go-kit/log v0.2.0/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0= -github.com/go-kit/log v0.2.1 h1:MRVx0/zhvdseW+Gza6N9rVzU/IVzaeE1SFI4raAhmBU= -github.com/go-kit/log v0.2.1/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0= -github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= -github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= -github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= -github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4= -github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= @@ -603,7 +582,6 @@ github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJA github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA= github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= -github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= @@ -668,7 +646,6 @@ github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeN github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 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/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= @@ -744,22 +721,13 @@ github.com/itchyny/timefmt-go v0.1.6 h1:ia3s54iciXDdzWzwaVKXZPbiXzxxnv1SPGFfM/my github.com/itchyny/timefmt-go v0.1.6/go.mod h1:RRDZYC5s9ErkjQvTvvU7keJjxUYzIISJGxm9/mAERQg= github.com/jhump/protoreflect v1.16.0 h1:54fZg+49widqXYQ0b+usAFHbMkBGR4PpXrsHc8+TBDg= github.com/jhump/protoreflect v1.16.0/go.mod h1:oYPd7nPvcBw/5wlDfm/AVmU9zH9BgqGCI469pGxfj/8= -github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= -github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= -github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= -github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= -github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc= github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= @@ -798,7 +766,6 @@ github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= -github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/mennanov/fmutils v0.3.0 h1:2YSyrO8oOLQQwB/iKe+xDDGO6xCUHiIAj3gYhY7D4Ao= github.com/mennanov/fmutils v0.3.0/go.mod h1:ph1jsu8gV1gUgMURCmfIVbXKG3O2/O5o/UbPbbqu8zs= github.com/miekg/dns v1.1.57 h1:Jzi7ApEIzwEPLHWRcafCN9LZSBbqQpxjt/wpgvg7wcM= @@ -831,17 +798,10 @@ github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28= github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= -github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= -github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/nats-io/jwt/v2 v2.5.8 h1:uvdSzwWiEGWGXf+0Q+70qv6AQdvcvxrv9hPM0RiPamE= github.com/nats-io/jwt/v2 v2.5.8/go.mod h1:ZdWS1nZa6WMZfFwwgpEaqBV8EPGVgOTDHN/wTbz0Y5A= github.com/nats-io/nats-server/v2 v2.10.22 h1:Yt63BGu2c3DdMoBZNcR6pjGQwk/asrKU7VX846ibxDA= @@ -867,7 +827,6 @@ github.com/panmari/cuckoofilter v1.0.6 h1:WKb1aSj16h22x0CKVtTCaRkJiCnVGPLEMGbNY8 github.com/panmari/cuckoofilter v1.0.6/go.mod h1:bKADbQPGbN6TxUvo/IbMEIUbKuASnpsOvrLTgpSX0aU= github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc= -github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -878,40 +837,15 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRI github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw= github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= -github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= -github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= -github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= -github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= -github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= -github.com/prometheus/client_golang v1.12.2/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= -github.com/prometheus/client_golang v1.13.0/go.mod h1:vTeo+zgvILHsnnj/39Ou/1fPN5nJFOEMgftOUOmlvYQ= github.com/prometheus/client_golang v1.20.5 h1:cxppBPuYhUnsO6yo/aoRol4L7q7UFfdm+bR9r+8l63Y= github.com/prometheus/client_golang v1.20.5/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= -github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= -github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= -github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= -github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= -github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= -github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= -github.com/prometheus/common v0.35.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA= -github.com/prometheus/common v0.37.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA= github.com/prometheus/common v0.60.1 h1:FUas6GcOw66yB/73KC+BOZoFJmbo/1pojoILArPAaSc= github.com/prometheus/common v0.60.1/go.mod h1:h0LYf1R1deLSKtD4Vdg8gy4RuOvENW2J/h19V5NADQw= -github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= -github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= -github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= -github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= -github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0uaxHdg830/4= github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= -github.com/prometheus/statsd_exporter v0.22.7/go.mod h1:N/TevpjkIh9ccs6nuzY3jQn9dFqnUakOjnEuMPJJJnI= -github.com/prometheus/statsd_exporter v0.27.1 h1:tcRJOmwlA83HPfWzosAgr2+zEN5XDFv+M2mn/uYkn5Y= -github.com/prometheus/statsd_exporter v0.27.1/go.mod h1:vA6ryDfsN7py/3JApEst6nLTJboq66XsNcJGNmC88NQ= github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 h1:N/ElC8H3+5XpJzTSTfLsJV/mx9Q9g7kxmchpfZyxgzM= github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/rivo/tview v0.0.0-20241103174730-c76f7879f592 h1:YIJ+B1hePP6AgynC5TcqpO0H9k3SSoZa2BGyL6vDUzM= @@ -944,9 +878,7 @@ github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFt github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ= github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU= github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k= -github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= -github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/slok/go-http-metrics v0.13.0 h1:lQDyJJx9wKhmbliyUsZ2l6peGnXRHjsjoqPt5VYzcP8= @@ -984,7 +916,6 @@ github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o 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/stvp/go-udp-testing v0.0.0-20201019212854-469649b16807/go.mod h1:7jxmlfBCDBXRzr0eAQJ48XC1hBu1np4CS5+cHEYfwpc= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/tchap/go-patricia/v2 v2.3.1 h1:6rQp39lgIYZ+MHmdEq4xzuk1t7OdC35z/xm0BGhTkes= @@ -1045,7 +976,6 @@ go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9i go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/zap v1.18.1/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI= -golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= @@ -1101,7 +1031,6 @@ golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0= golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= @@ -1109,7 +1038,6 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= -golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -1136,7 +1064,6 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220325170049-de3da57026de/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= @@ -1200,8 +1127,6 @@ golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ= golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1215,7 +1140,6 @@ golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1228,8 +1152,6 @@ golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1238,7 +1160,6 @@ golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1247,7 +1168,6 @@ golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -1259,7 +1179,6 @@ golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -1271,7 +1190,6 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220610221304-9f5ed59c137d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220624220833-87e55d714810/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220708085239-5a0f0661e09d/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-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -1597,8 +1515,8 @@ google.golang.org/grpc v1.49.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCD google.golang.org/grpc v1.50.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= google.golang.org/grpc v1.50.1/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= google.golang.org/grpc v1.51.0/go.mod h1:wgNDFcnuBGmxLKI/qn4T+m5BtEBYXJPvibbUPsAIPww= -google.golang.org/grpc v1.67.1 h1:zWnc1Vrcno+lHZCOofnIMvycFcc0QRGIzm9dhnDX68E= -google.golang.org/grpc v1.67.1/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA= +google.golang.org/grpc v1.68.0 h1:aHQeeJbo8zAkAa3pRzrVjZlbz6uSfeOXlJNQM0RAbz0= +google.golang.org/grpc v1.68.0/go.mod h1:fmSPC5AsjSBCK54MyHRx48kpOti1/jRfOlwEWywNjWA= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= @@ -1615,12 +1533,10 @@ google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQ google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= -google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= -gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +google.golang.org/protobuf v1.35.2 h1:8Ar7bF+apOIoThw1EdZl0p1oWvMqTHmpA2fRTyZO8io= +google.golang.org/protobuf v1.35.2/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= @@ -1628,13 +1544,9 @@ gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc= gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc= -gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/go.work b/go.work new file mode 100644 index 00000000..40b3e113 --- /dev/null +++ b/go.work @@ -0,0 +1,7 @@ +go 1.23.3 + +use ( + . + ./internal/pkg/service/builder + ./internal/pkg/xdg +) diff --git a/internal/pkg/service/builder/defaults.go b/internal/pkg/service/builder/defaults.go new file mode 100644 index 00000000..d42d1a6a --- /dev/null +++ b/internal/pkg/service/builder/defaults.go @@ -0,0 +1,39 @@ +package builder + +import ( + "net/http" + + "github.com/go-http-utils/headers" +) + +var DefaultGatewayAllowedHeaders = []string{ + headers.Authorization, + headers.ContentType, + headers.IfMatch, + headers.IfNoneMatch, + "Depth", +} + +var DefaultGatewayAllowedMethods = []string{ + http.MethodGet, + http.MethodPost, + http.MethodHead, + http.MethodDelete, + http.MethodPut, + http.MethodPatch, + "PROPFIND", + "MKCOL", + "COPY", + "MOVE", +} + +var DefaultGatewayAllowedOrigins = []string{ + "http://localhost", + "http://localhost:*", + "https://localhost", + "https://localhost:*", + "http://127.0.0.1", + "http://127.0.0.1:*", + "https://127.0.0.1", + "https://127.0.0.1:*", +} diff --git a/internal/pkg/service/builder/go.mod b/internal/pkg/service/builder/go.mod new file mode 100644 index 00000000..68e5e75c --- /dev/null +++ b/internal/pkg/service/builder/go.mod @@ -0,0 +1,46 @@ +module github.com/aserto-dev/service-host + +go 1.23 + +toolchain go1.23.2 + +require ( + github.com/aserto-dev/go-aserto v0.33.1 + github.com/go-http-utils/headers v0.0.0-20181008091004-fed159eddc2a + github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.0.1 + github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 + github.com/grpc-ecosystem/grpc-gateway/v2 v2.23.0 + github.com/pkg/errors v0.9.1 + github.com/prometheus/client_golang v1.20.5 + github.com/rs/cors v1.11.1 + github.com/rs/zerolog v1.33.0 + github.com/slok/go-http-metrics v0.13.0 + golang.org/x/sync v0.9.0 + google.golang.org/grpc v1.67.1 + google.golang.org/protobuf v1.35.1 +) + +require ( + github.com/aserto-dev/header v0.0.8 // indirect + github.com/beorn7/perks v1.0.1 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.1.0 // indirect + github.com/hashicorp/errwrap v1.0.0 // indirect + github.com/hashicorp/go-multierror v1.1.1 // indirect + github.com/klauspost/compress v1.17.11 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/prometheus/client_model v0.6.1 // indirect + github.com/prometheus/common v0.60.1 // indirect + github.com/prometheus/procfs v0.15.1 // indirect + github.com/samber/lo v1.47.0 // indirect + golang.org/x/net v0.31.0 // indirect + golang.org/x/sys v0.27.0 // indirect + golang.org/x/text v0.20.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20241104194629-dd2ea8efbc28 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20241104194629-dd2ea8efbc28 // indirect +) diff --git a/internal/pkg/service/builder/go.sum b/internal/pkg/service/builder/go.sum new file mode 100644 index 00000000..6a12b235 --- /dev/null +++ b/internal/pkg/service/builder/go.sum @@ -0,0 +1,90 @@ +github.com/aserto-dev/go-aserto v0.33.1 h1:oFTlatM7+mq2grD/ut+ff0v7CjCV6YenKd6XWJbonbE= +github.com/aserto-dev/go-aserto v0.33.1/go.mod h1:t2qs6Q7pXlMkKh5MhbssKDbGb62f36Q6Bj3QDXww31s= +github.com/aserto-dev/header v0.0.8 h1:T052WblWFZ/5Mg3MphHylE3sZobdIQpdj5cP3sPMhL8= +github.com/aserto-dev/header v0.0.8/go.mod h1:wmWm+omABTWf6QRRmw9yOdvgTstk/vYDqIA1duR8Pus= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/go-http-utils/headers v0.0.0-20181008091004-fed159eddc2a h1:v6zMvHuY9yue4+QkG/HQ/W67wvtQmWJ4SDo9aK/GIno= +github.com/go-http-utils/headers v0.0.0-20181008091004-fed159eddc2a/go.mod h1:I79BieaU4fxrw4LMXby6q5OS9XnoR9UIKLOzDFjUmuw= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +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/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.0.1 h1:qnpSQwGEnkcRpTqNOIR6bJbR0gAorgP9CSALpRcKoAA= +github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.0.1/go.mod h1:lXGCsh6c22WGtjr+qGHj1otzZpV/1kwTMAqkwZsnWRU= +github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.1.0 h1:pRhl55Yx1eC7BZ1N+BBWwnKaMyD8uC+34TLdndZMAKk= +github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.1.0/go.mod h1:XKMd7iuf/RGPSMJ/U4HP0zS2Z9Fh8Ps9a+6X26m/tmI= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.23.0 h1:ad0vkEBuk23VJzZR9nkLVG0YAoN9coASF1GusYX6AlU= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.23.0/go.mod h1:igFoXX2ELCW06bol23DWPB5BEWfZISOzSP5K2sbLea0= +github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= +github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= +github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc= +github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +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/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_golang v1.20.5 h1:cxppBPuYhUnsO6yo/aoRol4L7q7UFfdm+bR9r+8l63Y= +github.com/prometheus/client_golang v1.20.5/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= +github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= +github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= +github.com/prometheus/common v0.60.1 h1:FUas6GcOw66yB/73KC+BOZoFJmbo/1pojoILArPAaSc= +github.com/prometheus/common v0.60.1/go.mod h1:h0LYf1R1deLSKtD4Vdg8gy4RuOvENW2J/h19V5NADQw= +github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= +github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= +github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA= +github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= +github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= +github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8= +github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= +github.com/samber/lo v1.47.0 h1:z7RynLwP5nbyRscyvcD043DWYoOcYRv3mV8lBeqOCLc= +github.com/samber/lo v1.47.0/go.mod h1:RmDH9Ct32Qy3gduHQuKJ3gW1fMHAnE/fAzQuf6He5cU= +github.com/slok/go-http-metrics v0.13.0 h1:lQDyJJx9wKhmbliyUsZ2l6peGnXRHjsjoqPt5VYzcP8= +github.com/slok/go-http-metrics v0.13.0/go.mod h1:HIr7t/HbN2sJaunvnt9wKP9xoBBVZFo1/KiHU3b0w+4= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +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= +golang.org/x/net v0.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo= +golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM= +golang.org/x/sync v0.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ= +golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/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= +golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s= +golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug= +golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4= +google.golang.org/genproto/googleapis/api v0.0.0-20241104194629-dd2ea8efbc28 h1:M0KvPgPmDZHPlbRbaNU1APr28TvwvvdUPlSv7PUvy8g= +google.golang.org/genproto/googleapis/api v0.0.0-20241104194629-dd2ea8efbc28/go.mod h1:dguCy7UOdZhTvLzDyt15+rOrawrpM4q7DD9dQ1P11P4= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241104194629-dd2ea8efbc28 h1:XVhgTWWV3kGQlwJHR3upFWZeTsei6Oks1apkZSeonIE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241104194629-dd2ea8efbc28/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI= +google.golang.org/grpc v1.67.1 h1:zWnc1Vrcno+lHZCOofnIMvycFcc0QRGIzm9dhnDX68E= +google.golang.org/grpc v1.67.1/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA= +google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= +google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/internal/pkg/service/builder/health.go b/internal/pkg/service/builder/health.go new file mode 100644 index 00000000..79007118 --- /dev/null +++ b/internal/pkg/service/builder/health.go @@ -0,0 +1,37 @@ +package builder + +import ( + "github.com/aserto-dev/go-aserto" + + "google.golang.org/grpc" + "google.golang.org/grpc/health" + healthpb "google.golang.org/grpc/health/grpc_health_v1" + "google.golang.org/grpc/reflection" +) + +type Health struct { + Server *health.Server + GRPCServer *grpc.Server + Address string +} + +// newGRPCHealthServer creates a new HealthServer. +func newGRPCHealthServer(certCfg *aserto.TLSConfig) *Health { + healthServer := health.NewServer() + + grpcHealthServer, err := prepareGrpcServer(certCfg, nil) + if err != nil { + panic(err) + } + + healthpb.RegisterHealthServer(grpcHealthServer, healthServer) + reflection.Register(grpcHealthServer) + return &Health{ + Server: healthServer, + GRPCServer: grpcHealthServer, + } +} + +func (h *Health) SetServiceStatus(service string, status healthpb.HealthCheckResponse_ServingStatus) { + h.Server.SetServingStatus(service, status) +} diff --git a/internal/pkg/service/builder/service.go b/internal/pkg/service/builder/service.go new file mode 100644 index 00000000..3beabe80 --- /dev/null +++ b/internal/pkg/service/builder/service.go @@ -0,0 +1,66 @@ +package builder + +import ( + "context" + "net" + "net/http" + "time" + + "github.com/aserto-dev/go-aserto" + + "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" + "google.golang.org/grpc" +) + +type ServiceInterface interface { + Start(context.Context) error + Stop(context.Context) error +} + +type GRPCRegistrations func(server *grpc.Server) + +type HandlerRegistrations func(ctx context.Context, mux *runtime.ServeMux, grpcEndpoint string, opts []grpc.DialOption) error + +type Server struct { + Config *API + Server *grpc.Server + Listener net.Listener + Gateway Gateway + Started chan bool + Cleanup []func() +} + +type Gateway struct { + Server *http.Server + Mux *http.ServeMux + Certs *aserto.TLSConfig +} + +type API struct { + Needs []string `json:"needs"` + GRPC struct { + FQDN string `json:"fqdn"` + ListenAddress string `json:"listen_address"` + // Default connection timeout is 120 seconds + // https://godoc.org/google.golang.org/grpc#ConnectionTimeout + ConnectionTimeoutSeconds uint32 `json:"connection_timeout_seconds"` + Certs aserto.TLSConfig `json:"certs"` + } `json:"grpc"` + Gateway struct { + FQDN string `json:"fqdn"` + ListenAddress string `json:"listen_address"` + AllowedOrigins []string `json:"allowed_origins"` + AllowedHeaders []string `json:"allowed_headers"` + AllowedMethods []string `json:"allowed_methods"` + Certs aserto.TLSConfig `json:"certs"` + HTTP bool `json:"http"` + ReadTimeout time.Duration `json:"read_timeout"` + ReadHeaderTimeout time.Duration `json:"read_header_timeout"` + WriteTimeout time.Duration `json:"write_timeout"` + IdleTimeout time.Duration `json:"idle_timeout"` + } `json:"gateway"` +} + +func (g *Gateway) AddHandler(pattern string, handler http.HandlerFunc) { + g.Mux.Handle(pattern, handler) +} diff --git a/internal/pkg/service/builder/service_factory.go b/internal/pkg/service/builder/service_factory.go new file mode 100644 index 00000000..594389e3 --- /dev/null +++ b/internal/pkg/service/builder/service_factory.go @@ -0,0 +1,302 @@ +package builder + +import ( + "context" + "fmt" + "net" + "net/http" + "strconv" + + "github.com/aserto-dev/go-aserto" + + "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" + "github.com/pkg/errors" + "github.com/prometheus/client_golang/prometheus" + "github.com/rs/cors" + metrics "github.com/slok/go-http-metrics/metrics/prometheus" + "github.com/slok/go-http-metrics/middleware" + "github.com/slok/go-http-metrics/middleware/std" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials/insecure" + "google.golang.org/grpc/metadata" + "google.golang.org/grpc/reflection" + "google.golang.org/protobuf/encoding/protojson" + "google.golang.org/protobuf/proto" +) + +type ServiceFactory struct{} + +var mdlw middleware.Middleware + +func NewServiceFactory() *ServiceFactory { + if reg == nil { + reg = prometheus.NewRegistry() + } + mdlw = middleware.New(middleware.Config{ + Recorder: metrics.NewRecorder(metrics.Config{ + Registry: reg, + }), + }) + + return &ServiceFactory{} +} + +type GRPCOptions struct { + ServerOptions []grpc.ServerOption + Registrations GRPCRegistrations +} + +type GatewayOptions struct { + HandlerRegistrations HandlerRegistrations + ErrorHandler runtime.ErrorHandlerFunc +} + +func (f *ServiceFactory) CreateService( + config *API, + grpcOpts *GRPCOptions, + gatewayOpts *GatewayOptions, + cleanup ...func(), +) (*Server, error) { + grpcServer, err := prepareGrpcServer(&config.GRPC.Certs, grpcOpts.ServerOptions) + if err != nil { + return nil, err + } + grpcOpts.Registrations(grpcServer) + reflection.Register(grpcServer) + + listener, err := net.Listen("tcp", config.GRPC.ListenAddress) + if err != nil { + return nil, err + } + + gateway := Gateway{} + if gatewayOpts != nil && config.Gateway.ListenAddress != "" { + gateway, err = f.prepareGateway(config, gatewayOpts) + if err != nil { + return nil, err + } + } + + return &Server{ + Config: config, + Server: grpcServer, + Listener: listener, + Gateway: gateway, + Started: make(chan bool), + Cleanup: cleanup, + }, nil +} + +// prepareGateway provides a http server that will have the registrations pointed to the corresponding configured grpc server. +func (f *ServiceFactory) prepareGateway(config *API, gatewayOpts *GatewayOptions) (Gateway, error) { + if len(config.Gateway.AllowedHeaders) == 0 { + config.Gateway.AllowedHeaders = DefaultGatewayAllowedHeaders + } + if len(config.Gateway.AllowedOrigins) == 0 { + config.Gateway.AllowedOrigins = DefaultGatewayAllowedOrigins + } + if len(config.Gateway.AllowedMethods) == 0 { + config.Gateway.AllowedMethods = DefaultGatewayAllowedMethods + } + + c := cors.New(cors.Options{ + AllowedHeaders: config.Gateway.AllowedHeaders, + AllowedOrigins: config.Gateway.AllowedOrigins, + AllowedMethods: config.Gateway.AllowedMethods, + Debug: false, + }) + + runtimeMux := f.gatewayMux(config.Gateway.AllowedHeaders, gatewayOpts.ErrorHandler) + + opts := []grpc.DialOption{} + if TLS(&config.GRPC.Certs) { + tlsCreds, err := config.GRPC.Certs.ClientCredentials(true) + if err != nil { + return Gateway{}, errors.Wrapf(err, "failed to get TLS credentials") + } + + opts = append(opts, grpc.WithTransportCredentials(tlsCreds)) + } else { + opts = append(opts, grpc.WithTransportCredentials(insecure.NewCredentials())) + } + + grpcEndpoint := fmt.Sprintf("dns:///%s", config.GRPC.ListenAddress) + + if err := gatewayOpts.HandlerRegistrations(context.Background(), runtimeMux, grpcEndpoint, opts); err != nil { + return Gateway{}, err + } + + mux := http.NewServeMux() + mux.Handle("/", runtimeMux) + mux.Handle("/api/", fieldsMaskHandler(runtimeMux)) + + gtwHandler := std.Handler("", mdlw, mux) + + gtwServer := &http.Server{ + Addr: config.Gateway.ListenAddress, + Handler: c.Handler(gtwHandler), + ReadTimeout: config.Gateway.ReadTimeout, + ReadHeaderTimeout: config.Gateway.ReadHeaderTimeout, + WriteTimeout: config.Gateway.WriteTimeout, + IdleTimeout: config.Gateway.IdleTimeout, + } + + if NoTLS(&config.Gateway.Certs) { + config.Gateway.HTTP = true + } + + if !config.Gateway.HTTP { + tlsServerConfig, err := config.Gateway.Certs.ServerConfig() + if err != nil { + return Gateway{Server: gtwServer, Mux: mux, Certs: &config.Gateway.Certs}, err + } + gtwServer.TLSConfig = tlsServerConfig + return Gateway{Server: gtwServer, Mux: mux, Certs: &config.Gateway.Certs}, nil + } + + return Gateway{Server: gtwServer, Mux: mux, Certs: nil}, nil +} + +// gatewayMux creates a gateway multiplexer for serving the API as an OpenAPI endpoint. +func (f *ServiceFactory) gatewayMux(allowedHeaders []string, errorHandler runtime.ErrorHandlerFunc) *runtime.ServeMux { + opts := []runtime.ServeMuxOption{ + runtime.WithIncomingHeaderMatcher(func(key string) (string, bool) { + if contains(allowedHeaders, key) { + return key, true + } + return runtime.DefaultHeaderMatcher(key) + }), + runtime.WithMetadata(captureGatewayRoute), + runtime.WithMarshalerOption( + runtime.MIMEWildcard, + &runtime.JSONPb{ + MarshalOptions: protojson.MarshalOptions{ + Multiline: false, + Indent: " ", + AllowPartial: true, + UseProtoNames: true, + UseEnumNumbers: false, + EmitUnpopulated: true, + }, + UnmarshalOptions: protojson.UnmarshalOptions{ + AllowPartial: true, + DiscardUnknown: true, + }, + }, + ), + runtime.WithMarshalerOption( + "application/json+masked", + &runtime.JSONPb{ + MarshalOptions: protojson.MarshalOptions{ + Multiline: false, + Indent: " ", + AllowPartial: true, + UseProtoNames: true, + UseEnumNumbers: false, + EmitUnpopulated: false, + }, + UnmarshalOptions: protojson.UnmarshalOptions{ + AllowPartial: true, + DiscardUnknown: true, + }, + }, + ), + runtime.WithUnescapingMode(runtime.UnescapingModeAllExceptSlash), + runtime.WithForwardResponseOption(httpResponseModifier), + } + + if errorHandler != nil { + opts = append(opts, runtime.WithErrorHandler(errorHandler)) + } + + return runtime.NewServeMux(opts...) +} + +func httpResponseModifier(ctx context.Context, w http.ResponseWriter, p proto.Message) error { + md, ok := runtime.ServerMetadataFromContext(ctx) + if !ok { + return nil + } + + // set http status code + if vals := md.HeaderMD.Get("x-http-code"); len(vals) > 0 { + code, err := strconv.Atoi(vals[0]) + if err != nil { + return err + } + // delete the headers to not expose any grpc-metadata in http response + delete(md.HeaderMD, "x-http-code") + delete(w.Header(), "Grpc-Metadata-X-Http-Code") + w.WriteHeader(code) + } + + return nil +} + +// prepareGrpcServer provides a new grpc server with the provided grpc.ServerOptions using the provided certificates. +func prepareGrpcServer(certCfg *aserto.TLSConfig, opts []grpc.ServerOption) (*grpc.Server, error) { + // NoTLS path. + if NoTLS(certCfg) { + opts = append(opts, grpc.Creds(insecure.NewCredentials())) + return grpc.NewServer(opts...), nil + } + + // TLS path. + tlsCreds, err := certCfg.ServerCredentials() + if err != nil { + return nil, errors.Wrapf(err, "failed to get TLS credentials") + } + + tlsAuth := grpc.Creds(tlsCreds) + opts = append(opts, tlsAuth) + + return grpc.NewServer(opts...), nil +} + +// fieldsMaskHandler will set the Content-Type to "application/json+masked", which +// will signal the marshaler to not emit unpopulated types, which is needed to +// serialize the masked result set. +// This happens if a fields.mask query parameter is present and set. +func fieldsMaskHandler(h http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if p, ok := r.URL.Query()["fields.mask"]; ok && len(p) > 0 && p[0] != "" { + r.Header.Set("Content-Type", "application/json+masked") + } + h.ServeHTTP(w, r) + }) +} + +func contains[T comparable](slice []T, item T) bool { + for i := range slice { + if slice[i] == item { + return true + } + } + return false +} + +type key int + +var pathPatternKey key + +type gatewayPathPattern struct { + PathPattern string +} + +func captureGatewayRoute(ctx context.Context, r *http.Request) metadata.MD { + if pattern, ok := runtime.HTTPPathPattern(ctx); ok { + if gwPathPattern := gatewayContextValue(r); gwPathPattern != nil { + gwPathPattern.PathPattern = pattern + } + } + return nil +} + +func gatewayContextValue(r *http.Request) *gatewayPathPattern { + gwPathPattern, ok := r.Context().Value(pathPatternKey).(*gatewayPathPattern) + if !ok { + return nil + } + + return gwPathPattern +} diff --git a/internal/pkg/service/builder/service_manager.go b/internal/pkg/service/builder/service_manager.go new file mode 100644 index 00000000..94e58c6a --- /dev/null +++ b/internal/pkg/service/builder/service_manager.go @@ -0,0 +1,257 @@ +package builder + +import ( + "context" + "net" + "net/http" + "reflect" + "time" + + "github.com/aserto-dev/go-aserto" + + grpc_prometheus "github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus" + go_prometheus "github.com/grpc-ecosystem/go-grpc-prometheus" + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/collectors" + "github.com/prometheus/client_golang/prometheus/promhttp" + "github.com/rs/zerolog" + "golang.org/x/sync/errgroup" + "google.golang.org/grpc" +) + +type ServiceManager struct { + Context context.Context + logger *zerolog.Logger + errGroup *errgroup.Group + + Servers map[string]*Server + DependencyMap map[string][]string + HealthServer *Health + MetricServer *http.Server + shutdownTimeout int // timeout to force stop services in seconds +} + +var reg *prometheus.Registry + +func NewServiceManager(logger *zerolog.Logger) *ServiceManager { + if reg == nil { + reg = prometheus.NewRegistry() + } + serviceLogger := logger.With().Str("component", "service-manager").Logger() + errGroup, ctx := errgroup.WithContext(context.Background()) + return &ServiceManager{ + Context: ctx, + logger: &serviceLogger, + Servers: make(map[string]*Server), + DependencyMap: make(map[string][]string), + errGroup: errGroup, + shutdownTimeout: 30, + } +} + +func (s *ServiceManager) WithShutdownTimeout(seconds int) *ServiceManager { + s.shutdownTimeout = seconds + return s +} + +func (s *ServiceManager) AddGRPCServer(server *Server) error { + s.Servers[server.Config.GRPC.ListenAddress] = server + return nil +} + +func (s *ServiceManager) SetupHealthServer(address string, certCfg *aserto.TLSConfig) error { + healthServer := newGRPCHealthServer(certCfg) + healthServer.Address = address + + s.HealthServer = healthServer + healthListener, err := net.Listen("tcp", address) + s.logger.Info().Msgf("Starting %s health server", address) + if err != nil { + return err + } + s.errGroup.Go(func() error { + return healthServer.GRPCServer.Serve(healthListener) + }) + return nil +} + +func (s *ServiceManager) SetupMetricsServer(address string, certCfg *aserto.TLSConfig, enableZpages bool) ([]grpc.ServerOption, + error, +) { + metric := http.Server{ + ReadTimeout: 5 * time.Second, + ReadHeaderTimeout: 5 * time.Second, + WriteTimeout: 30 * time.Second, + IdleTimeout: 30 * time.Second, + } + s.MetricServer = &metric + mux := http.NewServeMux() + + grpcm := grpc_prometheus.NewServerMetrics( + grpc_prometheus.WithServerCounterOptions(), + grpc_prometheus.WithServerHandlingTimeHistogram(), + ) + + reg.MustRegister(collectors.NewGoCollector()) + reg.MustRegister(collectors.NewBuildInfoCollector()) + reg.MustRegister(grpcm) + + mux.Handle("/metrics", promhttp.HandlerFor(reg, promhttp.HandlerOpts{ + Registry: reg, + })) + + metric.Handler = mux + metric.Addr = address + + s.logger.Info().Msgf("Starting %s metric server", address) + + if NoTLS(certCfg) { + s.errGroup.Go(metric.ListenAndServe) + } else { + s.errGroup.Go(func() error { + return metric.ListenAndServeTLS(certCfg.Cert, certCfg.Key) + }) + } + + exemplarFromContext := func(ctx context.Context) prometheus.Labels { + method, ok := grpc.Method(ctx) + + if ok { + return prometheus.Labels{"method": method} + } + return nil + } + + var opts []grpc.ServerOption + + unary := grpc.ChainUnaryInterceptor(grpcm.UnaryServerInterceptor(grpc_prometheus.WithExemplarFromContext(exemplarFromContext))) + stream := grpc.ChainStreamInterceptor(grpcm.StreamServerInterceptor(grpc_prometheus.WithExemplarFromContext(exemplarFromContext))) + opts = append(opts, + unary, + stream, + grpc.ChainUnaryInterceptor(go_prometheus.UnaryServerInterceptor), + grpc.ChainStreamInterceptor(go_prometheus.StreamServerInterceptor), + ) + + return opts, nil +} + +func (s *ServiceManager) StartServers(ctx context.Context) error { + for serverAddress, value := range s.Servers { + address := serverAddress + serverDetails := value + + // log all service details. + s.logDetails(address, &serverDetails.Config.GRPC) + s.logDetails(address, &serverDetails.Config.Gateway) + + s.errGroup.Go(func() error { + if dependsOnArray, ok := s.DependencyMap[address]; ok { + for _, dependsOn := range dependsOnArray { + s.logger.Info().Msgf("%s waiting for %s", address, dependsOn) + <-s.Servers[dependsOn].Started // wait for started from the dependent service. + } + } + grpcServer := serverDetails.Server + listener := serverDetails.Listener + s.logger.Info().Msgf("Starting %s gRPC server", address) + s.errGroup.Go(func() error { + return grpcServer.Serve(listener) + }) + + httpServer := serverDetails.Gateway + if httpServer.Server != nil { + s.errGroup.Go(func() error { + s.logger.Info().Msgf("Starting %s gateway server", httpServer.Server.Addr) + if NoTLS(httpServer.Certs) { + err := httpServer.Server.ListenAndServe() + if err != nil { + return err + } + } + if TLS(httpServer.Certs) { + err := httpServer.Server.ListenAndServeTLS(httpServer.Certs.Cert, httpServer.Certs.Key) + if err != nil { + return err + } + } + return nil + }) + } + + serverDetails.Started <- true // send started information. + return nil + }) + } + return nil +} + +func (s *ServiceManager) logDetails(address string, element interface{}) { + ref := reflect.ValueOf(element).Elem() + typeOfT := ref.Type() + + for i := 0; i < ref.NumField(); i++ { + f := ref.Field(i) + s.logger.Debug().Str("address", address).Msgf("%s = %v\n", typeOfT.Field(i).Name, f.Interface()) + } +} + +func (s *ServiceManager) StopServers(ctx context.Context) { + timeout := time.Duration(s.shutdownTimeout) * time.Second + timeoutContext, cancel := context.WithTimeout(ctx, timeout) + defer cancel() + + if s.HealthServer != nil { + s.logger.Info().Msgf("Stopping %s health server", s.HealthServer.Address) + if !shutDown(s.HealthServer.GRPCServer, timeout) { + s.logger.Warn().Msgf("Stopped %s health server forcefully", s.HealthServer.Address) + } + } + if s.MetricServer != nil { + s.logger.Info().Msgf("Stopping %s metric server", s.MetricServer.Addr) + err := s.MetricServer.Shutdown(timeoutContext) + if err != nil { + s.logger.Err(err).Msgf("failed to shutdown metric server %s", s.MetricServer.Addr) + s.logger.Debug().Msgf("forcefully closing metric server %s", s.MetricServer.Addr) + if err := s.MetricServer.Close(); err != nil { + s.logger.Err(err).Msgf("failed to close the metric server %s", s.MetricServer.Addr) + } + } + } + for address, value := range s.Servers { + s.logger.Info().Msgf("Stopping %s gRPC server", address) + if !shutDown(value.Server, timeout) { + s.logger.Warn().Msgf("Stopped %s gRPC forcefully", address) + } + if value.Gateway.Server != nil { + s.logger.Info().Msgf("Stopping %s gateway server", value.Gateway.Server.Addr) + err := value.Gateway.Server.Shutdown(timeoutContext) + if err != nil { + s.logger.Err(err).Msgf("failed to shutdown gateway for %s", address) + s.logger.Debug().Msgf("forcefully closing gateway %s", address) + if err := value.Gateway.Server.Close(); err != nil { + s.logger.Err(err).Msgf("failed to close gateway server %s", address) + } + } + } + for _, cleanup := range value.Cleanup { + s.logger.Info().Msgf("Running cleanups for %s", address) + cleanup() + } + } +} + +func shutDown(server *grpc.Server, timeout time.Duration) bool { + result := make(chan bool, 1) + go func() { + server.GracefulStop() + result <- true + }() + select { + case <-time.After(timeout): + server.Stop() + return false + case response := <-result: + return response + } +} diff --git a/internal/pkg/service/builder/tls.go b/internal/pkg/service/builder/tls.go new file mode 100644 index 00000000..bab95780 --- /dev/null +++ b/internal/pkg/service/builder/tls.go @@ -0,0 +1,11 @@ +package builder + +import "github.com/aserto-dev/go-aserto" + +func NoTLS(cfg *aserto.TLSConfig) bool { + return (cfg == nil || cfg.CA == "" || cfg.Key == "" || cfg.Cert == "") +} + +func TLS(cfg *aserto.TLSConfig) bool { + return !NoTLS(cfg) +} diff --git a/pkg/cli/xdg/LICENSE b/internal/pkg/xdg/LICENSE similarity index 100% rename from pkg/cli/xdg/LICENSE rename to internal/pkg/xdg/LICENSE diff --git a/pkg/cli/xdg/README.md b/internal/pkg/xdg/README.md similarity index 100% rename from pkg/cli/xdg/README.md rename to internal/pkg/xdg/README.md diff --git a/pkg/cli/xdg/base_dirs.go b/internal/pkg/xdg/base_dirs.go similarity index 100% rename from pkg/cli/xdg/base_dirs.go rename to internal/pkg/xdg/base_dirs.go diff --git a/pkg/cli/xdg/example_test.go b/internal/pkg/xdg/example_test.go similarity index 100% rename from pkg/cli/xdg/example_test.go rename to internal/pkg/xdg/example_test.go diff --git a/internal/pkg/xdg/go.mod b/internal/pkg/xdg/go.mod new file mode 100644 index 00000000..6a54daab --- /dev/null +++ b/internal/pkg/xdg/go.mod @@ -0,0 +1,18 @@ +module github.com/adrg/xdg + +go 1.19 + +require ( + github.com/pkg/errors v0.9.1 + github.com/stretchr/testify v1.9.0 + golang.org/x/sys v0.27.0 +) + +require ( + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/kr/pretty v0.3.1 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/rogpeppe/go-internal v1.12.0 // indirect + gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/internal/pkg/xdg/go.sum b/internal/pkg/xdg/go.sum new file mode 100644 index 00000000..cb4c7847 --- /dev/null +++ b/internal/pkg/xdg/go.sum @@ -0,0 +1,27 @@ +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= +github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= +github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +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= +golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s= +golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/pkg/cli/xdg/internal/pathutil/pathutil.go b/internal/pkg/xdg/internal/pathutil/pathutil.go similarity index 100% rename from pkg/cli/xdg/internal/pathutil/pathutil.go rename to internal/pkg/xdg/internal/pathutil/pathutil.go diff --git a/pkg/cli/xdg/internal/pathutil/pathutil_test.go b/internal/pkg/xdg/internal/pathutil/pathutil_test.go similarity index 100% rename from pkg/cli/xdg/internal/pathutil/pathutil_test.go rename to internal/pkg/xdg/internal/pathutil/pathutil_test.go diff --git a/pkg/cli/xdg/internal/pathutil/pathutil_unix.go b/internal/pkg/xdg/internal/pathutil/pathutil_unix.go similarity index 100% rename from pkg/cli/xdg/internal/pathutil/pathutil_unix.go rename to internal/pkg/xdg/internal/pathutil/pathutil_unix.go diff --git a/pkg/cli/xdg/internal/pathutil/pathutil_unix_test.go b/internal/pkg/xdg/internal/pathutil/pathutil_unix_test.go similarity index 100% rename from pkg/cli/xdg/internal/pathutil/pathutil_unix_test.go rename to internal/pkg/xdg/internal/pathutil/pathutil_unix_test.go diff --git a/pkg/cli/xdg/internal/pathutil/pathutil_windows.go b/internal/pkg/xdg/internal/pathutil/pathutil_windows.go similarity index 100% rename from pkg/cli/xdg/internal/pathutil/pathutil_windows.go rename to internal/pkg/xdg/internal/pathutil/pathutil_windows.go diff --git a/pkg/cli/xdg/internal/pathutil/pathutil_windows_test.go b/internal/pkg/xdg/internal/pathutil/pathutil_windows_test.go similarity index 100% rename from pkg/cli/xdg/internal/pathutil/pathutil_windows_test.go rename to internal/pkg/xdg/internal/pathutil/pathutil_windows_test.go diff --git a/pkg/cli/xdg/paths_darwin.go b/internal/pkg/xdg/paths_darwin.go similarity index 100% rename from pkg/cli/xdg/paths_darwin.go rename to internal/pkg/xdg/paths_darwin.go diff --git a/pkg/cli/xdg/paths_darwin_test.go b/internal/pkg/xdg/paths_darwin_test.go similarity index 100% rename from pkg/cli/xdg/paths_darwin_test.go rename to internal/pkg/xdg/paths_darwin_test.go diff --git a/pkg/cli/xdg/paths_unix.go b/internal/pkg/xdg/paths_unix.go similarity index 100% rename from pkg/cli/xdg/paths_unix.go rename to internal/pkg/xdg/paths_unix.go diff --git a/pkg/cli/xdg/paths_unix_test.go b/internal/pkg/xdg/paths_unix_test.go similarity index 100% rename from pkg/cli/xdg/paths_unix_test.go rename to internal/pkg/xdg/paths_unix_test.go diff --git a/pkg/cli/xdg/paths_windows.go b/internal/pkg/xdg/paths_windows.go similarity index 100% rename from pkg/cli/xdg/paths_windows.go rename to internal/pkg/xdg/paths_windows.go diff --git a/pkg/cli/xdg/paths_windows_test.go b/internal/pkg/xdg/paths_windows_test.go similarity index 100% rename from pkg/cli/xdg/paths_windows_test.go rename to internal/pkg/xdg/paths_windows_test.go diff --git a/pkg/cli/xdg/user_dirs.go b/internal/pkg/xdg/user_dirs.go similarity index 100% rename from pkg/cli/xdg/user_dirs.go rename to internal/pkg/xdg/user_dirs.go diff --git a/pkg/cli/xdg/xdg.go b/internal/pkg/xdg/xdg.go similarity index 100% rename from pkg/cli/xdg/xdg.go rename to internal/pkg/xdg/xdg.go diff --git a/pkg/cli/xdg/xdg_test.go b/internal/pkg/xdg/xdg_test.go similarity index 100% rename from pkg/cli/xdg/xdg_test.go rename to internal/pkg/xdg/xdg_test.go diff --git a/makefile b/makefile index c8584b00..fc6e16bf 100644 --- a/makefile +++ b/makefile @@ -47,6 +47,11 @@ build: @(go env GOVERSION | grep "go${GO_VER}") || (echo "go version check failed expected go${GO_VER} got $$(go env GOVERSION)"; exit 1) @${EXT_BIN_DIR}/goreleaser build --clean --snapshot --single-target +PHONY: go-mod-tidy +go-mod-tidy: + @echo -e "$(ATTN_COLOR)==> $@ $(NO_COLOR)" + @go work edit -json | jq -r '.Use[].DiskPath' | xargs -I{} bash -c 'cd {} && echo "${PWD}/go.mod" && go mod tidy -v && cd -' + .PHONY: dev-release dev-release: @echo -e "$(ATTN_COLOR)==> $@ $(NO_COLOR)" @@ -77,6 +82,11 @@ test-snapshot: @echo -e "$(ATTN_COLOR)==> $@ $(NO_COLOR)" @${EXT_BIN_DIR}/goreleaser release --config .goreleaser-test.yml --clean --snapshot +.PHONE: container-tag +container-tag: + @echo -e "$(ATTN_COLOR)==> $@ $(NO_COLOR)" + @scripts/container-tag.sh > .container-tag.env + .PHONY: test test: test-snapshot @echo -e "$(ATTN_COLOR)==> test github.com/aserto-dev/topaz/pkg/app/tests/$@/... $(NO_COLOR)" @@ -97,10 +107,10 @@ $(ASSETS): install-check2decision TEMPLATES = "assets/api-auth.json" "assets/gdrive.json" "assets/github.json" "assets/multi-tenant.json" "assets/peoplefinder.json" "assets/simple-rbac.json" "assets/slack.json" "assets/todo.json" .PHONY: test-templates test-templates: $(TEMPLATES) -$(TEMPLATES): snapshot +$(TEMPLATES): test-snapshot @echo -e "$(ATTN_COLOR)==> github.com/aserto-dev/topaz/assets/$@ $(NO_COLOR)" - @echo topaz templates install $@ --force --no-console --container-tag=$$(${EXT_BIN_DIR}/svu patch --strip-prefix)-$$(git rev-parse --short HEAD)-dirty-${GOARCH} - @./dist/topaz_${GOOS}_${GOARCH}/topaz templates install $@ --force --no-console --container-tag=$$(${EXT_BIN_DIR}/svu patch --strip-prefix)-$$(git rev-parse --short HEAD)-dirty-${GOARCH} + @echo topaz templates install $@ --force --no-console --container-tag=test-$$(git rev-parse --short HEAD)-${GOARCH} + @./dist/topaz_${GOOS}_${GOARCH}/topaz templates install $@ --force --no-console --container-tag=test-$$(git rev-parse --short HEAD)-${GOARCH} @./dist/topaz_${GOOS}_${GOARCH}/topaz stop --wait .PHONY: vault-login diff --git a/pkg/app/authorizer.go b/pkg/app/authorizer.go index 7eef22f9..7c19c4d0 100644 --- a/pkg/app/authorizer.go +++ b/pkg/app/authorizer.go @@ -5,7 +5,6 @@ import ( "net/http" "strconv" - "github.com/aserto-dev/certs" authz "github.com/aserto-dev/go-authorizer/aserto/authorizer/v2" azOpenAPI "github.com/aserto-dev/openapi-authorizer/publish/authorizer" builder "github.com/aserto-dev/service-host" @@ -17,8 +16,6 @@ import ( "github.com/pkg/errors" "github.com/rs/zerolog" - "go.opencensus.io/plugin/ocgrpc" - "go.opencensus.io/stats/view" "google.golang.org/grpc" ) @@ -35,8 +32,8 @@ const ( ) func NewAuthorizer(ctx context.Context, cfg *builder.API, commonConfig *config.Common, authorizerOpts []grpc.ServerOption, logger *zerolog.Logger) (ServiceTypes, error) { - if cfg.GRPC.Certs.TLSCertPath != "" { - tlsCreds, err := certs.GRPCServerTLSCreds(cfg.GRPC.Certs) + if builder.TLS(&cfg.GRPC.Certs) { + tlsCreds, err := cfg.GRPC.Certs.ServerCredentials() if err != nil { return nil, errors.Wrap(err, "failed to calculate tls config") } @@ -44,10 +41,6 @@ func NewAuthorizer(ctx context.Context, cfg *builder.API, commonConfig *config.C tlsAuth := grpc.Creds(tlsCreds) authorizerOpts = append(authorizerOpts, tlsAuth) } - if err := view.Register(ocgrpc.DefaultServerViews...); err != nil { - return nil, err - } - authorizerOpts = append(authorizerOpts, grpc.StatsHandler(&ocgrpc.ServerHandler{})) authResolvers := resolvers.New() diff --git a/pkg/app/console.go b/pkg/app/console.go index d6e577e5..760d062b 100644 --- a/pkg/app/console.go +++ b/pkg/app/console.go @@ -59,19 +59,15 @@ func (e *ConsoleService) PrepareConfig(cfg *config.Config) *handlers.TopazCfg { directoryServiceURL = readerURL } } + writerURL := "" if serviceConfig, ok := cfg.APIConfig.Services[writerService]; ok { writerURL = getGatewayAddress(serviceConfig) } - importerURL := "" - if serviceConfig, ok := cfg.APIConfig.Services[importerService]; ok { - importerURL = getGatewayAddress(serviceConfig) - } - exporterURL := "" - if serviceConfig, ok := cfg.APIConfig.Services[exporterService]; ok { - exporterURL = getGatewayAddress(serviceConfig) - } + importerURL := "" // always empty, no gateway service associated with the importer service. + + exporterURL := "" // always empty, no gateway service associated with the exporter service. modelURL := "" if serviceConfig, ok := cfg.APIConfig.Services[modelService]; ok { @@ -115,11 +111,14 @@ func getGatewayAddress(serviceConfig *builder.API) string { } addr := serviceAddress(serviceConfig.Gateway.ListenAddress) + if builder.NoTLS(&serviceConfig.Gateway.Certs) { + serviceConfig.Gateway.HTTP = true + } + if serviceConfig.Gateway.HTTP { return fmt.Sprintf("http://%s", addr) - } else { - return fmt.Sprintf("https://%s", addr) } + return fmt.Sprintf("https://%s", addr) } func serviceAddress(listenAddress string) string { diff --git a/pkg/app/handlers/config.go b/pkg/app/handlers/config.go index 05090086..42aa737a 100644 --- a/pkg/app/handlers/config.go +++ b/pkg/app/handlers/config.go @@ -114,9 +114,8 @@ func authType(r *http.Request) string { authEnabled := r.Context().Value(AuthEnabled) if authEnabled != nil && authEnabled.(bool) { return "apiKey" - } else { - return "anonymous" } + return "anonymous" } func writeJSON(buf []byte, w http.ResponseWriter, _ *http.Request) { diff --git a/pkg/app/tests/authz/authz_test.go b/pkg/app/tests/authz/authz_test.go index e12074f4..f672f513 100644 --- a/pkg/app/tests/authz/authz_test.go +++ b/pkg/app/tests/authz/authz_test.go @@ -3,7 +3,6 @@ package authz_test import ( "context" "os" - "runtime" "testing" "time" @@ -34,7 +33,7 @@ func TestMain(m *testing.M) { ctx := context.Background() h, err := tc.NewHarness(ctx, &testcontainers.ContainerRequest{ - Image: "ghcr.io/aserto-dev/topaz:test-" + tc.CommitSHA() + "-" + runtime.GOARCH, + Image: tc.TestImage(), ExposedPorts: []string{"9292/tcp", "9393/tcp"}, Env: map[string]string{ "TOPAZ_CERTS_DIR": "/certs", @@ -123,8 +122,8 @@ func DecisionTreeWithMissingIdentity(ctx context.Context, azClient authorizer.Au if assert.Error(t, errX) { s, ok := status.FromError(errX) - assert.Equal(t, ok, true) - assert.Equal(t, s.Code(), codes.NotFound) + assert.True(t, ok) + assert.Equal(t, codes.NotFound, s.Code()) } assert.Nil(t, respX, "response object should be nil") } @@ -149,7 +148,7 @@ func DecisionTreeWithUserID(ctx context.Context, azClient authorizer.AuthorizerC t.Logf("ERR >>> %s\n", errX) } - assert.NoError(t, errX) + require.NoError(t, errX) assert.NotNil(t, respX, "response object should not be nil") assert.Equal(t, "peoplefinder.GET", respX.PathRoot) @@ -178,8 +177,8 @@ func IsWithMissingIdentity(ctx context.Context, azClient authorizer.AuthorizerCl if assert.Error(t, errX) { s, ok := status.FromError(errX) - assert.Equal(t, ok, true) - assert.Equal(t, s.Code(), codes.NotFound) + assert.True(t, ok, true) + assert.Equal(t, codes.NotFound, s.Code()) } assert.Nil(t, respX, "response object should be nil") } @@ -213,8 +212,8 @@ func QueryWithMissingIdentity(ctx context.Context, azClient authorizer.Authorize if assert.Error(t, errX) { s, ok := status.FromError(errX) - assert.Equal(t, ok, true) - assert.Equal(t, s.Code(), codes.NotFound) + assert.True(t, ok, true) + assert.Equal(t, codes.NotFound, s.Code()) } assert.Nil(t, respX, "response object should be nil") } diff --git a/pkg/app/tests/builtin/builtin_test.go b/pkg/app/tests/builtin/builtin_test.go index 6836aa80..9c68965b 100644 --- a/pkg/app/tests/builtin/builtin_test.go +++ b/pkg/app/tests/builtin/builtin_test.go @@ -3,7 +3,6 @@ package builtin_test import ( "context" "os" - "runtime" "testing" "time" @@ -29,7 +28,7 @@ func TestMain(m *testing.M) { ctx := context.Background() h, err := tc.NewHarness(ctx, &testcontainers.ContainerRequest{ - Image: "ghcr.io/aserto-dev/topaz:test-" + tc.CommitSHA() + "-" + runtime.GOARCH, + Image: tc.TestImage(), ExposedPorts: []string{"9292/tcp", "9393/tcp", "9494/tcp", "9595/tcp"}, Env: map[string]string{ "TOPAZ_CERTS_DIR": "/certs", diff --git a/pkg/app/tests/common/common.go b/pkg/app/tests/common/common.go index b4f78fb9..2e78fa29 100644 --- a/pkg/app/tests/common/common.go +++ b/pkg/app/tests/common/common.go @@ -77,5 +77,5 @@ func CommitSHA() string { } func TestImage() string { - return "ghcr.io/aserto-dev/topaz:test-" + CommitSHA() + "-" + runtime.GOARCH + return "ghcr.io/aserto-dev/topaz:0.0.0-test-" + CommitSHA() + "-" + runtime.GOARCH } diff --git a/pkg/app/tests/manifest/manifest_test.go b/pkg/app/tests/manifest/manifest_test.go index 86beb37d..cda90f3d 100644 --- a/pkg/app/tests/manifest/manifest_test.go +++ b/pkg/app/tests/manifest/manifest_test.go @@ -3,10 +3,10 @@ package manifest_test import ( "bytes" "context" + "errors" "io" "os" "path" - "runtime" "testing" "time" @@ -33,7 +33,7 @@ func TestMain(m *testing.M) { ctx := context.Background() h, err := tc.NewHarness(ctx, &testcontainers.ContainerRequest{ - Image: "ghcr.io/aserto-dev/topaz:test-" + tc.CommitSHA() + "-" + runtime.GOARCH, + Image: tc.TestImage(), ExposedPorts: []string{"9292/tcp", "9393/tcp"}, Env: map[string]string{ "TOPAZ_CERTS_DIR": "/certs", @@ -97,7 +97,7 @@ func TestManifest(t *testing.T) { // write manifest to temp manifest file bytesRecv, err := io.Copy(w, body) - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, bytesSend, bytesRecv) @@ -120,7 +120,7 @@ func TestManifest(t *testing.T) { n, err := body.Read(buf) assert.Error(t, err, "EOF") - assert.Equal(t, n, 0) + assert.Equal(t, 0, n) assert.Len(t, buf, 1024) } else { assert.NoError(t, err) @@ -139,7 +139,7 @@ func getManifest(ctx context.Context, dsm dsm3.ModelClient) (*dsm3.Metadata, io. bytesRecv := 0 for { resp, err := stream.Recv() - if err == io.EOF { + if errors.Is(err, io.EOF) { break } diff --git a/pkg/app/tests/policy/policy_test.go b/pkg/app/tests/policy/policy_test.go index 15a4a50b..25b531ee 100644 --- a/pkg/app/tests/policy/policy_test.go +++ b/pkg/app/tests/policy/policy_test.go @@ -3,7 +3,6 @@ package policy_test import ( "context" "os" - "runtime" "testing" "time" @@ -30,7 +29,7 @@ func TestMain(m *testing.M) { ctx := context.Background() h, err := tc.NewHarness(ctx, &testcontainers.ContainerRequest{ - Image: "ghcr.io/aserto-dev/topaz:test-" + tc.CommitSHA() + "-" + runtime.GOARCH, + Image: tc.TestImage(), ExposedPorts: []string{"9292/tcp", "9393/tcp"}, Env: map[string]string{ "TOPAZ_CERTS_DIR": "/certs", @@ -112,7 +111,7 @@ func ListPolicies(ctx context.Context, azClient authorizer.AuthorizerClient) fun assert.NoError(err) assert.NotNil(listPoliciesResponse.Result) - assert.Greater(len(listPoliciesResponse.Result), 0) + assert.NotEmpty(listPoliciesResponse.Result) } } @@ -129,7 +128,7 @@ func ListPoliciesMasked(ctx context.Context, azClient authorizer.AuthorizerClien assert.NoError(err) assert.NotNil(listPoliciesResponse.Result) - assert.Greater(len(listPoliciesResponse.Result), 0) + assert.NotEmpty(listPoliciesResponse.Result) assert.Nil(listPoliciesResponse.Result[0].Id) assert.NotNil(listPoliciesResponse.Result[0].Raw) } @@ -149,7 +148,7 @@ func ListPoliciesMaskedComposed(ctx context.Context, azClient authorizer.Authori assert.NoError(err) assert.NotNil(listPoliciesResponse.Result) - assert.Greater(len(listPoliciesResponse.Result), 0) + assert.NotEmpty(listPoliciesResponse.Result) assert.Nil(listPoliciesResponse.Result[0].Id) assert.NotNil(listPoliciesResponse.Result[0].Raw) assert.NotNil(listPoliciesResponse.Result[0].PackagePath) @@ -169,7 +168,7 @@ func ListPoliciesInvalidMask(ctx context.Context, azClient authorizer.Authorizer assert.NoError(err) assert.NotNil(listPoliciesResponse.Result) - assert.Greater(len(listPoliciesResponse.Result), 0) + assert.NotEmpty(listPoliciesResponse.Result) assert.NotNil(listPoliciesResponse.Result[0].Id) assert.NotNil(listPoliciesResponse.Result[0].Raw) assert.NotNil(listPoliciesResponse.Result[0].PackagePath) @@ -188,7 +187,7 @@ func ListPoliciesEmptyMask(ctx context.Context, azClient authorizer.AuthorizerCl assert.NoError(err) assert.NotNil(listPoliciesResponse.Result) - assert.Greater(len(listPoliciesResponse.Result), 0) + assert.NotEmpty(listPoliciesResponse.Result) assert.NotNil(listPoliciesResponse.Result[0].Id) assert.NotNil(listPoliciesResponse.Result[0].Raw) assert.NotNil(listPoliciesResponse.Result[0].PackagePath) diff --git a/pkg/app/tests/query/query_test.go b/pkg/app/tests/query/query_test.go index 5e55d3ae..77347945 100644 --- a/pkg/app/tests/query/query_test.go +++ b/pkg/app/tests/query/query_test.go @@ -4,7 +4,6 @@ import ( "context" "encoding/json" "os" - "runtime" "testing" "time" @@ -31,7 +30,7 @@ func TestMain(m *testing.M) { ctx := context.Background() h, err := tc.NewHarness(ctx, &testcontainers.ContainerRequest{ - Image: "ghcr.io/aserto-dev/topaz:test-" + tc.CommitSHA() + "-" + runtime.GOARCH, + Image: tc.TestImage(), ExposedPorts: []string{"9292/tcp", "9393/tcp"}, Env: map[string]string{ "TOPAZ_CERTS_DIR": "/certs", @@ -118,7 +117,7 @@ var queryTests = []struct { } require.NotNil(t, result) - require.Greater(t, len(result.Result), 0) + require.NotEmpty(t, result.Result) require.Contains(t, result.Result[0].Bindings, "x") require.Contains(t, result.Result[0].Bindings["x"], "env") }, @@ -141,7 +140,7 @@ var queryTests = []struct { require.NoError(t, err) } require.NotNil(t, result) - require.Greater(t, len(result.Result), 0) + require.NotEmpty(t, result.Result) require.Contains(t, result.Result[0].Bindings, "x") require.Contains(t, result.Result[0].Bindings["x"], "rebac") }, @@ -165,11 +164,11 @@ var queryTests = []struct { } require.NotNil(t, result) - require.Greater(t, len(result.Result), 0) + require.NotEmpty(t, result.Result) require.Contains(t, result.Result[0].Bindings, "x") require.Contains(t, result.Result[0].Bindings["x"], "id") binding := result.Result[0].Bindings["x"].(map[string]interface{}) - require.Equal(t, binding["id"], "euang@acmecorp.com") + require.Equal(t, "euang@acmecorp.com", binding["id"]) }, }, { @@ -191,9 +190,9 @@ var queryTests = []struct { } require.NotNil(t, result) - require.Greater(t, len(result.Result), 0) + require.NotEmpty(t, result.Result) require.Contains(t, result.Result[0].Bindings, "x") - require.Contains(t, result.Result[0].Bindings["x"], "euang@acmecorp.com") + require.Contains(t, "euang@acmecorp.com", result.Result[0].Bindings["x"]) }, }, { @@ -219,7 +218,7 @@ var queryTests = []struct { } require.NotNil(t, result) - require.Greater(t, len(result.Result), 0) + require.NotEmpty(t, result.Result) require.Contains(t, result.Result[0].Bindings, "x") require.Contains(t, result.Result[0].Bindings["x"], "identity") require.Contains(t, result.Result[0].Bindings["x"], "user") @@ -252,7 +251,7 @@ var queryTests = []struct { } require.NotNil(t, result) - require.Greater(t, len(result.Result), 0) + require.NotEmpty(t, result.Result) require.Contains(t, result.Result[0].Bindings, "x") require.Contains(t, result.Result[0].Bindings["x"], "identity") require.Contains(t, result.Result[0].Bindings["x"], "user") @@ -260,7 +259,7 @@ var queryTests = []struct { bindings := result.Result[0].Bindings["x"].(map[string]interface{}) require.Contains(t, bindings["identity"], "identity") require.Contains(t, bindings["identity"], "type") - require.Equal(t, bindings["user"], map[string]interface{}{}) + require.Equal(t, map[string]interface{}{}, bindings["user"]) }, }, } diff --git a/pkg/app/tests/template-no-tls/template-no-tls_test.go b/pkg/app/tests/template-no-tls/template-no-tls_test.go new file mode 100644 index 00000000..2f69b92d --- /dev/null +++ b/pkg/app/tests/template-no-tls/template-no-tls_test.go @@ -0,0 +1,190 @@ +package template_no_tls_test + +import ( + "context" + "os" + "path/filepath" + "testing" + "time" + + assets_test "github.com/aserto-dev/topaz/assets" + tc "github.com/aserto-dev/topaz/pkg/app/tests/common" + "github.com/aserto-dev/topaz/pkg/cli/cc" + azc "github.com/aserto-dev/topaz/pkg/cli/clients/authorizer" + dsc "github.com/aserto-dev/topaz/pkg/cli/clients/directory" + "github.com/aserto-dev/topaz/pkg/cli/cmd/authorizer" + "github.com/aserto-dev/topaz/pkg/cli/cmd/common" + "github.com/aserto-dev/topaz/pkg/cli/cmd/directory" + "github.com/aserto-dev/topaz/pkg/cli/cmd/templates" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/testcontainers/testcontainers-go" + "github.com/testcontainers/testcontainers-go/wait" +) + +var addr string + +func TestMain(m *testing.M) { + rc := 0 + defer func() { + os.Exit(rc) + }() + + ctx := context.Background() + h, err := tc.NewHarness(ctx, &testcontainers.ContainerRequest{ + Image: tc.TestImage(), + ExposedPorts: []string{"9292/tcp", "9393/tcp"}, + Env: map[string]string{ + "TOPAZ_CERTS_DIR": "/certs", + "TOPAZ_DB_DIR": "/data", + "TOPAZ_DECISIONS_DIR": "/decisions", + }, + Files: []testcontainers.ContainerFile{ + { + Reader: assets_test.ConfigNoTLSReader(), + ContainerFilePath: "/config/config.yaml", + FileMode: 0x700, + }, + }, + WaitingFor: wait.ForAll( + wait.ForExposedPort(), + wait.ForLog("Starting 0.0.0.0:9393 gateway server"), + ).WithStartupTimeoutDefault(120 * time.Second).WithDeadline(360 * time.Second), + }) + if err != nil { + rc = 99 + return + } + + defer func() { + if err := h.Close(ctx); err != nil { + rc = 100 + } + }() + + addr = h.AddrGRPC(ctx) + + rc = m.Run() +} + +var tcs = []string{ + "../../../../assets/acmecorp.json", + "../../../../assets/api-auth.json", + "../../../../assets/citadel.json", + "../../../../assets/gdrive.json", + "../../../../assets/github.json", + "../../../../assets/multi-tenant.json", + "../../../../assets/peoplefinder.json", + "../../../../assets/simple-rbac.json", + "../../../../assets/slack.json", + "../../../../assets/todo.json", +} + +func TestTemplate(t *testing.T) { + t.Logf("addr: %s", addr) + + t.Setenv("TOPAZ_NO_COLOR", "true") + c, err := cc.NewCommonContext(context.Background(), true, filepath.Join(cc.GetTopazDir(), common.CLIConfigurationFile)) + require.NoError(t, err) + + dsConfig := &dsc.Config{ + Host: addr, + Insecure: false, + Plaintext: true, + } + + azConfig := &azc.Config{ + Host: addr, + Insecure: false, + Plaintext: true, + } + + for _, tmpl := range tcs { + absPath, err := filepath.Abs(tmpl) + require.NoError(t, err) + + tmpl, err := templates.GetTemplateFromFile(absPath) + require.NoError(t, err) + + t.Logf("name %s", tmpl.Name) + t.Logf("template: %s", absPath) + + dirPath := filepath.Dir(absPath) + t.Logf("dir %s", dirPath) + + manifestFile := filepath.Join(dirPath, tmpl.Assets.Manifest) + t.Logf("manifestFile: %s", manifestFile) + t.Run(tmpl.Name+"-DeleteManifest", DeleteManifest(c, dsConfig)) + t.Run(tmpl.Name+"-SetManifest", SetManifest(c, dsConfig, manifestFile)) + + if len(tmpl.Assets.IdentityData) > 0 { + idpDataDir := filepath.Dir(filepath.Join(dirPath, tmpl.Assets.IdentityData[0])) + t.Logf("idp_data: %s", idpDataDir) + t.Run(tmpl.Name+"-ImportIdentityData", ImportData(c, dsConfig, idpDataDir)) + } + + if len(tmpl.Assets.DomainData) > 0 { + domainDataDir := filepath.Dir(filepath.Join(dirPath, tmpl.Assets.DomainData[0])) + t.Logf("domain_data: %s", domainDataDir) + t.Run(tmpl.Name+"-ImportDomainData", ImportData(c, dsConfig, domainDataDir)) + } + + if len(tmpl.Assets.Assertions) > 0 { + assertionsFile := filepath.Join(dirPath, tmpl.Assets.Assertions[0]) + t.Logf("assertionsFile: %s", assertionsFile) + t.Run(tmpl.Name+"-ExecDirectoryTest", ExecDirectoryTests(c, dsConfig, []string{assertionsFile})) + } + + if len(tmpl.Assets.Assertions) > 1 { + decisionsFile := filepath.Join(dirPath, tmpl.Assets.Assertions[1]) + t.Logf("decisionsFile: %s", decisionsFile) + t.Run(tmpl.Name+"-ExecAuthorizerTest", ExecAuthorizerTests(c, azConfig, []string{decisionsFile})) + } + } +} + +func DeleteManifest(c *cc.CommonCtx, cfg *dsc.Config) func(*testing.T) { + return func(t *testing.T) { + cmd := directory.DeleteManifestCmd{Config: *cfg, Force: true} + if err := cmd.Run(c); err != nil { + assert.NoError(t, err) + } + } +} + +func SetManifest(c *cc.CommonCtx, cfg *dsc.Config, path string) func(*testing.T) { + return func(t *testing.T) { + cmd := directory.SetManifestCmd{Config: *cfg, Path: path} + if err := cmd.Run(c); err != nil { + assert.NoError(t, err) + } + } +} + +func ImportData(c *cc.CommonCtx, cfg *dsc.Config, dir string) func(*testing.T) { + return func(t *testing.T) { + cmd := directory.ImportCmd{Config: *cfg, Directory: dir} + if err := cmd.Run(c); err != nil { + assert.NoError(t, err) + } + } +} + +func ExecDirectoryTests(c *cc.CommonCtx, cfg *dsc.Config, files []string) func(*testing.T) { + return func(t *testing.T) { + cmd := directory.TestExecCmd{Config: *cfg, TestExecCmd: common.TestExecCmd{Files: files, Summary: true, Desc: "on-error"}} + if err := cmd.Run(c); err != nil { + assert.NoError(t, err) + } + } +} + +func ExecAuthorizerTests(c *cc.CommonCtx, cfg *azc.Config, files []string) func(*testing.T) { + return func(t *testing.T) { + cmd := authorizer.TestExecCmd{Config: *cfg, TestExecCmd: common.TestExecCmd{Files: files, Summary: true, Desc: "on-error"}} + if err := cmd.Run(c); err != nil { + assert.NoError(t, err) + } + } +} diff --git a/pkg/app/tests/template/template_test.go b/pkg/app/tests/template/template_test.go index 8b030903..47db314b 100644 --- a/pkg/app/tests/template/template_test.go +++ b/pkg/app/tests/template/template_test.go @@ -4,7 +4,6 @@ import ( "context" "os" "path/filepath" - "runtime" "testing" "time" @@ -34,7 +33,7 @@ func TestMain(m *testing.M) { ctx := context.Background() h, err := tc.NewHarness(ctx, &testcontainers.ContainerRequest{ - Image: "ghcr.io/aserto-dev/topaz:test-" + tc.CommitSHA() + "-" + runtime.GOARCH, + Image: tc.TestImage(), ExposedPorts: []string{"9292/tcp", "9393/tcp"}, Env: map[string]string{ "TOPAZ_CERTS_DIR": "/certs", @@ -85,18 +84,20 @@ var tcs = []string{ func TestTemplate(t *testing.T) { t.Logf("addr: %s", addr) - os.Setenv("TOPAZ_NO_COLOR", "true") + t.Setenv("TOPAZ_NO_COLOR", "true") c, err := cc.NewCommonContext(context.Background(), true, filepath.Join(cc.GetTopazDir(), common.CLIConfigurationFile)) require.NoError(t, err) dsConfig := &dsc.Config{ - Host: addr, - Insecure: true, + Host: addr, + Insecure: true, + Plaintext: false, } azConfig := &azc.Config{ - Host: addr, - Insecure: true, + Host: addr, + Insecure: true, + Plaintext: false, } for _, tmpl := range tcs { diff --git a/pkg/app/topaz.go b/pkg/app/topaz.go index 71476558..0b7d3a42 100644 --- a/pkg/app/topaz.go +++ b/pkg/app/topaz.go @@ -215,7 +215,7 @@ func (e *Topaz) setupHealthAndMetrics() ([]grpc.ServerOption, error) { if e.Configuration.APIConfig.Metrics.ListenAddress != "" { metricsMiddleware, err := e.Manager.SetupMetricsServer(e.Configuration.APIConfig.Metrics.ListenAddress, e.Configuration.APIConfig.Metrics.Certificates, - e.Configuration.APIConfig.Metrics.ZPages) + false) if err != nil { return nil, err } @@ -296,42 +296,38 @@ func KeepAliveDialOption() []grpc.DialOption { } func (e *Topaz) GetDecisionLogger(cfg config.DecisionLogConfig) (decisionlog.DecisionLogger, error) { - var decisionlogger decisionlog.DecisionLogger + var decisionLogger decisionlog.DecisionLogger var err error switch cfg.Type { case "self": - decisionlogger, err = self.New(e.Context, cfg.Config, e.Logger, KeepAliveDialOption()...) + decisionLogger, err = self.New(e.Context, cfg.Config, e.Logger, KeepAliveDialOption()...) if err != nil { return nil, err } case "file": - maxsize := 0 - maxfiles := 0 - - logpath := cfg.Config["log_file_path"] - maxsize, _ = cfg.Config["max_file_size_mb"].(int) - maxfiles, _ = cfg.Config["max_file_count"].(int) - - decisionlogger, err = file.New(e.Context, &file.Config{ - LogFilePath: fmt.Sprintf("%v", logpath), - MaxFileSizeMB: maxsize, - MaxFileCount: maxfiles, + logPath := cfg.Config["log_file_path"] + maxSize, _ := cfg.Config["max_file_size_mb"].(int) + maxFiles, _ := cfg.Config["max_file_count"].(int) + + decisionLogger, err = file.New(e.Context, &file.Config{ + LogFilePath: fmt.Sprintf("%v", logPath), + MaxFileSizeMB: maxSize, + MaxFileCount: maxFiles, }, e.Logger) if err != nil { return nil, err } default: - decisionlogger, err = nop.New(e.Context, e.Logger) + decisionLogger, err = nop.New(e.Context, e.Logger) if err != nil { return nil, err } - } - return decisionlogger, err + return decisionLogger, err } func (e *Topaz) validateConfig() error { diff --git a/pkg/cc/cc.go b/pkg/cc/cc.go index d9819897..5034e067 100644 --- a/pkg/cc/cc.go +++ b/pkg/cc/cc.go @@ -23,30 +23,30 @@ var ( once sync.Once cc *CC cleanup func() - singletonErr error + errSingleton error ) // NewCC creates a singleton CC. func NewCC(logOutput logger.Writer, errOutput logger.ErrWriter, configPath config.Path, overrides config.Overrider) (*CC, func(), error) { once.Do(func() { - cc, cleanup, singletonErr = buildCC(logOutput, errOutput, configPath, overrides) + cc, cleanup, errSingleton = buildCC(logOutput, errOutput, configPath, overrides) }) return cc, func() { cleanup() once = sync.Once{} - }, singletonErr + }, errSingleton } // NewTestCC creates a singleton CC to be used for testing. // It uses a fake context (context.Background). func NewTestCC(logOutput logger.Writer, errOutput logger.ErrWriter, configPath config.Path, overrides config.Overrider) (*CC, func(), error) { once.Do(func() { - cc, cleanup, singletonErr = buildTestCC(logOutput, errOutput, configPath, overrides) + cc, cleanup, errSingleton = buildTestCC(logOutput, errOutput, configPath, overrides) }) return cc, func() { cleanup() once = sync.Once{} - }, singletonErr + }, errSingleton } diff --git a/pkg/cc/config/config.go b/pkg/cc/config/config.go index 850a6f80..25d0aa11 100644 --- a/pkg/cc/config/config.go +++ b/pkg/cc/config/config.go @@ -4,7 +4,6 @@ import ( "fmt" "io" "os" - "path/filepath" "github.com/aserto-dev/certs" client "github.com/aserto-dev/go-aserto" @@ -20,10 +19,7 @@ import ( // CommandMode -- enum type. type CommandMode int -var ( - DefaultTLSGenDir = os.ExpandEnv("$HOME/.config/topaz/certs") - CertificateSets = []string{"grpc", "gateway"} -) +var CertificateSets = []string{"grpc", "gateway"} // CommandMode -- enum constants. const ( @@ -34,13 +30,13 @@ const ( type ServicesConfig struct { Health struct { - ListenAddress string `json:"listen_address"` - Certificates *certs.TLSCredsConfig `json:"certs"` + ListenAddress string `json:"listen_address"` + Certificates *client.TLSConfig `json:"certs"` } `json:"health"` Metrics struct { - ListenAddress string `json:"listen_address"` - Certificates *certs.TLSCredsConfig `json:"certs"` - ZPages bool `json:"zpages"` + ListenAddress string `json:"listen_address"` + Certificates *client.TLSConfig `json:"certs"` + ZPages bool `json:"zpages"` } `json:"metrics"` Services map[string]*builder.API `json:"services"` } @@ -151,11 +147,6 @@ func NewConfig(configPath Path, log *zerolog.Logger, overrides Overrider, certsG return nil, errors.Wrap(err, "failed to validate config file") } - err = setDefaultCerts(configLoader.Configuration) - if err != nil { - return nil, err - } - if certsGenerator != nil { err = configLoader.Configuration.setupCerts(log, certsGenerator) if err != nil { @@ -184,15 +175,17 @@ func NewLoggerConfig(configPath Path, overrides Overrider) (*logger.Config, erro } func (c *Config) setupCerts(log *zerolog.Logger, certsGenerator *certs.Generator) error { + commonName := "topaz" + existingFiles := []string{} for serviceName, config := range c.APIConfig.Services { for _, file := range []string{ - config.GRPC.Certs.TLSCACertPath, - config.GRPC.Certs.TLSCertPath, - config.GRPC.Certs.TLSKeyPath, - config.Gateway.Certs.TLSCACertPath, - config.Gateway.Certs.TLSCertPath, - config.Gateway.Certs.TLSKeyPath, + config.GRPC.Certs.CA, + config.GRPC.Certs.Cert, + config.GRPC.Certs.Key, + config.Gateway.Certs.CA, + config.Gateway.Certs.Cert, + config.Gateway.Certs.Key, } { exists, err := FileExists(file) if err != nil { @@ -207,29 +200,32 @@ func (c *Config) setupCerts(log *zerolog.Logger, certsGenerator *certs.Generator } if len(existingFiles) == 0 { - err := certsGenerator.MakeDevCert(&certs.CertGenConfig{ - CommonName: fmt.Sprintf("%s-grpc", serviceName), - CertKeyPath: config.GRPC.Certs.TLSKeyPath, - CertPath: config.GRPC.Certs.TLSCertPath, - CACertPath: config.GRPC.Certs.TLSCACertPath, - DefaultTLSGenDir: DefaultTLSGenDir, - }) - if err != nil { - return errors.Wrapf(err, "failed to generate grpc certs (%s)", serviceName) + if builder.TLS(&config.GRPC.Certs) { + err := certsGenerator.MakeDevCert(&certs.CertGenConfig{ + CommonName: fmt.Sprintf("%s-grpc", commonName), + CertKeyPath: config.GRPC.Certs.Key, + CertPath: config.GRPC.Certs.Cert, + CertCAPath: config.GRPC.Certs.CA, + }) + if err != nil { + return errors.Wrapf(err, "failed to generate grpc certs (%s)", serviceName) + } + log.Info().Str("service", serviceName).Msg("gRPC certs configured") } - err = certsGenerator.MakeDevCert(&certs.CertGenConfig{ - CommonName: fmt.Sprintf("%s-gateway", serviceName), - CertKeyPath: config.Gateway.Certs.TLSKeyPath, - CertPath: config.Gateway.Certs.TLSCertPath, - CACertPath: config.Gateway.Certs.TLSCACertPath, - DefaultTLSGenDir: DefaultTLSGenDir, - }) - if err != nil { - return errors.Wrapf(err, "failed to generate gateway certs (%s)", serviceName) + if builder.TLS(&config.Gateway.Certs) { + err := certsGenerator.MakeDevCert(&certs.CertGenConfig{ + CommonName: fmt.Sprintf("%s-gateway", commonName), + CertKeyPath: config.Gateway.Certs.Key, + CertPath: config.Gateway.Certs.Cert, + CertCAPath: config.Gateway.Certs.CA, + }) + if err != nil { + return errors.Wrapf(err, "failed to generate gateway certs (%s)", serviceName) + } + log.Info().Str("service", serviceName).Msg("gateway certs configured") } } - log.Info().Str("service", serviceName).Msg("certs configured") } return nil } @@ -243,28 +239,3 @@ func FileExists(path string) (bool, error) { return false, errors.Wrapf(err, "failed to stat file '%s'", path) } } - -func setDefaultCerts(cfg *Config) error { - for srvName, config := range cfg.APIConfig.Services { - if config.GRPC.ListenAddress == "" { - return errors.New(fmt.Sprintf("%s must have a grpc listen address specified", srvName)) - } - - if config.GRPC.Certs.TLSCertPath == "" { - fmt.Fprintf(os.Stderr, "SKIPPED setDefaultCerts for %q\n", srvName) - continue - } - - if config.GRPC.Certs.TLSCACertPath == "" || config.GRPC.Certs.TLSCertPath == "" || config.GRPC.Certs.TLSKeyPath == "" { - config.GRPC.Certs.TLSKeyPath = filepath.Join(DefaultTLSGenDir, fmt.Sprintf("%s_grpc.key", srvName)) - config.GRPC.Certs.TLSCertPath = filepath.Join(DefaultTLSGenDir, fmt.Sprintf("%s_grpc.crt", srvName)) - config.GRPC.Certs.TLSCACertPath = filepath.Join(DefaultTLSGenDir, fmt.Sprintf("%s_grpc-ca.crt", srvName)) - } - if config.Gateway.Certs.TLSCACertPath == "" || config.Gateway.Certs.TLSCertPath == "" || config.Gateway.Certs.TLSKeyPath == "" { - config.Gateway.Certs.TLSKeyPath = filepath.Join(DefaultTLSGenDir, fmt.Sprintf("%s_gateway.key", srvName)) - config.Gateway.Certs.TLSCertPath = filepath.Join(DefaultTLSGenDir, fmt.Sprintf("%s_gateway.crt", srvName)) - config.Gateway.Certs.TLSCACertPath = filepath.Join(DefaultTLSGenDir, fmt.Sprintf("%s_gateway-ca.crt", srvName)) - } - } - return nil -} diff --git a/pkg/cc/config/helper.go b/pkg/cc/config/helper.go index 9dd766d7..2a74f62b 100644 --- a/pkg/cc/config/helper.go +++ b/pkg/cc/config/helper.go @@ -36,11 +36,3 @@ func (c *currentConfig) Services() ([]string, error) { return k }), nil } - -func (c *currentConfig) HealthService() (string, error) { - if c.err != nil { - return "", c.err - } - - return c.Configuration.APIConfig.Health.ListenAddress, nil -} diff --git a/pkg/cc/config/loader.go b/pkg/cc/config/loader.go index a972df98..7cf0edc7 100644 --- a/pkg/cc/config/loader.go +++ b/pkg/cc/config/loader.go @@ -98,26 +98,26 @@ func (l *Loader) GetPaths() ([]string, error) { paths[l.Configuration.Edge.DBPath] = true } if l.Configuration.APIConfig.Health.Certificates != nil { - if l.Configuration.APIConfig.Health.Certificates.TLSCACertPath != "" { - paths[l.Configuration.APIConfig.Health.Certificates.TLSCACertPath] = true + if l.Configuration.APIConfig.Health.Certificates.CA != "" { + paths[l.Configuration.APIConfig.Health.Certificates.CA] = true } - if l.Configuration.APIConfig.Health.Certificates.TLSCertPath != "" { - paths[l.Configuration.APIConfig.Health.Certificates.TLSCertPath] = true + if l.Configuration.APIConfig.Health.Certificates.Cert != "" { + paths[l.Configuration.APIConfig.Health.Certificates.Cert] = true } - if l.Configuration.APIConfig.Health.Certificates.TLSKeyPath != "" { - paths[l.Configuration.APIConfig.Health.Certificates.TLSKeyPath] = true + if l.Configuration.APIConfig.Health.Certificates.Key != "" { + paths[l.Configuration.APIConfig.Health.Certificates.Key] = true } } if l.Configuration.APIConfig.Metrics.Certificates != nil { - if l.Configuration.APIConfig.Metrics.Certificates.TLSCACertPath != "" { - paths[l.Configuration.APIConfig.Metrics.Certificates.TLSCACertPath] = true + if l.Configuration.APIConfig.Metrics.Certificates.CA != "" { + paths[l.Configuration.APIConfig.Metrics.Certificates.CA] = true } - if l.Configuration.APIConfig.Metrics.Certificates.TLSCertPath != "" { - paths[l.Configuration.APIConfig.Metrics.Certificates.TLSCertPath] = true + if l.Configuration.APIConfig.Metrics.Certificates.Cert != "" { + paths[l.Configuration.APIConfig.Metrics.Certificates.Cert] = true } - if l.Configuration.APIConfig.Metrics.Certificates.TLSKeyPath != "" { - paths[l.Configuration.APIConfig.Metrics.Certificates.TLSKeyPath] = true + if l.Configuration.APIConfig.Metrics.Certificates.Key != "" { + paths[l.Configuration.APIConfig.Metrics.Certificates.Key] = true } } @@ -231,23 +231,23 @@ func getPortFromAddress(address string) (string, error) { func getUniqueServiceCertPaths(services map[string]*builder.API) []string { paths := make(map[string]bool) for _, service := range services { - if service.GRPC.Certs.TLSCACertPath != "" { - paths[service.GRPC.Certs.TLSCACertPath] = true + if service.GRPC.Certs.CA != "" { + paths[service.GRPC.Certs.CA] = true } - if service.GRPC.Certs.TLSCertPath != "" { - paths[service.GRPC.Certs.TLSCertPath] = true + if service.GRPC.Certs.Cert != "" { + paths[service.GRPC.Certs.Cert] = true } - if service.GRPC.Certs.TLSKeyPath != "" { - paths[service.GRPC.Certs.TLSKeyPath] = true + if service.GRPC.Certs.Key != "" { + paths[service.GRPC.Certs.Key] = true } - if service.Gateway.Certs.TLSCACertPath != "" { - paths[service.Gateway.Certs.TLSCACertPath] = true + if service.Gateway.Certs.CA != "" { + paths[service.Gateway.Certs.CA] = true } - if service.Gateway.Certs.TLSCertPath != "" { - paths[service.Gateway.Certs.TLSCertPath] = true + if service.Gateway.Certs.Cert != "" { + paths[service.Gateway.Certs.Cert] = true } - if service.Gateway.Certs.TLSKeyPath != "" { - paths[service.Gateway.Certs.TLSKeyPath] = true + if service.Gateway.Certs.Key != "" { + paths[service.Gateway.Certs.Key] = true } } var pathList []string @@ -278,7 +278,6 @@ func getDecisionLogPaths(decisionLogConfig DecisionLogConfig) ([]string, error) return []string{selfCfg.StoreDirectory, selfCfg.Scribe.CACertPath, selfCfg.Scribe.ClientCertPath, selfCfg.Scribe.ClientKeyPath}, nil default: return nil, nil // nop decision logger - } } diff --git a/pkg/cc/config/schema/config.json b/pkg/cc/config/schema/config.json index 2ba3ef10..0e888fce 100644 --- a/pkg/cc/config/schema/config.json +++ b/pkg/cc/config/schema/config.json @@ -320,7 +320,8 @@ "zpages": { "description": "enable zPages trace & metric endpoint", "type": "boolean", - "default": "false" + "default": "false", + "deprecated": true } } }, @@ -442,7 +443,8 @@ "http": { "description": "allow HTTP traffic", "type": "boolean", - "default": false + "default": false, + "deprecated": true }, "read_timeout": { "description": "ReadTimeout is the maximum duration for reading the entire request, including the body", diff --git a/pkg/cc/config/schema/config.yaml b/pkg/cc/config/schema/config.yaml index 1b6b3b51..0e9fbe18 100644 --- a/pkg/cc/config/schema/config.yaml +++ b/pkg/cc/config/schema/config.yaml @@ -71,7 +71,6 @@ api: tls_key_path: '${TOPAZ_CERTS_DIR}/gateway.key' tls_cert_path: '${TOPAZ_CERTS_DIR}/gateway.crt' tls_ca_cert_path: '${TOPAZ_CERTS_DIR}/gateway-ca.crt' - zpages: true services: console: diff --git a/pkg/cc/config/templates.go b/pkg/cc/config/templates.go index 41d78524..e10d783e 100644 --- a/pkg/cc/config/templates.go +++ b/pkg/cc/config/templates.go @@ -131,13 +131,13 @@ directory: # remote directory is used to resolve the identity for the authorizer. remote_directory: address: "0.0.0.0:9292" # set as default, it should be the same as the reader as we resolve the identity from the local directory service. - insecure: true tenant_id: "" api_key: "" token: "" client_cert_path: "" client_key_path: "" ca_cert_path: "" + insecure: true no_tls: false headers: @@ -166,17 +166,9 @@ auth: api: health: listen_address: "0.0.0.0:9494" - certs: - tls_key_path: '${TOPAZ_CERTS_DIR}/gateway.key' - tls_cert_path: '${TOPAZ_CERTS_DIR}/gateway.crt' - tls_ca_cert_path: '${TOPAZ_CERTS_DIR}/gateway-ca.crt' metrics: listen_address: "0.0.0.0:9696" - certs: - tls_key_path: '${TOPAZ_CERTS_DIR}/gateway.key' - tls_cert_path: '${TOPAZ_CERTS_DIR}/gateway.crt' - tls_ca_cert_path: '${TOPAZ_CERTS_DIR}/gateway-ca.crt' zpages: true services: @@ -374,42 +366,6 @@ api: tls_key_path: '${TOPAZ_CERTS_DIR}/grpc.key' tls_cert_path: '${TOPAZ_CERTS_DIR}/grpc.crt' tls_ca_cert_path: '${TOPAZ_CERTS_DIR}/grpc-ca.crt' - gateway: - listen_address: "0.0.0.0:9393" - fqdn: "" - allowed_headers: - - "Authorization" - - "Content-Type" - - "If-Match" - - "If-None-Match" - - "Depth" - allowed_methods: - - "GET" - - "POST" - - "HEAD" - - "DELETE" - - "PUT" - - "PATCH" - - "PROFIND" - - "MKCOL" - - "COPY" - - "MOVE" - allowed_origins: - - http://localhost - - http://localhost:* - - https://localhost - - https://localhost:* - - https://*.aserto.com - - https://*aserto-console.netlify.app - certs: - tls_key_path: '${TOPAZ_CERTS_DIR}/gateway.key' - tls_cert_path: '${TOPAZ_CERTS_DIR}/gateway.crt' - tls_ca_cert_path: '${TOPAZ_CERTS_DIR}/gateway-ca.crt' - http: false - read_timeout: 2s - read_header_timeout: 2s - write_timeout: 2s - idle_timeout: 30s importer: needs: @@ -421,42 +377,6 @@ api: tls_key_path: '${TOPAZ_CERTS_DIR}/grpc.key' tls_cert_path: '${TOPAZ_CERTS_DIR}/grpc.crt' tls_ca_cert_path: '${TOPAZ_CERTS_DIR}/grpc-ca.crt' - gateway: - listen_address: "0.0.0.0:9393" - fqdn: "" - allowed_headers: - - "Authorization" - - "Content-Type" - - "If-Match" - - "If-None-Match" - - "Depth" - allowed_methods: - - "GET" - - "POST" - - "HEAD" - - "DELETE" - - "PUT" - - "PATCH" - - "PROFIND" - - "MKCOL" - - "COPY" - - "MOVE" - allowed_origins: - - http://localhost - - http://localhost:* - - https://localhost - - https://localhost:* - - https://*.aserto.com - - https://*aserto-console.netlify.app - certs: - tls_key_path: '${TOPAZ_CERTS_DIR}/gateway.key' - tls_cert_path: '${TOPAZ_CERTS_DIR}/gateway.crt' - tls_ca_cert_path: '${TOPAZ_CERTS_DIR}/gateway-ca.crt' - http: false - read_timeout: 2s - read_header_timeout: 2s - write_timeout: 2s - idle_timeout: 30s authorizer: needs: diff --git a/pkg/cc/wire_gen.go b/pkg/cc/wire_gen.go index f93dd17c..5164daae 100644 --- a/pkg/cc/wire_gen.go +++ b/pkg/cc/wire_gen.go @@ -30,8 +30,8 @@ func buildCC(logOutput logger.Writer, errOutput logger.ErrWriter, configPath con if err != nil { return nil, nil, err } - // generator := certs.NewGenerator(zerologLogger) - configConfig, err := config.NewConfig(configPath, zerologLogger, overrides, nil) + generator := certs.NewGenerator(zerologLogger) + configConfig, err := config.NewConfig(configPath, zerologLogger, overrides, generator) if err != nil { return nil, nil, err } diff --git a/pkg/cli/cc/cc.go b/pkg/cli/cc/cc.go index 215ccd20..06e19f46 100644 --- a/pkg/cli/cc/cc.go +++ b/pkg/cli/cc/cc.go @@ -3,7 +3,6 @@ package cc import ( "context" "encoding/json" - "errors" "fmt" "io" "os" @@ -11,10 +10,13 @@ import ( "sync" "time" + client "github.com/aserto-dev/go-aserto" "github.com/aserto-dev/topaz/pkg/cli/cc/iostream" "github.com/aserto-dev/topaz/pkg/cli/dockerx" + "github.com/aserto-dev/topaz/pkg/version" "github.com/docker/docker/api/types" "github.com/fullstorydev/grpcurl" + "github.com/pkg/errors" "github.com/samber/lo" "google.golang.org/grpc" "google.golang.org/grpc/credentials" @@ -140,29 +142,35 @@ func (c *CommonCtx) GetRunningContainers() ([]*types.Container, error) { return topazContainers, nil } -func (c *CommonCtx) IsServing(grpcAddress string) bool { +func (c *CommonCtx) IsServing(cfg *client.Config) (bool, error) { if c.Config.Defaults.NoCheck { - return true + return true, nil } - tlsConf, err := grpcurl.ClientTLSConfig(true, "", "", "") - if err != nil { - return false - } + var creds credentials.TransportCredentials - creds := credentials.NewTLS(tlsConf) + if cfg.Insecure { + tlsConf, err := grpcurl.ClientTLSConfig(cfg.Insecure, "", "", "") + if err != nil { + return false, errors.Wrap(err, "failed to create TLS config") + } + creds = credentials.NewTLS(tlsConf) + } opts := []grpc.DialOption{ - grpc.WithUserAgent("topaz/dev-build (no version set)"), - grpc.WithTransportCredentials(insecure.NewCredentials()), + grpc.WithUserAgent(version.UserAgent()), } + opts = append(opts, grpc.WithTransportCredentials(insecure.NewCredentials())) + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() - _, err = grpcurl.BlockingDial(ctx, "tcp", grpcAddress, creds, opts...) + if _, err := grpcurl.BlockingDial(ctx, "tcp", cfg.Address, creds, opts...); err != nil { + return false, err + } - return err == nil + return true, nil } func (c *CommonCtx) SaveContextConfig(configurationFile string) error { diff --git a/pkg/cli/cc/client.go b/pkg/cli/cc/client.go index 5024000c..1996b02a 100644 --- a/pkg/cli/cc/client.go +++ b/pkg/cli/cc/client.go @@ -14,6 +14,7 @@ const ( defaultAuthorizerToken = "" defaultTenantID = "" defaultInsecure = false + defaultPlaintext = false defaultNoCheck = false defaultNoColor = false ) @@ -76,6 +77,15 @@ func Insecure() bool { return defaultInsecure } +func Plaintext() bool { + if plaintext := os.Getenv("TOPAZ_PLAINTEXT"); plaintext != "" { + if b, err := strconv.ParseBool(plaintext); err == nil { + return b + } + } + return defaultPlaintext +} + func NoCheck() bool { if noCheck := os.Getenv("TOPAZ_NO_CHECK"); noCheck != "" { if b, err := strconv.ParseBool(noCheck); err == nil { diff --git a/pkg/cli/cc/health.go b/pkg/cli/cc/health.go index 6fbb406f..e1908576 100644 --- a/pkg/cli/cc/health.go +++ b/pkg/cli/cc/health.go @@ -13,11 +13,8 @@ const rpcTimeout = time.Second * 30 // ServiceHealthStatus adopted from grpc-health-probe cli implementation // https://github.com/grpc-ecosystem/grpc-health-probe/blob/master/main.go. -func ServiceHealthStatus(ctx context.Context, addr, service string) (bool, error) { - conn, err := client.NewConnection( - client.WithAddr(addr), - client.WithInsecure(true), - ) +func ServiceHealthStatus(ctx context.Context, cfg *client.Config, service string) (bool, error) { + conn, err := cfg.Connect() if err != nil { return false, err } diff --git a/pkg/cli/certs/generator.go b/pkg/cli/certs/generator.go index 7588558a..a79e6ce5 100644 --- a/pkg/cli/certs/generator.go +++ b/pkg/cli/certs/generator.go @@ -58,12 +58,11 @@ func generate(c *cc.CommonCtx, dnsNames []string, certPaths ...*CertPaths) error for _, certPaths := range certPaths { if err := generator.MakeDevCert(&certs.CertGenConfig{ - CommonName: certPaths.Name, - CertKeyPath: certPaths.Key, - CertPath: certPaths.Cert, - CACertPath: certPaths.CA, - DefaultTLSGenDir: certPaths.Dir, - DNSNames: dnsNames, + CommonName: certPaths.Name, + CertKeyPath: certPaths.Key, + CertPath: certPaths.Cert, + CertCAPath: certPaths.CA, + DNSNames: dnsNames, }); err != nil { return errors.Wrap(err, "failed to create dev certs") } diff --git a/pkg/cli/clients/authorizer/client.go b/pkg/cli/clients/authorizer/client.go index c44a177c..77543ae6 100644 --- a/pkg/cli/clients/authorizer/client.go +++ b/pkg/cli/clients/authorizer/client.go @@ -12,15 +12,17 @@ import ( az2 "github.com/aserto-dev/go-authorizer/aserto/authorizer/v2" "github.com/aserto-dev/topaz/pkg/cli/cc" + "github.com/aserto-dev/topaz/pkg/version" ) type Config struct { - Host string `flag:"host" short:"H" default:"${authorizer_svc}" env:"TOPAZ_AUTHORIZER_SVC" help:"authorizer service address"` - APIKey string `flag:"api-key" short:"k" default:"${authorizer_key}" env:"TOPAZ_AUTHORIZER_KEY" help:"authorizer API key"` - Token string `flag:"token" default:"${authorizer_token}" env:"TOPAZ_AUTHORIZER_TOKEN" help:"authorizer OAuth2.0 token" hidden:""` - Insecure bool `flag:"insecure" short:"i" default:"${insecure}" env:"TOPAZ_INSECURE" help:"skip TLS verification"` - TenantID string `flag:"tenant-id" help:"" default:"${tenant_id}" env:"ASERTO_TENANT_ID" ` - Headers map[string]string `flag:"headers" env:"TOPAZ_AUTHORIZER_HEADERS" help:"additional headers to send to the authorizer service"` + Host string `flag:"host" short:"H" default:"${authorizer_svc}" env:"TOPAZ_AUTHORIZER_SVC" help:"authorizer service address"` + APIKey string `flag:"api-key" short:"k" default:"${authorizer_key}" env:"TOPAZ_AUTHORIZER_KEY" help:"authorizer API key"` + Token string `flag:"token" default:"${authorizer_token}" env:"TOPAZ_AUTHORIZER_TOKEN" help:"authorizer OAuth2.0 token" hidden:""` + Insecure bool `flag:"insecure" short:"i" default:"${insecure}" env:"TOPAZ_INSECURE" help:"skip TLS verification"` + Plaintext bool `flag:"plaintext" short:"P" default:"${plaintext}" env:"TOPAZ_PLAINTEXT" help:"use plain-text HTTP/2 (no TLS)"` + TenantID string `flag:"tenant-id" help:"" default:"${tenant_id}" env:"ASERTO_TENANT_ID" ` + Headers map[string]string `flag:"headers" env:"TOPAZ_AUTHORIZER_HEADERS" help:"additional headers to send to the authorizer service"` } type Client struct { @@ -53,24 +55,24 @@ func (cfg *Config) Connect(ctx context.Context) (*grpc.ClientConn, error) { return nil, err } - return cfg.connect() + return cfg.ClientConfig().Connect() } func (cfg *Config) validate(ctx context.Context) error { - tlsConf, err := grpcurl.ClientTLSConfig(cfg.Insecure, "", "", "") - if err != nil { - return errors.Wrap(err, "failed to create TLS config") + var creds credentials.TransportCredentials + if cfg.Insecure { + tlsConf, err := grpcurl.ClientTLSConfig(cfg.Insecure, "", "", "") + if err != nil { + return errors.Wrap(err, "failed to create TLS config") + } + creds = credentials.NewTLS(tlsConf) } - creds := credentials.NewTLS(tlsConf) - opts := []grpc.DialOption{ - grpc.WithUserAgent("topaz/dev-build (no version set)"), // TODO: verify user-agent value + grpc.WithUserAgent(version.UserAgent()), } - if cfg.Insecure { - opts = append(opts, grpc.WithTransportCredentials(insecure.NewCredentials())) - } + opts = append(opts, grpc.WithTransportCredentials(insecure.NewCredentials())) if _, err := grpcurl.BlockingDial(ctx, "tcp", cfg.Host, creds, opts...); err != nil { return err @@ -79,15 +81,14 @@ func (cfg *Config) validate(ctx context.Context) error { return nil } -func (cfg *Config) connect() (*grpc.ClientConn, error) { - clientCfg := &client.Config{ +func (cfg *Config) ClientConfig() *client.Config { + return &client.Config{ Address: cfg.Host, Insecure: cfg.Insecure, + NoTLS: cfg.Plaintext, APIKey: cfg.APIKey, Token: cfg.Token, TenantID: cfg.TenantID, Headers: cfg.Headers, } - - return clientCfg.Connect() } diff --git a/pkg/cli/clients/directory/backup.go b/pkg/cli/clients/directory/backup.go index 4c582f0f..1a4c96db 100644 --- a/pkg/cli/clients/directory/backup.go +++ b/pkg/cli/clients/directory/backup.go @@ -4,6 +4,7 @@ import ( "archive/tar" "compress/gzip" "context" + "errors" "fmt" "io" "os" @@ -115,7 +116,7 @@ func (c *Client) createBackupFiles(stream dse3.Exporter_ExportClient, dirPath st for { msg, err := stream.Recv() - if err == io.EOF { + if errors.Is(err, io.EOF) { break } if err != nil { diff --git a/pkg/cli/clients/directory/client.go b/pkg/cli/clients/directory/client.go index 2dcecee8..7105eeef 100644 --- a/pkg/cli/clients/directory/client.go +++ b/pkg/cli/clients/directory/client.go @@ -11,6 +11,7 @@ import ( dsr3 "github.com/aserto-dev/go-directory/aserto/directory/reader/v3" dsw3 "github.com/aserto-dev/go-directory/aserto/directory/writer/v3" "github.com/aserto-dev/topaz/pkg/cli/cc" + "github.com/aserto-dev/topaz/pkg/version" "github.com/fullstorydev/grpcurl" "github.com/pkg/errors" @@ -20,12 +21,13 @@ import ( ) type Config struct { - Host string `flag:"host" short:"H" default:"${directory_svc}" env:"TOPAZ_DIRECTORY_SVC" help:"directory service address"` - APIKey string `flag:"api-key" short:"k" default:"${directory_key}" env:"TOPAZ_DIRECTORY_KEY" help:"directory API key"` - Token string `flag:"token" default:"${directory_token}" env:"TOPAZ_DIRECTORY_TOKEN" help:"directory OAuth2.0 token" hidden:""` - Insecure bool `flag:"insecure" short:"i" default:"${insecure}" env:"TOPAZ_INSECURE" help:"skip TLS verification"` - TenantID string `flag:"tenant-id" help:"" default:"${tenant_id}" env:"ASERTO_TENANT_ID" ` - Headers map[string]string `flag:"headers" env:"TOPAZ_DIRECTORY_HEADERS" help:"additional headers to send to the directory service"` + Host string `flag:"host" short:"H" default:"${directory_svc}" env:"TOPAZ_DIRECTORY_SVC" help:"directory service address"` + APIKey string `flag:"api-key" short:"k" default:"${directory_key}" env:"TOPAZ_DIRECTORY_KEY" help:"directory API key"` + Token string `flag:"token" default:"${directory_token}" env:"TOPAZ_DIRECTORY_TOKEN" help:"directory OAuth2.0 token" hidden:""` + Insecure bool `flag:"insecure" short:"i" default:"${insecure}" env:"TOPAZ_INSECURE" help:"skip TLS verification"` + Plaintext bool `flag:"plaintext" short:"P" default:"${plaintext}" env:"TOPAZ_PLAINTEXT" help:"use plain-text HTTP/2 (no TLS)"` + TenantID string `flag:"tenant-id" help:"" default:"${tenant_id}" env:"ASERTO_TENANT_ID" ` + Headers map[string]string `flag:"headers" env:"TOPAZ_DIRECTORY_HEADERS" help:"additional headers to send to the directory service"` } type Client struct { @@ -64,28 +66,28 @@ func (cfg *Config) Connect(ctx context.Context) (*grpc.ClientConn, error) { return nil, errors.Errorf("no host specified") } - if err := cfg.validate(ctx); err != nil { + if err := cfg.Validate(ctx); err != nil { return nil, err } - return cfg.connect() + return cfg.ClientConfig().Connect() } -func (cfg *Config) validate(ctx context.Context) error { - tlsConf, err := grpcurl.ClientTLSConfig(cfg.Insecure, "", "", "") - if err != nil { - return errors.Wrap(err, "failed to create TLS config") +func (cfg *Config) Validate(ctx context.Context) error { + var creds credentials.TransportCredentials + if cfg.Insecure { + tlsConf, err := grpcurl.ClientTLSConfig(cfg.Insecure, "", "", "") + if err != nil { + return errors.Wrap(err, "failed to create TLS config") + } + creds = credentials.NewTLS(tlsConf) } - creds := credentials.NewTLS(tlsConf) - opts := []grpc.DialOption{ - grpc.WithUserAgent("topaz/dev-build (no version set)"), // TODO: verify user-agent value + grpc.WithUserAgent(version.UserAgent()), } - if cfg.Insecure { - opts = append(opts, grpc.WithTransportCredentials(insecure.NewCredentials())) - } + opts = append(opts, grpc.WithTransportCredentials(insecure.NewCredentials())) if _, err := grpcurl.BlockingDial(ctx, "tcp", cfg.Host, creds, opts...); err != nil { return err @@ -94,15 +96,14 @@ func (cfg *Config) validate(ctx context.Context) error { return nil } -func (cfg *Config) connect() (*grpc.ClientConn, error) { - clientCfg := &client.Config{ +func (cfg *Config) ClientConfig() *client.Config { + return &client.Config{ Address: cfg.Host, Insecure: cfg.Insecure, + NoTLS: cfg.Plaintext, APIKey: cfg.APIKey, Token: cfg.Token, TenantID: cfg.TenantID, Headers: cfg.Headers, } - - return clientCfg.Connect() } diff --git a/pkg/cli/clients/directory/export.go b/pkg/cli/clients/directory/export.go index f705bead..520f0638 100644 --- a/pkg/cli/clients/directory/export.go +++ b/pkg/cli/clients/directory/export.go @@ -2,6 +2,7 @@ package directory import ( "context" + "errors" "fmt" "io" "os" @@ -39,7 +40,7 @@ func (c *Client) Export(ctx context.Context, objectsFile, relationsFile string) for { msg, err := stream.Recv() - if err == io.EOF { + if errors.Is(err, io.EOF) { break } if err != nil { diff --git a/pkg/cli/cmd/cli.go b/pkg/cli/cmd/cli.go index 46919bbf..05d64e88 100644 --- a/pkg/cli/cmd/cli.go +++ b/pkg/cli/cmd/cli.go @@ -123,5 +123,5 @@ func (cmd *DeleteManifestCmd) Run(c *cc.CommonCtx) error { } func movedErr(oldCmd, newCmd string) error { - return errors.Errorf("The command %q was moved to %q.", oldCmd, newCmd) + return errors.Errorf("the command %q was moved to %q.", oldCmd, newCmd) } diff --git a/pkg/cli/cmd/directory/backup.go b/pkg/cli/cmd/directory/backup.go index 5519c0c3..91922256 100644 --- a/pkg/cli/cmd/directory/backup.go +++ b/pkg/cli/cmd/directory/backup.go @@ -18,7 +18,7 @@ type BackupCmd struct { const defaultFileName = "backup.tar.gz" func (cmd *BackupCmd) Run(c *cc.CommonCtx) error { - if !c.IsServing(cmd.Host) { + if ok, _ := c.IsServing(cmd.ClientConfig()); !ok { return errors.Wrap(cc.ErrNotServing, cmd.Host) } diff --git a/pkg/cli/cmd/directory/exporter.go b/pkg/cli/cmd/directory/exporter.go index a8743838..779ba2f4 100644 --- a/pkg/cli/cmd/directory/exporter.go +++ b/pkg/cli/cmd/directory/exporter.go @@ -14,7 +14,7 @@ type ExportCmd struct { } func (cmd *ExportCmd) Run(c *cc.CommonCtx) error { - if !c.IsServing(cmd.Host) { + if ok, _ := c.IsServing(cmd.ClientConfig()); !ok { return errors.Wrap(cc.ErrNotServing, cmd.Host) } c.Con().Info().Msg(">>> exporting data to %s", cmd.Directory) diff --git a/pkg/cli/cmd/directory/importer.go b/pkg/cli/cmd/directory/importer.go index 50b771e8..12543e0b 100644 --- a/pkg/cli/cmd/directory/importer.go +++ b/pkg/cli/cmd/directory/importer.go @@ -15,7 +15,7 @@ type ImportCmd struct { } func (cmd *ImportCmd) Run(c *cc.CommonCtx) error { - if !c.IsServing(cmd.Host) { + if ok, _ := c.IsServing(cmd.ClientConfig()); !ok { return errors.Wrap(cc.ErrNotServing, cmd.Host) } c.Con().Info().Msg(">>> importing data from %s", cmd.Directory) diff --git a/pkg/cli/cmd/directory/manifest.go b/pkg/cli/cmd/directory/manifest.go index e29c2da6..fecf5d44 100644 --- a/pkg/cli/cmd/directory/manifest.go +++ b/pkg/cli/cmd/directory/manifest.go @@ -29,7 +29,7 @@ type DeleteManifestCmd struct { } func (cmd *GetManifestCmd) Run(c *cc.CommonCtx) error { - if !c.IsServing(cmd.Host) { + if ok, _ := c.IsServing(cmd.ClientConfig()); !ok { return errors.Wrap(cc.ErrNotServing, cmd.Host) } dsClient, err := dsc.NewClient(c, &cmd.Config) @@ -62,7 +62,7 @@ func (cmd *GetManifestCmd) Run(c *cc.CommonCtx) error { } func (cmd *SetManifestCmd) Run(c *cc.CommonCtx) error { - if !c.IsServing(cmd.Host) { + if ok, _ := c.IsServing(cmd.ClientConfig()); !ok { return errors.Wrap(cc.ErrNotServing, cmd.Host) } dsClient, err := dsc.NewClient(c, &cmd.Config) @@ -84,7 +84,7 @@ func (cmd *SetManifestCmd) Run(c *cc.CommonCtx) error { } func (cmd *DeleteManifestCmd) Run(c *cc.CommonCtx) error { - if !c.IsServing(cmd.Host) { + if ok, _ := c.IsServing(cmd.ClientConfig()); !ok { return errors.Wrap(cc.ErrNotServing, cmd.Host) } dsClient, err := dsc.NewClient(c, &cmd.Config) diff --git a/pkg/cli/cmd/directory/restore.go b/pkg/cli/cmd/directory/restore.go index c62b5f12..9322181a 100644 --- a/pkg/cli/cmd/directory/restore.go +++ b/pkg/cli/cmd/directory/restore.go @@ -15,7 +15,7 @@ type RestoreCmd struct { } func (cmd *RestoreCmd) Run(c *cc.CommonCtx) error { - if !c.IsServing(cmd.Host) { + if ok, _ := c.IsServing(cmd.ClientConfig()); !ok { return errors.Wrap(cc.ErrNotServing, cmd.Host) } diff --git a/pkg/cli/cmd/directory/stats.go b/pkg/cli/cmd/directory/stats.go index b7d5191a..675a696c 100644 --- a/pkg/cli/cmd/directory/stats.go +++ b/pkg/cli/cmd/directory/stats.go @@ -39,7 +39,7 @@ func (cmd *StatsCmd) Run(c *cc.CommonCtx) error { for { msg, err := stream.Recv() - if err == io.EOF { + if errors.Is(err, io.EOF) { break } if err != nil { diff --git a/pkg/cli/cmd/templates/install.go b/pkg/cli/cmd/templates/install.go index 0697c547..a559845a 100644 --- a/pkg/cli/cmd/templates/install.go +++ b/pkg/cli/cmd/templates/install.go @@ -7,6 +7,7 @@ import ( "path" "path/filepath" + client "github.com/aserto-dev/go-aserto" "github.com/aserto-dev/topaz/pkg/cc/config" "github.com/aserto-dev/topaz/pkg/cli/cc" azc "github.com/aserto-dev/topaz/pkg/cli/clients/authorizer" @@ -113,12 +114,22 @@ func (cmd *InstallTemplateCmd) installTemplate(c *cc.CommonCtx, tmpl *template) fmt.Fprintln(c.StdErr(), "This configuration file still uses TOPAZ_DIR environment variable.") fmt.Fprintln(c.StdErr(), "Please change to using the new TOPAZ_DB_DIR and TOPAZ_CERTS_DIR environment variables.") } - addr, _ := cfg.HealthService() - if health, err := cc.ServiceHealthStatus(c.Context, addr, "model"); err != nil { + + healthCfg := &client.Config{ + Address: cfg.Configuration.APIConfig.Health.ListenAddress, + ClientCertPath: "", // cfg.Configuration.APIConfig.Health.Certificates.TLSCertPath, + ClientKeyPath: "", // cfg.Configuration.APIConfig.Health.Certificates.TLSKeyPath, + CACertPath: "", // cfg.Configuration.APIConfig.Health.Certificates.TLSCACertPath, + Insecure: false, + NoTLS: true, + } + + if health, err := cc.ServiceHealthStatus(c.Context, healthCfg, "model"); err != nil { return errors.Wrapf(err, "unable to check health status") } else if !health { return errors.Errorf("gRPC endpoint not SERVING") } + if model, ok := cfg.Configuration.APIConfig.Services["model"]; !ok { return errors.Errorf("model service not configured") } else { diff --git a/pkg/cli/cmd/topaz/stop.go b/pkg/cli/cmd/topaz/stop.go index 3babef76..dc01247d 100644 --- a/pkg/cli/cmd/topaz/stop.go +++ b/pkg/cli/cmd/topaz/stop.go @@ -34,6 +34,7 @@ func (cmd *StopCmd) Run(c *cc.CommonCtx) error { if err := dc.Stop(container.Names[0]); err != nil { return err } + if cmd.Wait { var ports []string for _, port := range container.Ports { diff --git a/pkg/cli/fflag/fflag_test.go b/pkg/cli/fflag/fflag_test.go index 4c88b015..988fd457 100644 --- a/pkg/cli/fflag/fflag_test.go +++ b/pkg/cli/fflag/fflag_test.go @@ -10,33 +10,33 @@ import ( func TestFFlag(t *testing.T) { { ff := fflag.FFlag(0) - assert.Equal(t, false, ff.IsSet(fflag.Editor)) - assert.Equal(t, false, ff.IsSet(fflag.Prompter)) + assert.False(t, ff.IsSet(fflag.Editor)) + assert.False(t, ff.IsSet(fflag.Prompter)) } { ff := fflag.FFlag(1) - assert.Equal(t, true, ff.IsSet(fflag.Editor)) - assert.Equal(t, false, ff.IsSet(fflag.Prompter)) + assert.True(t, ff.IsSet(fflag.Editor)) + assert.False(t, ff.IsSet(fflag.Prompter)) } { ff := fflag.FFlag(2) - assert.Equal(t, false, ff.IsSet(fflag.Editor)) - assert.Equal(t, true, ff.IsSet(fflag.Prompter)) + assert.False(t, ff.IsSet(fflag.Editor)) + assert.True(t, ff.IsSet(fflag.Prompter)) } { ff := fflag.FFlag(3) - assert.Equal(t, true, ff.IsSet(fflag.Editor)) - assert.Equal(t, true, ff.IsSet(fflag.Prompter)) + assert.True(t, ff.IsSet(fflag.Editor)) + assert.True(t, ff.IsSet(fflag.Prompter)) } { ff := fflag.FFlag(31) - assert.Equal(t, true, ff.IsSet(fflag.Editor)) - assert.Equal(t, true, ff.IsSet(fflag.Prompter)) + assert.True(t, ff.IsSet(fflag.Editor)) + assert.True(t, ff.IsSet(fflag.Prompter)) } { ff := fflag.FFlag(63) - assert.Equal(t, true, ff.IsSet(fflag.Editor)) - assert.Equal(t, true, ff.IsSet(fflag.Prompter)) + assert.True(t, ff.IsSet(fflag.Editor)) + assert.True(t, ff.IsSet(fflag.Prompter)) } } diff --git a/pkg/cli/xdg/go.mod b/pkg/cli/xdg/go.mod deleted file mode 100644 index 7c7c24b2..00000000 --- a/pkg/cli/xdg/go.mod +++ /dev/null @@ -1,14 +0,0 @@ -module github.com/adrg/xdg - -go 1.19 - -require ( - github.com/stretchr/testify v1.9.0 - golang.org/x/sys v0.22.0 -) - -require ( - github.com/davecgh/go-spew v1.1.1 // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect -) diff --git a/pkg/cli/xdg/go.sum b/pkg/cli/xdg/go.sum deleted file mode 100644 index 63f91f26..00000000 --- a/pkg/cli/xdg/go.sum +++ /dev/null @@ -1,12 +0,0 @@ -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -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= -golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= -golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/pkg/debug/debug.go b/pkg/debug/debug.go index ab4ab390..c1023b30 100644 --- a/pkg/debug/debug.go +++ b/pkg/debug/debug.go @@ -81,18 +81,16 @@ func (srv *Server) Stop() { return } - if srv != nil { - var shutdown context.CancelFunc - ctx := context.Background() - if srv.cfg.ShutdownTimeout > 0 { - shutdownTimeout := time.Duration(srv.cfg.ShutdownTimeout) * time.Second - ctx, shutdown = context.WithTimeout(ctx, shutdownTimeout) - defer shutdown() - } + var shutdown context.CancelFunc + ctx := context.Background() + if srv.cfg.ShutdownTimeout > 0 { + shutdownTimeout := time.Duration(srv.cfg.ShutdownTimeout) * time.Second + ctx, shutdown = context.WithTimeout(ctx, shutdownTimeout) + defer shutdown() + } - err := srv.server.Shutdown(ctx) - if err != nil { - srv.logger.Info().Err(err).Str("state", "shutdown").Msg("debug-service") - } + err := srv.server.Shutdown(ctx) + if err != nil { + srv.logger.Info().Err(err).Str("state", "shutdown").Msg("debug-service") } } diff --git a/pkg/version/version.go b/pkg/version/version.go index c8e0b3ed..fcc28259 100644 --- a/pkg/version/version.go +++ b/pkg/version/version.go @@ -4,6 +4,8 @@ import ( "fmt" "runtime" "time" + + "github.com/aserto-dev/topaz/pkg/cli/x" ) var ( @@ -50,3 +52,7 @@ func (vi Info) String() string { vi.Date, ) } + +func UserAgent() string { + return fmt.Sprintf("%s/%s", x.AppName, GetInfo().Version) +} diff --git a/scripts/container-tag.sh b/scripts/container-tag.sh new file mode 100755 index 00000000..74626905 --- /dev/null +++ b/scripts/container-tag.sh @@ -0,0 +1,2 @@ +#!/usr/bin/env bash +echo "CONTAINER_TAG=0.0.0-test-$(git rev-parse --short HEAD)-$(uname -m)" diff --git a/scripts/templates-test.sh b/scripts/templates-test.sh new file mode 100755 index 00000000..3e3f1dd8 --- /dev/null +++ b/scripts/templates-test.sh @@ -0,0 +1,59 @@ +#!/usr/bin/env bash + +templates=("assets/api-auth.json" "assets/gdrive.json" "assets/github.json" "assets/multi-tenant.json" "assets/peoplefinder.json" "assets/simple-rbac.json" "assets/slack.json" "assets/todo.json") + +ttopaz="./dist/topaz_$(go env GOOS)_$(go env GOARCH)/topaz" + +eval "$ttopaz version" + +for tmpl in ${templates[@]}; do + echo $tmpl + # cat $tmpl | jq . + + args="directory delete manifest --force --plaintext" + ./dist/topaz_$(go env GOOS)_$(go env GOARCH)/topaz $args + + manifest=$(cat $tmpl | jq -r '.assets.manifest') + echo $manifest + args="directory set manifest $PWD/assets/$manifest --plaintext" + echo $args + ./dist/topaz_$(go env GOOS)_$(go env GOARCH)/topaz $args + + idp_data=$(cat $tmpl | jq -r '.assets.idp_data[0]') + idp_data_dir=$(dirname "$idp_data" ) + echo $idp_data_dir + args="directory import --directory $PWD/assets/$idp_data_dir --plaintext" + echo $args + ./dist/topaz_$(go env GOOS)_$(go env GOARCH)/topaz $args + + domain_data=$(cat $tmpl | jq -r '.assets.domain_data[0]') + domain_data_dir=$(dirname "$domain_data" ) + echo $domain_data_dir + if [[ -z "$domain_data" ]]; then + echo "NO DOMAIN DATA" + else + args="directory import --directory $PWD/assets/$domain_data_dir --plaintext" + echo $args + ./dist/topaz_$(go env GOOS)_$(go env GOARCH)/topaz $args + fi + + assertion=$(cat $tmpl | jq -r '.assets.assertions[0]') + echo $assertion + if [[ -z "$assertion" ]]; then + echo "NO ASSERTIONS" + else + args="directory test exec $PWD/assets/$assertion --summary --plaintext" + echo $args + ./dist/topaz_$(go env GOOS)_$(go env GOARCH)/topaz $args + fi + + decisions=$(cat $tmpl | jq -r '.assets.assertions[1]') + echo $decisions + if [[ -z "$decisions" ]]; then + echo "NO DECISIONS" + else + args="authorizer test exec $PWD/assets/$decisions --summary --plaintext --host localhost:9292" + echo $args + ./dist/topaz_$(go env GOOS)_$(go env GOARCH)/topaz $args + fi +done From 57526a701ee721d9e39aeaf1127f495742cfa4a5 Mon Sep 17 00:00:00 2001 From: Gert Drapers <1533850+gertd@users.noreply.github.com> Date: Fri, 15 Nov 2024 12:15:00 -0800 Subject: [PATCH 3/6] address review feedback --- go.mod | 15 ++-- go.work | 1 - internal/pkg/service/builder/go.mod | 46 ---------- internal/pkg/service/builder/go.sum | 90 ------------------- internal/pkg/service/builder/service.go | 2 +- .../pkg/service/builder/service_factory.go | 24 ++--- .../pkg/service/builder/service_manager.go | 19 ++-- internal/pkg/service/builder/tls.go | 11 --- pkg/app/authorizer.go | 4 +- pkg/app/console.go | 6 +- pkg/app/edgedir.go | 2 +- pkg/app/topaz.go | 2 +- pkg/app/topaz/wire.go | 2 +- pkg/app/topaz/wire_gen.go | 2 +- pkg/cc/config/config.go | 6 +- pkg/cc/config/helper.go | 2 +- pkg/cc/config/loader.go | 2 +- 17 files changed, 36 insertions(+), 200 deletions(-) delete mode 100644 internal/pkg/service/builder/go.mod delete mode 100644 internal/pkg/service/builder/go.sum delete mode 100644 internal/pkg/service/builder/tls.go diff --git a/go.mod b/go.mod index 91ab0603..9df0749a 100644 --- a/go.mod +++ b/go.mod @@ -2,8 +2,6 @@ module github.com/aserto-dev/topaz go 1.23.3 -replace github.com/aserto-dev/service-host => ./internal/pkg/service/builder - replace github.com/adrg/xdg => ./internal/pkg/xdg require ( @@ -27,16 +25,18 @@ require ( github.com/aserto-dev/openapi-directory v0.31.2 github.com/aserto-dev/runtime v0.69.1 github.com/aserto-dev/self-decision-logger v0.0.8 - github.com/aserto-dev/service-host v0.0.0-00010101000000-000000000000 github.com/cli/browser v1.3.0 github.com/docker/docker v27.1.1+incompatible github.com/docker/go-connections v0.5.0 github.com/fatih/color v1.18.0 github.com/fullstorydev/grpcurl v1.9.1 github.com/gdamore/tcell/v2 v2.7.4 + github.com/go-http-utils/headers v0.0.0-20181008091004-fed159eddc2a github.com/google/uuid v1.6.0 github.com/google/wire v0.6.0 github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 + github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.0.1 + github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 github.com/grpc-ecosystem/grpc-gateway/v2 v2.23.0 github.com/itchyny/gojq v0.12.16 github.com/lestrrat-go/jwx/v2 v2.1.2 @@ -50,9 +50,12 @@ require ( github.com/open-policy-agent/opa v0.69.0 github.com/opencontainers/image-spec v1.1.0 github.com/pkg/errors v0.9.1 + github.com/prometheus/client_golang v1.20.5 github.com/rivo/tview v0.0.0-20241103174730-c76f7879f592 + github.com/rs/cors v1.11.1 github.com/rs/zerolog v1.33.0 github.com/samber/lo v1.47.0 + github.com/slok/go-http-metrics v0.13.0 github.com/spf13/cobra v1.8.1 github.com/spf13/viper v1.19.0 github.com/stretchr/testify v1.9.0 @@ -98,7 +101,6 @@ require ( github.com/felixge/httpsnoop v1.0.4 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/gdamore/encoding v1.0.1 // indirect - github.com/go-http-utils/headers v0.0.0-20181008091004-fed159eddc2a // indirect github.com/go-ini/ini v1.67.0 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect @@ -110,9 +112,7 @@ require ( github.com/google/cel-go v0.21.0 // indirect github.com/google/subcommands v1.2.0 // indirect github.com/gorilla/mux v1.8.1 // indirect - github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.0.1 // indirect github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.1.0 // indirect - github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/hashicorp/hcl v1.0.0 // indirect @@ -153,20 +153,17 @@ require ( github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect - github.com/prometheus/client_golang v1.20.5 // indirect github.com/prometheus/client_model v0.6.1 // indirect github.com/prometheus/common v0.60.1 // indirect github.com/prometheus/procfs v0.15.1 // indirect github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 // indirect github.com/rivo/uniseg v0.4.7 // indirect - github.com/rs/cors v1.11.1 // indirect github.com/sagikazarmark/locafero v0.4.0 // indirect github.com/sagikazarmark/slog-shim v0.1.0 // indirect github.com/segmentio/asm v1.2.0 // indirect github.com/shirou/gopsutil/v3 v3.23.12 // indirect github.com/shoenig/go-m1cpu v0.1.6 // indirect github.com/sirupsen/logrus v1.9.3 // indirect - github.com/slok/go-http-metrics v0.13.0 // indirect github.com/sourcegraph/conc v0.3.0 // indirect github.com/spf13/afero v1.11.0 // indirect github.com/spf13/cast v1.6.0 // indirect diff --git a/go.work b/go.work index 40b3e113..54aa0bcf 100644 --- a/go.work +++ b/go.work @@ -2,6 +2,5 @@ go 1.23.3 use ( . - ./internal/pkg/service/builder ./internal/pkg/xdg ) diff --git a/internal/pkg/service/builder/go.mod b/internal/pkg/service/builder/go.mod deleted file mode 100644 index 68e5e75c..00000000 --- a/internal/pkg/service/builder/go.mod +++ /dev/null @@ -1,46 +0,0 @@ -module github.com/aserto-dev/service-host - -go 1.23 - -toolchain go1.23.2 - -require ( - github.com/aserto-dev/go-aserto v0.33.1 - github.com/go-http-utils/headers v0.0.0-20181008091004-fed159eddc2a - github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.0.1 - github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 - github.com/grpc-ecosystem/grpc-gateway/v2 v2.23.0 - github.com/pkg/errors v0.9.1 - github.com/prometheus/client_golang v1.20.5 - github.com/rs/cors v1.11.1 - github.com/rs/zerolog v1.33.0 - github.com/slok/go-http-metrics v0.13.0 - golang.org/x/sync v0.9.0 - google.golang.org/grpc v1.67.1 - google.golang.org/protobuf v1.35.1 -) - -require ( - github.com/aserto-dev/header v0.0.8 // indirect - github.com/beorn7/perks v1.0.1 // indirect - github.com/cespare/xxhash/v2 v2.3.0 // indirect - github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect - github.com/google/uuid v1.6.0 // indirect - github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.1.0 // indirect - github.com/hashicorp/errwrap v1.0.0 // indirect - github.com/hashicorp/go-multierror v1.1.1 // indirect - github.com/klauspost/compress v1.17.11 // indirect - github.com/mattn/go-colorable v0.1.13 // indirect - github.com/mattn/go-isatty v0.0.20 // indirect - github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect - github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect - github.com/prometheus/client_model v0.6.1 // indirect - github.com/prometheus/common v0.60.1 // indirect - github.com/prometheus/procfs v0.15.1 // indirect - github.com/samber/lo v1.47.0 // indirect - golang.org/x/net v0.31.0 // indirect - golang.org/x/sys v0.27.0 // indirect - golang.org/x/text v0.20.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20241104194629-dd2ea8efbc28 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20241104194629-dd2ea8efbc28 // indirect -) diff --git a/internal/pkg/service/builder/go.sum b/internal/pkg/service/builder/go.sum deleted file mode 100644 index 6a12b235..00000000 --- a/internal/pkg/service/builder/go.sum +++ /dev/null @@ -1,90 +0,0 @@ -github.com/aserto-dev/go-aserto v0.33.1 h1:oFTlatM7+mq2grD/ut+ff0v7CjCV6YenKd6XWJbonbE= -github.com/aserto-dev/go-aserto v0.33.1/go.mod h1:t2qs6Q7pXlMkKh5MhbssKDbGb62f36Q6Bj3QDXww31s= -github.com/aserto-dev/header v0.0.8 h1:T052WblWFZ/5Mg3MphHylE3sZobdIQpdj5cP3sPMhL8= -github.com/aserto-dev/header v0.0.8/go.mod h1:wmWm+omABTWf6QRRmw9yOdvgTstk/vYDqIA1duR8Pus= -github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= -github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= -github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= -github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= -github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= -github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/go-http-utils/headers v0.0.0-20181008091004-fed159eddc2a h1:v6zMvHuY9yue4+QkG/HQ/W67wvtQmWJ4SDo9aK/GIno= -github.com/go-http-utils/headers v0.0.0-20181008091004-fed159eddc2a/go.mod h1:I79BieaU4fxrw4LMXby6q5OS9XnoR9UIKLOzDFjUmuw= -github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= -github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= -github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= -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/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= -github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.0.1 h1:qnpSQwGEnkcRpTqNOIR6bJbR0gAorgP9CSALpRcKoAA= -github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.0.1/go.mod h1:lXGCsh6c22WGtjr+qGHj1otzZpV/1kwTMAqkwZsnWRU= -github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.1.0 h1:pRhl55Yx1eC7BZ1N+BBWwnKaMyD8uC+34TLdndZMAKk= -github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.1.0/go.mod h1:XKMd7iuf/RGPSMJ/U4HP0zS2Z9Fh8Ps9a+6X26m/tmI= -github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho= -github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.23.0 h1:ad0vkEBuk23VJzZR9nkLVG0YAoN9coASF1GusYX6AlU= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.23.0/go.mod h1:igFoXX2ELCW06bol23DWPB5BEWfZISOzSP5K2sbLea0= -github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= -github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= -github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= -github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= -github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc= -github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= -github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= -github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= -github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= -github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= -github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -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/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= -github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= -github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= -github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_golang v1.20.5 h1:cxppBPuYhUnsO6yo/aoRol4L7q7UFfdm+bR9r+8l63Y= -github.com/prometheus/client_golang v1.20.5/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= -github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= -github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= -github.com/prometheus/common v0.60.1 h1:FUas6GcOw66yB/73KC+BOZoFJmbo/1pojoILArPAaSc= -github.com/prometheus/common v0.60.1/go.mod h1:h0LYf1R1deLSKtD4Vdg8gy4RuOvENW2J/h19V5NADQw= -github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= -github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= -github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA= -github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= -github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= -github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8= -github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= -github.com/samber/lo v1.47.0 h1:z7RynLwP5nbyRscyvcD043DWYoOcYRv3mV8lBeqOCLc= -github.com/samber/lo v1.47.0/go.mod h1:RmDH9Ct32Qy3gduHQuKJ3gW1fMHAnE/fAzQuf6He5cU= -github.com/slok/go-http-metrics v0.13.0 h1:lQDyJJx9wKhmbliyUsZ2l6peGnXRHjsjoqPt5VYzcP8= -github.com/slok/go-http-metrics v0.13.0/go.mod h1:HIr7t/HbN2sJaunvnt9wKP9xoBBVZFo1/KiHU3b0w+4= -github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= -github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= -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= -golang.org/x/net v0.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo= -golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM= -golang.org/x/sync v0.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ= -golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/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= -golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s= -golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug= -golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4= -google.golang.org/genproto/googleapis/api v0.0.0-20241104194629-dd2ea8efbc28 h1:M0KvPgPmDZHPlbRbaNU1APr28TvwvvdUPlSv7PUvy8g= -google.golang.org/genproto/googleapis/api v0.0.0-20241104194629-dd2ea8efbc28/go.mod h1:dguCy7UOdZhTvLzDyt15+rOrawrpM4q7DD9dQ1P11P4= -google.golang.org/genproto/googleapis/rpc v0.0.0-20241104194629-dd2ea8efbc28 h1:XVhgTWWV3kGQlwJHR3upFWZeTsei6Oks1apkZSeonIE= -google.golang.org/genproto/googleapis/rpc v0.0.0-20241104194629-dd2ea8efbc28/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI= -google.golang.org/grpc v1.67.1 h1:zWnc1Vrcno+lHZCOofnIMvycFcc0QRGIzm9dhnDX68E= -google.golang.org/grpc v1.67.1/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA= -google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= -google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/internal/pkg/service/builder/service.go b/internal/pkg/service/builder/service.go index 3beabe80..f749486d 100644 --- a/internal/pkg/service/builder/service.go +++ b/internal/pkg/service/builder/service.go @@ -21,7 +21,7 @@ type GRPCRegistrations func(server *grpc.Server) type HandlerRegistrations func(ctx context.Context, mux *runtime.ServeMux, grpcEndpoint string, opts []grpc.DialOption) error -type Server struct { +type Service struct { Config *API Server *grpc.Server Listener net.Listener diff --git a/internal/pkg/service/builder/service_factory.go b/internal/pkg/service/builder/service_factory.go index 594389e3..e0c44d80 100644 --- a/internal/pkg/service/builder/service_factory.go +++ b/internal/pkg/service/builder/service_factory.go @@ -8,6 +8,7 @@ import ( "strconv" "github.com/aserto-dev/go-aserto" + "github.com/samber/lo" "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" "github.com/pkg/errors" @@ -56,7 +57,7 @@ func (f *ServiceFactory) CreateService( grpcOpts *GRPCOptions, gatewayOpts *GatewayOptions, cleanup ...func(), -) (*Server, error) { +) (*Service, error) { grpcServer, err := prepareGrpcServer(&config.GRPC.Certs, grpcOpts.ServerOptions) if err != nil { return nil, err @@ -77,7 +78,7 @@ func (f *ServiceFactory) CreateService( } } - return &Server{ + return &Service{ Config: config, Server: grpcServer, Listener: listener, @@ -109,7 +110,7 @@ func (f *ServiceFactory) prepareGateway(config *API, gatewayOpts *GatewayOptions runtimeMux := f.gatewayMux(config.Gateway.AllowedHeaders, gatewayOpts.ErrorHandler) opts := []grpc.DialOption{} - if TLS(&config.GRPC.Certs) { + if config.GRPC.Certs.HasCert() { tlsCreds, err := config.GRPC.Certs.ClientCredentials(true) if err != nil { return Gateway{}, errors.Wrapf(err, "failed to get TLS credentials") @@ -141,9 +142,7 @@ func (f *ServiceFactory) prepareGateway(config *API, gatewayOpts *GatewayOptions IdleTimeout: config.Gateway.IdleTimeout, } - if NoTLS(&config.Gateway.Certs) { - config.Gateway.HTTP = true - } + config.Gateway.HTTP = !config.Gateway.Certs.HasCert() if !config.Gateway.HTTP { tlsServerConfig, err := config.Gateway.Certs.ServerConfig() @@ -161,7 +160,7 @@ func (f *ServiceFactory) prepareGateway(config *API, gatewayOpts *GatewayOptions func (f *ServiceFactory) gatewayMux(allowedHeaders []string, errorHandler runtime.ErrorHandlerFunc) *runtime.ServeMux { opts := []runtime.ServeMuxOption{ runtime.WithIncomingHeaderMatcher(func(key string) (string, bool) { - if contains(allowedHeaders, key) { + if lo.Contains(allowedHeaders, key) { return key, true } return runtime.DefaultHeaderMatcher(key) @@ -236,7 +235,7 @@ func httpResponseModifier(ctx context.Context, w http.ResponseWriter, p proto.Me // prepareGrpcServer provides a new grpc server with the provided grpc.ServerOptions using the provided certificates. func prepareGrpcServer(certCfg *aserto.TLSConfig, opts []grpc.ServerOption) (*grpc.Server, error) { // NoTLS path. - if NoTLS(certCfg) { + if !certCfg.HasCert() { opts = append(opts, grpc.Creds(insecure.NewCredentials())) return grpc.NewServer(opts...), nil } @@ -266,15 +265,6 @@ func fieldsMaskHandler(h http.Handler) http.Handler { }) } -func contains[T comparable](slice []T, item T) bool { - for i := range slice { - if slice[i] == item { - return true - } - } - return false -} - type key int var pathPatternKey key diff --git a/internal/pkg/service/builder/service_manager.go b/internal/pkg/service/builder/service_manager.go index 94e58c6a..9bdece1e 100644 --- a/internal/pkg/service/builder/service_manager.go +++ b/internal/pkg/service/builder/service_manager.go @@ -24,7 +24,7 @@ type ServiceManager struct { logger *zerolog.Logger errGroup *errgroup.Group - Servers map[string]*Server + Servers map[string]*Service DependencyMap map[string][]string HealthServer *Health MetricServer *http.Server @@ -42,7 +42,7 @@ func NewServiceManager(logger *zerolog.Logger) *ServiceManager { return &ServiceManager{ Context: ctx, logger: &serviceLogger, - Servers: make(map[string]*Server), + Servers: make(map[string]*Service), DependencyMap: make(map[string][]string), errGroup: errGroup, shutdownTimeout: 30, @@ -54,7 +54,7 @@ func (s *ServiceManager) WithShutdownTimeout(seconds int) *ServiceManager { return s } -func (s *ServiceManager) AddGRPCServer(server *Server) error { +func (s *ServiceManager) AddGRPCServer(server *Service) error { s.Servers[server.Config.GRPC.ListenAddress] = server return nil } @@ -105,7 +105,7 @@ func (s *ServiceManager) SetupMetricsServer(address string, certCfg *aserto.TLSC s.logger.Info().Msgf("Starting %s metric server", address) - if NoTLS(certCfg) { + if !certCfg.HasCert() { s.errGroup.Go(metric.ListenAndServe) } else { s.errGroup.Go(func() error { @@ -163,14 +163,13 @@ func (s *ServiceManager) StartServers(ctx context.Context) error { if httpServer.Server != nil { s.errGroup.Go(func() error { s.logger.Info().Msgf("Starting %s gateway server", httpServer.Server.Addr) - if NoTLS(httpServer.Certs) { - err := httpServer.Server.ListenAndServe() + if httpServer.Certs.HasCert() { + err := httpServer.Server.ListenAndServeTLS(httpServer.Certs.Cert, httpServer.Certs.Key) if err != nil { return err } - } - if TLS(httpServer.Certs) { - err := httpServer.Server.ListenAndServeTLS(httpServer.Certs.Cert, httpServer.Certs.Key) + } else { + err := httpServer.Server.ListenAndServe() if err != nil { return err } @@ -190,7 +189,7 @@ func (s *ServiceManager) logDetails(address string, element interface{}) { ref := reflect.ValueOf(element).Elem() typeOfT := ref.Type() - for i := 0; i < ref.NumField(); i++ { + for i := range ref.NumField() { f := ref.Field(i) s.logger.Debug().Str("address", address).Msgf("%s = %v\n", typeOfT.Field(i).Name, f.Interface()) } diff --git a/internal/pkg/service/builder/tls.go b/internal/pkg/service/builder/tls.go deleted file mode 100644 index bab95780..00000000 --- a/internal/pkg/service/builder/tls.go +++ /dev/null @@ -1,11 +0,0 @@ -package builder - -import "github.com/aserto-dev/go-aserto" - -func NoTLS(cfg *aserto.TLSConfig) bool { - return (cfg == nil || cfg.CA == "" || cfg.Key == "" || cfg.Cert == "") -} - -func TLS(cfg *aserto.TLSConfig) bool { - return !NoTLS(cfg) -} diff --git a/pkg/app/authorizer.go b/pkg/app/authorizer.go index 7c19c4d0..3a38fb7f 100644 --- a/pkg/app/authorizer.go +++ b/pkg/app/authorizer.go @@ -7,7 +7,7 @@ import ( authz "github.com/aserto-dev/go-authorizer/aserto/authorizer/v2" azOpenAPI "github.com/aserto-dev/openapi-authorizer/publish/authorizer" - builder "github.com/aserto-dev/service-host" + builder "github.com/aserto-dev/topaz/internal/pkg/service/builder" "github.com/aserto-dev/topaz/pkg/app/impl" "github.com/aserto-dev/topaz/pkg/cc/config" "github.com/aserto-dev/topaz/pkg/rapidoc" @@ -32,7 +32,7 @@ const ( ) func NewAuthorizer(ctx context.Context, cfg *builder.API, commonConfig *config.Common, authorizerOpts []grpc.ServerOption, logger *zerolog.Logger) (ServiceTypes, error) { - if builder.TLS(&cfg.GRPC.Certs) { + if cfg.GRPC.Certs.HasCert() { tlsCreds, err := cfg.GRPC.Certs.ServerCredentials() if err != nil { return nil, errors.Wrap(err, "failed to calculate tls config") diff --git a/pkg/app/console.go b/pkg/app/console.go index 760d062b..cf46b05c 100644 --- a/pkg/app/console.go +++ b/pkg/app/console.go @@ -6,7 +6,7 @@ import ( "net/http" "strings" - builder "github.com/aserto-dev/service-host" + builder "github.com/aserto-dev/topaz/internal/pkg/service/builder" "github.com/aserto-dev/topaz/pkg/app/handlers" "github.com/aserto-dev/topaz/pkg/cc/config" "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" @@ -111,9 +111,7 @@ func getGatewayAddress(serviceConfig *builder.API) string { } addr := serviceAddress(serviceConfig.Gateway.ListenAddress) - if builder.NoTLS(&serviceConfig.Gateway.Certs) { - serviceConfig.Gateway.HTTP = true - } + serviceConfig.Gateway.HTTP = !serviceConfig.Gateway.Certs.HasCert() if serviceConfig.Gateway.HTTP { return fmt.Sprintf("http://%s", addr) diff --git a/pkg/app/edgedir.go b/pkg/app/edgedir.go index 6aef7c6e..7110862b 100644 --- a/pkg/app/edgedir.go +++ b/pkg/app/edgedir.go @@ -13,7 +13,7 @@ import ( dsm3stream "github.com/aserto-dev/go-directory/pkg/gateway/model/v3" "github.com/aserto-dev/go-edge-ds/pkg/directory" dsOpenAPI "github.com/aserto-dev/openapi-directory/publish/directory" - builder "github.com/aserto-dev/service-host" + builder "github.com/aserto-dev/topaz/internal/pkg/service/builder" "github.com/aserto-dev/topaz/pkg/rapidoc" "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" diff --git a/pkg/app/topaz.go b/pkg/app/topaz.go index 0b7d3a42..cb67c1e8 100644 --- a/pkg/app/topaz.go +++ b/pkg/app/topaz.go @@ -10,10 +10,10 @@ import ( eds "github.com/aserto-dev/go-edge-ds" console "github.com/aserto-dev/go-topaz-ui" "github.com/aserto-dev/self-decision-logger/logger/self" - builder "github.com/aserto-dev/service-host" decisionlog "github.com/aserto-dev/topaz/decision_log" "github.com/aserto-dev/topaz/decision_log/logger/file" "github.com/aserto-dev/topaz/decision_log/logger/nop" + builder "github.com/aserto-dev/topaz/internal/pkg/service/builder" "github.com/aserto-dev/topaz/pkg/app/auth" "github.com/aserto-dev/topaz/pkg/app/handlers" "github.com/aserto-dev/topaz/pkg/app/middlewares" diff --git a/pkg/app/topaz/wire.go b/pkg/app/topaz/wire.go index 9781f60a..4d8aa6c4 100644 --- a/pkg/app/topaz/wire.go +++ b/pkg/app/topaz/wire.go @@ -8,7 +8,7 @@ import ( "google.golang.org/grpc" "github.com/aserto-dev/logger" - builder "github.com/aserto-dev/service-host" + builder "github.com/aserto-dev/topaz/internal/pkg/service/builder" "github.com/aserto-dev/topaz/pkg/app" "github.com/aserto-dev/topaz/pkg/cc" "github.com/aserto-dev/topaz/pkg/cc/config" diff --git a/pkg/app/topaz/wire_gen.go b/pkg/app/topaz/wire_gen.go index 35dc0901..fc4e5b8a 100644 --- a/pkg/app/topaz/wire_gen.go +++ b/pkg/app/topaz/wire_gen.go @@ -8,7 +8,7 @@ package topaz import ( "github.com/aserto-dev/logger" - "github.com/aserto-dev/service-host" + "github.com/aserto-dev/topaz/internal/pkg/service/builder" "github.com/aserto-dev/topaz/pkg/app" "github.com/aserto-dev/topaz/pkg/cc" "github.com/aserto-dev/topaz/pkg/cc/config" diff --git a/pkg/cc/config/config.go b/pkg/cc/config/config.go index 25d0aa11..1e10775e 100644 --- a/pkg/cc/config/config.go +++ b/pkg/cc/config/config.go @@ -10,7 +10,7 @@ import ( "github.com/aserto-dev/go-edge-ds/pkg/directory" "github.com/aserto-dev/logger" "github.com/aserto-dev/runtime" - builder "github.com/aserto-dev/service-host" + builder "github.com/aserto-dev/topaz/internal/pkg/service/builder" "github.com/aserto-dev/topaz/pkg/debug" "github.com/pkg/errors" "github.com/rs/zerolog" @@ -200,7 +200,7 @@ func (c *Config) setupCerts(log *zerolog.Logger, certsGenerator *certs.Generator } if len(existingFiles) == 0 { - if builder.TLS(&config.GRPC.Certs) { + if config.GRPC.Certs.HasCert() && config.GRPC.Certs.HasCA() { err := certsGenerator.MakeDevCert(&certs.CertGenConfig{ CommonName: fmt.Sprintf("%s-grpc", commonName), CertKeyPath: config.GRPC.Certs.Key, @@ -213,7 +213,7 @@ func (c *Config) setupCerts(log *zerolog.Logger, certsGenerator *certs.Generator log.Info().Str("service", serviceName).Msg("gRPC certs configured") } - if builder.TLS(&config.Gateway.Certs) { + if config.Gateway.Certs.HasCert() && config.Gateway.Certs.HasCA() { err := certsGenerator.MakeDevCert(&certs.CertGenConfig{ CommonName: fmt.Sprintf("%s-gateway", commonName), CertKeyPath: config.Gateway.Certs.Key, diff --git a/pkg/cc/config/helper.go b/pkg/cc/config/helper.go index 2a74f62b..6f461242 100644 --- a/pkg/cc/config/helper.go +++ b/pkg/cc/config/helper.go @@ -1,7 +1,7 @@ package config import ( - builder "github.com/aserto-dev/service-host" + builder "github.com/aserto-dev/topaz/internal/pkg/service/builder" "github.com/samber/lo" ) diff --git a/pkg/cc/config/loader.go b/pkg/cc/config/loader.go index 7cf0edc7..5aea3084 100644 --- a/pkg/cc/config/loader.go +++ b/pkg/cc/config/loader.go @@ -9,7 +9,7 @@ import ( "strings" "github.com/aserto-dev/self-decision-logger/logger/self" - builder "github.com/aserto-dev/service-host" + builder "github.com/aserto-dev/topaz/internal/pkg/service/builder" "github.com/aserto-dev/topaz/pkg/cli/cc" "github.com/mitchellh/mapstructure" "github.com/spf13/viper" From a5c3fb14907af27693d3562948768ceb0bd3bafa Mon Sep 17 00:00:00 2001 From: Gert Drapers <1533850+gertd@users.noreply.github.com> Date: Fri, 15 Nov 2024 17:33:19 -0800 Subject: [PATCH 4/6] use same test timeout as in ci.yaml --- makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/makefile b/makefile index fc6e16bf..30d75766 100644 --- a/makefile +++ b/makefile @@ -90,7 +90,7 @@ container-tag: .PHONY: test test: test-snapshot @echo -e "$(ATTN_COLOR)==> test github.com/aserto-dev/topaz/pkg/app/tests/$@/... $(NO_COLOR)" - @${EXT_BIN_DIR}/gotestsum --format short-verbose -- -count=1 -parallel=1 -v -coverprofile=cover.out -coverpkg=./... ./... + @${EXT_BIN_DIR}/gotestsum --format short-verbose -- -count=1 -timeout 120s -parallel=1 -v -coverprofile=cover.out -coverpkg=./... ./... .PHONY: write-version write-version: From ca5f4db53c964dc0b86bd6cca4fb36227ac0e11e Mon Sep 17 00:00:00 2001 From: Gert Drapers <1533850+gertd@users.noreply.github.com> Date: Fri, 15 Nov 2024 17:34:14 -0800 Subject: [PATCH 5/6] remove rapidoc UI --- pkg/app/authorizer.go | 17 ----------------- pkg/app/edgedir.go | 17 ----------------- 2 files changed, 34 deletions(-) diff --git a/pkg/app/authorizer.go b/pkg/app/authorizer.go index 3a38fb7f..9acbcf1a 100644 --- a/pkg/app/authorizer.go +++ b/pkg/app/authorizer.go @@ -10,7 +10,6 @@ import ( builder "github.com/aserto-dev/topaz/internal/pkg/service/builder" "github.com/aserto-dev/topaz/pkg/app/impl" "github.com/aserto-dev/topaz/pkg/cc/config" - "github.com/aserto-dev/topaz/pkg/rapidoc" "github.com/aserto-dev/topaz/resolvers" "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" @@ -74,9 +73,6 @@ func (e *Authorizer) GetGatewayRegistration(services ...string) builder.HandlerR if err := mux.HandlePath(http.MethodGet, authorizerOpenAPISpec, azOpenAPIHandler); err != nil { return err } - if err := mux.HandlePath(http.MethodGet, authorizerOpenAPIDocs, azOpenAPIDocsHandler()); err != nil { - return err - } } return nil @@ -89,7 +85,6 @@ func (e *Authorizer) Cleanups() []func() { const ( authorizerOpenAPISpec string = "/authorizer/openapi.json" - authorizerOpenAPIDocs string = "/authorizer/docs" ) func azOpenAPIHandler(w http.ResponseWriter, r *http.Request, pathParams map[string]string) { @@ -102,15 +97,3 @@ func azOpenAPIHandler(w http.ResponseWriter, r *http.Request, pathParams map[str w.Header().Add("Content-Length", strconv.FormatInt(int64(len(buf)), 10)) _, _ = w.Write(buf) } - -func azOpenAPIDocsHandler() runtime.HandlerFunc { - h := rapidoc.Handler(&rapidoc.Opts{ - Path: authorizerOpenAPIDocs, - SpecURL: authorizerOpenAPISpec, - Title: "Aserto Directory HTTP API", - }, nil) - - return func(w http.ResponseWriter, r *http.Request, pathParams map[string]string) { - h.ServeHTTP(w, r) - } -} diff --git a/pkg/app/edgedir.go b/pkg/app/edgedir.go index 7110862b..6a06d983 100644 --- a/pkg/app/edgedir.go +++ b/pkg/app/edgedir.go @@ -14,7 +14,6 @@ import ( "github.com/aserto-dev/go-edge-ds/pkg/directory" dsOpenAPI "github.com/aserto-dev/openapi-directory/publish/directory" builder "github.com/aserto-dev/topaz/internal/pkg/service/builder" - "github.com/aserto-dev/topaz/pkg/rapidoc" "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" "github.com/samber/lo" @@ -110,9 +109,6 @@ func (e *EdgeDir) GetGatewayRegistration(services ...string) builder.HandlerRegi if err := mux.HandlePath(http.MethodGet, directoryOpenAPISpec, dsOpenAPIHandler); err != nil { return err } - if err := mux.HandlePath(http.MethodGet, directoryOpenAPIDocs, dsOpenAPIDocsHandler()); err != nil { - return err - } } return nil @@ -121,7 +117,6 @@ func (e *EdgeDir) GetGatewayRegistration(services ...string) builder.HandlerRegi const ( directoryOpenAPISpec string = "/directory/openapi.json" - directoryOpenAPIDocs string = "/directory/docs" ) func dsOpenAPIHandler(w http.ResponseWriter, r *http.Request, pathParams map[string]string) { @@ -134,15 +129,3 @@ func dsOpenAPIHandler(w http.ResponseWriter, r *http.Request, pathParams map[str w.Header().Add("Content-Length", strconv.FormatInt(int64(len(buf)), 10)) _, _ = w.Write(buf) } - -func dsOpenAPIDocsHandler() runtime.HandlerFunc { - h := rapidoc.Handler(&rapidoc.Opts{ - Path: directoryOpenAPIDocs, - SpecURL: directoryOpenAPISpec, - Title: "Aserto Directory HTTP API", - }, nil) - - return func(w http.ResponseWriter, r *http.Request, pathParams map[string]string) { - h.ServeHTTP(w, r) - } -} From 74dc059dac47b329eea1e7a567dbbfb58689a03d Mon Sep 17 00:00:00 2001 From: Gert Drapers <1533850+gertd@users.noreply.github.com> Date: Fri, 15 Nov 2024 17:35:18 -0800 Subject: [PATCH 6/6] do not register gateway instances for importer & exporter services --- pkg/app/edgedir.go | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/pkg/app/edgedir.go b/pkg/app/edgedir.go index 6a06d983..35928b0f 100644 --- a/pkg/app/edgedir.go +++ b/pkg/app/edgedir.go @@ -92,18 +92,6 @@ func (e *EdgeDir) GetGatewayRegistration(services ...string) builder.HandlerRegi return err } } - if lo.Contains(services, importerService) { - err := dsi3.RegisterImporterHandlerFromEndpoint(ctx, mux, grpcEndpoint, opts) - if err != nil { - return err - } - } - if lo.Contains(services, exporterService) { - err := dse3.RegisterExporterHandlerFromEndpoint(ctx, mux, grpcEndpoint, opts) - if err != nil { - return err - } - } if len(services) > 0 { if err := mux.HandlePath(http.MethodGet, directoryOpenAPISpec, dsOpenAPIHandler); err != nil {