diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..8d7488b --- /dev/null +++ b/.dockerignore @@ -0,0 +1,4 @@ +.idea +.vscode +x.json +zz.yaml \ No newline at end of file diff --git a/.github/workflows/push.yaml b/.github/workflows/push.yaml index c4fc7f7..72e4411 100644 --- a/.github/workflows/push.yaml +++ b/.github/workflows/push.yaml @@ -18,6 +18,7 @@ on: - LICENSE env: IMAGE_NAME: 'router' + ADAPTOR_IMAGE_NAME: 'router-adaptor' jobs: build: @@ -84,3 +85,17 @@ jobs: ghcr.io/datasance/${{ env.IMAGE_NAME }}:latest ghcr.io/datasance/${{ env.IMAGE_NAME }}:main + - name: Build and Push Router Adaptor to ghcr + uses: docker/build-push-action@v5 + id: build_push_adaptor_ghcr + with: + context: . + file: Dockerfile.adaptor + platforms: linux/amd64, linux/arm64 + push: true + tags: | + ghcr.io/datasance/${{ env.ADAPTOR_IMAGE_NAME }}:${{ steps.tags.outputs.VERSION }} + ghcr.io/datasance/${{ env.ADAPTOR_IMAGE_NAME }}:latest + ghcr.io/datasance/${{ env.ADAPTOR_IMAGE_NAME }}:main + + diff --git a/.gitignore b/.gitignore index d48c759..8d7488b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ .idea -.vscode \ No newline at end of file +.vscode +x.json +zz.yaml \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index baa8e0c..6c44c5a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1.20.14-alpine AS go-builder +FROM golang:1.21-alpine AS go-builder ARG TARGETOS ARG TARGETARCH @@ -9,13 +9,12 @@ COPY . /go/src/github.com/datasance/router RUN GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build -o bin/router FROM registry.access.redhat.com/ubi9/ubi-minimal:latest AS tz -RUN microdnf reinstall -y tzdata - +RUN microdnf install -y tzdata && microdnf reinstall -y tzdata FROM quay.io/skupper/skupper-router:main COPY LICENSE /licenses/LICENSE COPY --from=go-builder /go/src/github.com/datasance/router/bin/router /home/skrouterd/bin/router -COPY scripts/launch.sh /home/skrouterd/bin/launch.sh +# COPY scripts/launch.sh /home/skrouterd/bin/launch.sh COPY --from=tz /usr/share/zoneinfo /usr/share/zoneinfo CMD ["/home/skrouterd/bin/router"] diff --git a/Dockerfile.adaptor b/Dockerfile.adaptor new file mode 100644 index 0000000..c10ba5c --- /dev/null +++ b/Dockerfile.adaptor @@ -0,0 +1,3 @@ +FROM quay.io/skupper/kube-adaptor:2.0.1 + +COPY LICENSE /licenses/LICENSE diff --git a/go.mod b/go.mod index 3de5a11..459d9e7 100644 --- a/go.mod +++ b/go.mod @@ -1,5 +1,40 @@ module github.com/datasance/router -go 1.16 +go 1.21 -require github.com/datasance/iofog-go-sdk/v3 v3.4.13 +require ( + github.com/datasance/iofog-go-sdk/v3 v3.4.13 + github.com/interconnectedcloud/go-amqp v0.12.6-0.20200506124159-f51e540008b5 + gotest.tools/v3 v3.5.2 + k8s.io/api v0.26.0 + k8s.io/utils v0.0.0-20250321185631-1f6e0b77f77e +) + +require ( + github.com/Azure/azure-sdk-for-go v68.0.0+incompatible // indirect + github.com/Azure/go-autorest/autorest v0.11.30 // indirect + github.com/Azure/go-autorest/autorest/adal v0.9.24 // indirect + github.com/Azure/go-autorest/autorest/to v0.4.1 // indirect + github.com/Azure/go-autorest/autorest/validation v0.3.2 // indirect + github.com/eapache/channels v1.1.0 // indirect + github.com/eapache/queue v1.1.0 // indirect + github.com/fortytw2/leaktest v1.3.0 // indirect + github.com/go-logr/logr v1.2.3 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/google/go-cmp v0.5.9 // indirect + github.com/google/gofuzz v1.1.0 // indirect + github.com/gorilla/websocket v1.4.2 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/stretchr/testify v1.8.2 // indirect + golang.org/x/net v0.10.0 // indirect + golang.org/x/text v0.14.0 // indirect + gopkg.in/inf.v0 v0.9.1 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect + k8s.io/apimachinery v0.26.0 // indirect + k8s.io/klog/v2 v2.80.1 // indirect + sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 // indirect + sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect +) diff --git a/go.sum b/go.sum index 4662f98..ed6b957 100644 --- a/go.sum +++ b/go.sum @@ -1,1009 +1,167 @@ -cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= -cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= -cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= -cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= -cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= -cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= -cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= -cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= -cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= -cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= -cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= -cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= -cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= -cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= -cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= -cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= -cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= -cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= -cloud.google.com/go v0.83.0/go.mod h1:Z7MJUsANfY0pYPdw0lbnivPx4/vhy/e2FEkSkF7vAVY= -cloud.google.com/go v0.84.0/go.mod h1:RazrYuxIK6Kb7YrzzhPoLmCVzl7Sup4NrbKPg8KHSUM= -cloud.google.com/go v0.87.0/go.mod h1:TpDYlFy7vuLzZMMZ+B6iRiELaY7z/gJPaqbMx6mlWcY= -cloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aDQ= -cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI= -cloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW4= -cloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc= -cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= -cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= -cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= -cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= -cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= -cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= -cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= -cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= -cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= -cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= -cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= -cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= -cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= -cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= -cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= -cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= -cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= -cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= -dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= -github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= -github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c= -github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= -github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= -github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= -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/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= -github.com/antlr/antlr4/runtime/Go/antlr v0.0.0-20220418222510-f25a4f6275ed/go.mod h1:F7bn7fEU90QkQ3tnmaTx3LTKLEDqnwWODIYppRQ5hnY= -github.com/antlr/antlr4/runtime/Go/antlr v1.4.10/go.mod h1:F7bn7fEU90QkQ3tnmaTx3LTKLEDqnwWODIYppRQ5hnY= -github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= -github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= -github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= -github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= -github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= -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/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= -github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= -github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= -github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= -github.com/cenkalti/backoff/v4 v4.1.1/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= -github.com/cenkalti/backoff/v4 v4.1.3/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= -github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/certifi/gocertifi v0.0.0-20191021191039-0944d244cd40/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA= -github.com/certifi/gocertifi v0.0.0-20200922220541-2c3bb06c6054/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA= -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/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= -github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= -github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= -github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= -github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -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/cockroachdb/datadriven v0.0.0-20200714090401-bf6692d28da5/go.mod h1:h6jFvWxBdQXxjopDMZyH2UVceIRfR84bdzbkoKrsWNo= -github.com/cockroachdb/errors v1.2.4/go.mod h1:rQD95gz6FARkaKkQXUksEje/d9a6wBJoCr5oaCLELYA= -github.com/cockroachdb/logtags v0.0.0-20190617123548-eb05cc24525f/go.mod h1:i/u985jwjWRlyHXQbwatDASoW0RMlZ/3i9yJHE2xLkI= -github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= -github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= -github.com/coreos/go-oidc v2.1.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc= -github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= -github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= -github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= -github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= -github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= -github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/Azure/azure-sdk-for-go v68.0.0+incompatible h1:fcYLmCpyNYRnvJbPerq7U0hS+6+I79yEDJBqVNcqUzU= +github.com/Azure/azure-sdk-for-go v68.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= +github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs= +github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= +github.com/Azure/go-autorest/autorest v0.11.30 h1:iaZ1RGz/ALZtN5eq4Nr1SOFSlf2E4pDI3Tcsl+dZPVE= +github.com/Azure/go-autorest/autorest v0.11.30/go.mod h1:t1kpPIOpIVX7annvothKvb0stsrXa37i7b+xpmBW8Fs= +github.com/Azure/go-autorest/autorest/adal v0.9.22/go.mod h1:XuAbAEUv2Tta//+voMI038TrJBqjKam0me7qR+L8Cmk= +github.com/Azure/go-autorest/autorest/adal v0.9.24 h1:BHZfgGsGwdkHDyZdtQRQk1WeUdW0m2WPAwuHZwUi5i4= +github.com/Azure/go-autorest/autorest/adal v0.9.24/go.mod h1:7T1+g0PYFmACYW5LlG2fcoPiPlFHjClyRGL7dRlP5c8= +github.com/Azure/go-autorest/autorest/date v0.3.0 h1:7gUk1U5M/CQbp9WoqinNzJar+8KY+LPI6wiWrP/myHw= +github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74= +github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= +github.com/Azure/go-autorest/autorest/mocks v0.4.2/go.mod h1:Vy7OitM9Kei0i1Oj+LvyAWMXJHeKH1MVlzFugfVrmyU= +github.com/Azure/go-autorest/autorest/to v0.4.1 h1:CxNHBqdzTr7rLtdrtb5CMjJcDut+WNGCVv7OmS5+lTc= +github.com/Azure/go-autorest/autorest/to v0.4.1/go.mod h1:EtaofgU4zmtvn1zT2ARsjRFdq9vXx0YWtmElwL+GZ9M= +github.com/Azure/go-autorest/autorest/validation v0.3.2 h1:myD3tcvs+Fk1bkJ1Xx7xidop4z4FWvWADiMGMXeVd2E= +github.com/Azure/go-autorest/autorest/validation v0.3.2/go.mod h1:4z7eU88lSINAB5XL8mhfPumiUdoAQo/c7qXwbsM8Zhc= +github.com/Azure/go-autorest/logger v0.2.1 h1:IG7i4p/mDa2Ce4TRyAO8IHnVhAVF3RFU+ZtXWSmf4Tg= +github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= +github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo= +github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= github.com/datasance/iofog-go-sdk/v3 v3.4.13 h1:lilrkYZmeojFAb2rRmBHweWqKrIsxjpGvKKLToB8afM= github.com/datasance/iofog-go-sdk/v3 v3.4.13/go.mod h1:X6HEP0Lr0qh8QnYKTe2O2ispUXaSoLMZKpwil5ieT0g= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +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/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= -github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= -github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= -github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/eapache/channels v1.1.0 h1:F1taHcn7/F0i8DYqKXJnyhJcVpp2kgFcNePxXtnyu4k= github.com/eapache/channels v1.1.0/go.mod h1:jMm2qB5Ubtg9zLd+inMZd2/NUvXgzmWXsDaLyQIGfH0= github.com/eapache/queue v1.1.0 h1:YOEu7KNc61ntiQlcEeUIoDTJ2o8mQznoNvUhiigpIqc= github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= -github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= -github.com/emicklei/go-restful/v3 v3.8.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= -github.com/emicklei/go-restful/v3 v3.9.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= -github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= -github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= -github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= -github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= -github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= -github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= -github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE= -github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/evanphx/json-patch v0.5.2/go.mod h1:ZWS5hhDbVDyob71nXKNL0+PWn6ToqBHMikGIFbs31qQ= -github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= -github.com/evanphx/json-patch/v5 v5.6.0/go.mod h1:G79N1coSVB93tBe7j6PhzjmR3/2VvlbKOFpnXhI9Bw4= -github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= -github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= -github.com/form3tech-oss/jwt-go v3.2.3+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= -github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= -github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= -github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= -github.com/getsentry/raven-go v0.2.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ= -github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= -github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -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-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-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= -github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= +github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= +github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0= github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/go-logr/zapr v1.2.3/go.mod h1:eIauM6P8qSvTw5o2ez6UEAfGjQKrxQTl5EoK+Qa2oG4= -github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= -github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= -github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8= -github.com/go-openapi/jsonreference v0.20.0/go.mod h1:Ag74Ico3lPc+zR+qjn4XBUmXymS4zJbYVCZmcgkasdo= -github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= -github.com/go-openapi/swag v0.19.14/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= -github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= -github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= -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.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= -github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= +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= -github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4= -github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= -github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= -github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= -github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= -github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= -github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= -github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= -github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= -github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= -github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= -github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= -github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= -github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= -github.com/google/cel-go v0.12.5/go.mod h1:Jk7ljRzLBhkmiAwBoUxB1sZSCVBAzkqPF25olK/iRDw= -github.com/google/gnostic v0.5.7-v3refs/go.mod h1:73MKFl6jIHelAJNaBGFzt3SPtZULs9dYrGFt8OiIsHQ= -github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= -github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= +github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= +github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g= github.com/google/gofuzz v1.1.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= -github.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= -github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= -github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= -github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= -github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0= -github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= -github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= -github.com/grpc-ecosystem/go-grpc-middleware v1.3.0/go.mod h1:z0ButlSOZa5vEBq9m2m2hlwIgKw+rp3sdCBRoJY+30Y= -github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= -github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= -github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0/go.mod h1:hgWBS7lorOAVIJEQMi4ZsPv9hVvWI6+ch50m39Pf2Ks= -github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= -github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= -github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= -github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= -github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= -github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= -github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= -github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= -github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= -github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= -github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= -github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= -github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= -github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= -github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= -github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= -github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= -github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= -github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= -github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= -github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= -github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= -github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8= -github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= -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/interconnectedcloud/go-amqp v0.12.6-0.20200506124159-f51e540008b5 h1:n3J6cCOpmsEXSEEFpXszM5kNsqtb7XiX3Q2bxeplWeQ= +github.com/interconnectedcloud/go-amqp v0.12.6-0.20200506124159-f51e540008b5/go.mod h1:laGtnFhRcIocSgShx6P6FqnRqQoaXGEz87QpNXSnPS8= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= -github.com/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/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= -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.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= -github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= 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/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.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= -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/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= -github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= -github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= -github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= -github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= -github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= -github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= -github.com/matttproud/golang_protobuf_extensions v1.0.2/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= -github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= -github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= -github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= -github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= -github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= -github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= -github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c= -github.com/moby/term v0.0.0-20220808134915-39b0c02b01ae/go.mod h1:E2VnQOmVuvZB6UYnnDB0qG5Nq/1tD9acaOpo6xmt0Kw= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 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 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= -github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= -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/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= -github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= -github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= -github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= -github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= -github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= -github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= -github.com/onsi/ginkgo/v2 v2.1.4/go.mod h1:um6tUpWM/cxCK3/FK8BXqEiUMUwRgSM4JXG47RKZmLU= -github.com/onsi/ginkgo/v2 v2.1.6/go.mod h1:MEH45j8TBi6u9BMogfbp0stKC5cdGjumZj5Y7AG4VIk= -github.com/onsi/ginkgo/v2 v2.3.0/go.mod h1:Eew0uilEqZmIEZr8JrvYlvOM7Rr6xzTmMV8AyFNU9d0= -github.com/onsi/ginkgo/v2 v2.4.0/go.mod h1:iHkDK1fKGcBoEHT5W7YBq4RFWaQulw+caOMkAt4OrFo= -github.com/onsi/ginkgo/v2 v2.5.0/go.mod h1:Luc4sArBICYCS8THh8v3i3i5CuSZO+RaQRaJoeNwomw= -github.com/onsi/ginkgo/v2 v2.6.0/go.mod h1:63DOGlLAH8+REH8jUGdL3YpCpu7JODesutUjdENfUAc= -github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= -github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= -github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= -github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro= -github.com/onsi/gomega v1.20.1/go.mod h1:DtrZpjmvpn2mPm4YWQa0/ALMDj9v4YxLgojwPeREyVo= -github.com/onsi/gomega v1.21.1/go.mod h1:iYAIXgPSaDHak0LCMA+AWBpIKBr8WZicMxnE8luStNc= -github.com/onsi/gomega v1.22.1/go.mod h1:x6n7VNe4hw0vkyYUM4mjIXx3JbLiPaBPNgB7PRQ1tuM= -github.com/onsi/gomega v1.23.0/go.mod h1:Z/NWtiqwBrwUt4/2loMmHL63EDLnYHmVbuBpDr2vQAg= -github.com/onsi/gomega v1.24.0/go.mod h1:Z/NWtiqwBrwUt4/2loMmHL63EDLnYHmVbuBpDr2vQAg= -github.com/onsi/gomega v1.24.1/go.mod h1:3AOiACssS3/MajrniINInwbfOOtfZvplPzuRSmvt1jM= -github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= -github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= -github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= -github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= -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= +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/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= -github.com/pquerna/cachecontrol v0.1.0/go.mod h1:NrUG3Z7Rdu85UNR3vm7SOsl1nFIeSiQnrHV5K9mBcUI= -github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= -github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= -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.11.1/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= -github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= -github.com/prometheus/client_golang v1.14.0/go.mod h1:8vpkKitgIVNcqrRBWh1C4TIUQgYNtG/XQE4E/Zae36Y= -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.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w= -github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= -github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= -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.37.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA= -github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= -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/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= -github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= -github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= -github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= -github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= -github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= -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.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= -github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= -github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= -github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= -github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= -github.com/soheilhy/cmux v0.1.5/go.mod h1:T7TcVDs9LWfQgPlPsdngu6I6QIoyIFZDDC6sNE1GqG0= -github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= -github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= -github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= -github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= -github.com/spf13/cobra v1.1.3/go.mod h1:pGADOWyqRD/YMrPZigI/zbliZ2wVD/23d+is3pSWzOo= -github.com/spf13/cobra v1.6.0/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY= -github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= -github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= -github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= -github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= -github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= -github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= -github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= -github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= +github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= -github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= -go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4= -go.etcd.io/etcd/api/v3 v3.5.5/go.mod h1:KFtNaxGDw4Yx/BA4iPPwevUTAuqcsPxzyX8PHydchN8= -go.etcd.io/etcd/client/pkg/v3 v3.5.5/go.mod h1:ggrwbk069qxpKPq8/FKkQ3Xq9y39kbFR4LnKszpRXeQ= -go.etcd.io/etcd/client/v2 v2.305.5/go.mod h1:zQjKllfqfBVyVStbt4FaosoX2iYd8fV/GRy/PbowgP4= -go.etcd.io/etcd/client/v3 v3.5.5/go.mod h1:aApjR4WGlSumpnJ2kloS75h6aHUmAyaPLjHMxpc7E7c= -go.etcd.io/etcd/pkg/v3 v3.5.5/go.mod h1:6ksYFxttiUGzC2uxyqiyOEvhAiD0tuIqSZkX3TyPdaE= -go.etcd.io/etcd/raft/v3 v3.5.5/go.mod h1:76TA48q03g1y1VpTue92jZLr9lIHKUNcYdZOOGyx8rI= -go.etcd.io/etcd/server/v3 v3.5.5/go.mod h1:rZ95vDw/jrvsbj9XpTqPrTAB9/kzchVdhRirySPkUBc= -go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= -go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= -go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= -go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.25.0/go.mod h1:E5NNboN0UqSAki0Atn9kVwaN7I+l25gGxDqBueo/74E= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.35.0/go.mod h1:h8TWwRAhQpOd0aM5nYsRD8+flnkj+526GEIVlarH7eY= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.35.0/go.mod h1:9NiG9I2aHTKkcxqCILhjtyNA1QEiCjdBACv4IvrFQ+c= -go.opentelemetry.io/otel v1.0.1/go.mod h1:OPEOD4jIT2SlZPMmwT6FqZz2C0ZNdQqiWcoK6M0SNFU= -go.opentelemetry.io/otel v1.8.0/go.mod h1:2pkj+iMj0o03Y+cW6/m8Y4WkRdYN3AvCXCnzRMp9yvM= -go.opentelemetry.io/otel v1.10.0/go.mod h1:NbvWjCthWHKBEUMpf0/v8ZRZlni86PpGFEMA9pnQSnQ= -go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.10.0/go.mod h1:78XhIg8Ht9vR4tbLNUhXsiOnE2HOuSeKAiAcoVQEpOY= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.0.1/go.mod h1:Kv8liBeVNFkkkbilbgWRpV+wWuu+H5xdOT6HAgd30iw= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.10.0/go.mod h1:Krqnjl22jUJ0HgMzw5eveuCvFDXY4nSYb4F8t5gdrag= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.0.1/go.mod h1:xOvWoTOrQjxjW61xtOmD/WKGRYb/P4NzRo3bs65U6Rk= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.10.0/go.mod h1:OfUCyyIiDvNXHWpcWgbF+MWvqPZiNa3YDEnivcnYsV0= -go.opentelemetry.io/otel/metric v0.31.0/go.mod h1:ohmwj9KTSIeBnDBm/ZwH2PSZxZzoOaG2xZeekTRzL5A= -go.opentelemetry.io/otel/sdk v1.0.1/go.mod h1:HrdXne+BiwsOHYYkBE5ysIcv2bvdZstxzmCQhxTcZkI= -go.opentelemetry.io/otel/sdk v1.10.0/go.mod h1:vO06iKzD5baltJz1zarxMCNHFpUlUiOy4s65ECtn6kE= -go.opentelemetry.io/otel/trace v1.0.1/go.mod h1:5g4i4fKLaX2BQpSBsxw8YYcgKpMMSW3x7ZTuYBr3sUk= -go.opentelemetry.io/otel/trace v1.8.0/go.mod h1:0Bt3PXY8w+3pheS3hQUt+wow8b1ojPaTBoTCh2zIFI4= -go.opentelemetry.io/otel/trace v1.10.0/go.mod h1:Sij3YYczqAdz+EhmGhE6TpTxUO5/F/AzrK+kxfGqySM= -go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= -go.opentelemetry.io/proto/otlp v0.9.0/go.mod h1:1vKfU9rv61e9EVGthD1zNvUbiwPcimSsOPU9brfSHJg= -go.opentelemetry.io/proto/otlp v0.19.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U= -go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= -go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= -go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= -go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= -go.uber.org/goleak v1.1.12/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= -go.uber.org/goleak v1.2.0/go.mod h1:XJYK+MuIchqpmGmUSAzotztawfKvYLUIgg7guXrwVUo= -go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= -go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= -go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= -go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= -go.uber.org/zap v1.19.0/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI= -go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg= -golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/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= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= -golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= -golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= -golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= -golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= -golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= -golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= -golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= -golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= -golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= -golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= -golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= -golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= -golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= +golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI= -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-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/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-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181220203305-927f97764cc3/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= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -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= -golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -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-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= -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-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/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-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= -golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= -golang.org/x/net v0.3.1-0.20221206200815-1e63c2f08a10/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= -golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= -golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -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-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 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= -golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/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= -golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -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-20200923182605-d9f96fdee20d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210112080510-489259a85091/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= -golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/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= -golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/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-20220319134239-a9b59b0215f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220422013727-9388b58f7150/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/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-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= -golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA= -golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20220210224613-90d013bbcef8/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= -golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= -golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= -golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= -golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200505023115-26f46d2f7ef8/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= -golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= -golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -gomodules.xyz/jsonpatch/v2 v2.2.0/go.mod h1:WXp+iVDkoLQqPudfQ9GBlwB2eZ5DKOnjQZCYdOS8GPY= -google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= -google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= -google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= -google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= -google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= -google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= -google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= -google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU= -google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94= -google.golang.org/api v0.47.0/go.mod h1:Wbvgpq1HddcWVtzsVLyfLp8lDg6AA241LmgIL59tHXo= -google.golang.org/api v0.48.0/go.mod h1:71Pr1vy+TAZRPkPs/xlCf5SsU8WjuAWv1Pfjbtukyy4= -google.golang.org/api v0.50.0/go.mod h1:4bNT5pAuq5ji4SRZm+5QIkjny9JAyVD/3gaSihNefaw= -google.golang.org/api v0.51.0/go.mod h1:t4HdrdoNgyN5cbEfm7Lum0lcLDLiise1F8qDKX00sOU= -google.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6z3k= -google.golang.org/api v0.55.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= -google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdrMgI= -google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= -google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= -google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= -google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= -google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= -google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= -google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201019141844-1ed22bb0c154/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= -google.golang.org/genproto v0.0.0-20210513213006-bf773b8c8384/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A= -google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= -google.golang.org/genproto v0.0.0-20210604141403-392c879c8b08/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= -google.golang.org/genproto v0.0.0-20210608205507-b6d2f5bf0d7d/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= -google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24= -google.golang.org/genproto v0.0.0-20210713002101-d411969a0d9a/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= -google.golang.org/genproto v0.0.0-20210716133855-ce7ef5c701ea/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= -google.golang.org/genproto v0.0.0-20210728212813-7823e685a01f/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= -google.golang.org/genproto v0.0.0-20210805201207-89edb61ffb67/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= -google.golang.org/genproto v0.0.0-20210813162853-db860fec028c/go.mod h1:cFeNkxwySK631ADgubI+/XFU/xp8FD5KIVV4rj8UC5w= -google.golang.org/genproto v0.0.0-20210821163610-241b8fcbd6c8/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= -google.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= -google.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= -google.golang.org/genproto v0.0.0-20210903162649-d08c68adba83/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= -google.golang.org/genproto v0.0.0-20210924002016-3dee208752a0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20220502173005-c8bf987b8c21/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= -google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= -google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= -google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= -google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= -google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= -google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= -google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= -google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= -google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= -google.golang.org/grpc v1.37.1/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= -google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= -google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= -google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= -google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= -google.golang.org/grpc v1.41.0/go.mod h1:U3l9uK9J0sini8mHphKoXyaqDA/8VyGnDee1zzIUK6k= -google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= -google.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= -google.golang.org/grpc v1.46.2/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= -google.golang.org/grpc v1.49.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= -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= -google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= -google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= -google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= -google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= -google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= -google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -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= -gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= 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-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= -gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= -gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= -gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= -gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= -gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= -gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= -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= -gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= -gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8= -honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= -honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q= +gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA= +k8s.io/api v0.26.0 h1:IpPlZnxBpV1xl7TGk/X6lFtpgjgntCg8PJ+qrPHAC7I= k8s.io/api v0.26.0/go.mod h1:k6HDTaIFC8yn1i6pSClSqIwLABIcLV9l5Q4EcngKnQg= -k8s.io/apiextensions-apiserver v0.26.0/go.mod h1:7ez0LTiyW5nq3vADtK6C3kMESxadD51Bh6uz3JOlqWQ= +k8s.io/apimachinery v0.26.0 h1:1feANjElT7MvPqp0JT6F3Ss6TWDwmcjLypwoPpEf7zg= k8s.io/apimachinery v0.26.0/go.mod h1:tnPmbONNJ7ByJNz9+n9kMjNP8ON+1qoAIIC70lztu74= -k8s.io/apiserver v0.26.0/go.mod h1:aWhlLD+mU+xRo+zhkvP/gFNbShI4wBDHS33o0+JGI84= -k8s.io/client-go v0.26.0/go.mod h1:I2Sh57A79EQsDmn7F7ASpmru1cceh3ocVT9KlX2jEZg= -k8s.io/code-generator v0.26.0/go.mod h1:OMoJ5Dqx1wgaQzKgc+ZWaZPfGjdRq/Y3WubFrZmeI3I= -k8s.io/component-base v0.26.0/go.mod h1:lqHwlfV1/haa14F/Z5Zizk5QmzaVf23nQzCwVOQpfC8= -k8s.io/gengo v0.0.0-20210813121822-485abfe95c7c/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= -k8s.io/gengo v0.0.0-20220902162205-c0856e24416d/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= -k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= -k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= +k8s.io/klog/v2 v2.80.1 h1:atnLQ121W371wYYFawwYx1aEY2eUfs4l3J72wtgAwV4= k8s.io/klog/v2 v2.80.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= -k8s.io/kms v0.26.0/go.mod h1:ReC1IEGuxgfN+PDCIpR6w8+XMmDE7uJhxcCwMZFdIYc= -k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280/go.mod h1:+Axhij7bCpeqhklhUTe3xmOn6bWxolyZEeyaFpjGtl4= -k8s.io/utils v0.0.0-20210802155522-efc7438f0176/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= -k8s.io/utils v0.0.0-20221107191617-1a15be271d1d/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= -k8s.io/utils v0.0.0-20221128185143-99ec85e7a448/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= -rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= -rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= -rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= -sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.33/go.mod h1:soWkSNf2tZC7aMibXEqVhCd73GOY5fJikn8qbdzemB0= -sigs.k8s.io/controller-runtime v0.14.1/go.mod h1:GaRkrY8a7UZF0kqFFbUKG7n9ICiTY5T55P1RiE3UZlU= +k8s.io/utils v0.0.0-20250321185631-1f6e0b77f77e h1:KqK5c/ghOm8xkHYhlodbp6i6+r+ChV2vuAuVRdFbLro= +k8s.io/utils v0.0.0-20250321185631-1f6e0b77f77e/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 h1:iXTIw73aPyC+oRdyqqvVJuloN1p0AC/kzH07hu3NE+k= sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= +sigs.k8s.io/structured-merge-diff/v4 v4.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE= sigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ihdVs8cGKBraizNC69E= -sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= +sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= diff --git a/internal/config/platform.go b/internal/config/platform.go new file mode 100644 index 0000000..2379dcb --- /dev/null +++ b/internal/config/platform.go @@ -0,0 +1,63 @@ +package config + +import ( + "os" + "slices" + "strings" + + "github.com/datasance/router/internal/resources/types" + "github.com/datasance/router/internal/utils" + "k8s.io/utils/ptr" +) + +var ( + Platform string + configuredPlatform *types.Platform +) + +func ClearPlatform() { + configuredPlatform = nil +} + +// GetPlatform returns the runtime platform defined, +// where the lookup goes through the following sequence: +// - Platform variable, +// - SKUPPER_PLATFORM environment variable +// - Static platform defined by skupper switch +// - Default platform "kubernetes" otherwise. +// In case the defined platform is invalid, "kubernetes" +// will be returned. +func GetPlatform() types.Platform { + if configuredPlatform != nil { + return *configuredPlatform + } + + var platform types.Platform + for i, arg := range os.Args { + if slices.Contains([]string{"--platform", "-p"}, arg) && i+1 < len(os.Args) { + platformArg := os.Args[i+1] + platform = types.Platform(platformArg) + break + } else if strings.HasPrefix(arg, "--platform=") || strings.HasPrefix(arg, "-p=") { + platformArg := strings.Split(arg, "=")[1] + platform = types.Platform(platformArg) + break + } + } + if platform == "" { + platform = types.Platform(utils.DefaultStr(Platform, + os.Getenv(types.ENV_PLATFORM), + string(types.PlatformKubernetes))) + } + switch platform { + case types.PlatformPodman: + configuredPlatform = &platform + case types.PlatformDocker: + configuredPlatform = &platform + case types.PlatformLinux: + configuredPlatform = &platform + default: + configuredPlatform = ptr.To(types.PlatformKubernetes) + } + return *configuredPlatform +} diff --git a/internal/exec/exec.go b/internal/exec/exec.go index 838860a..806c2e0 100644 --- a/internal/exec/exec.go +++ b/internal/exec/exec.go @@ -22,7 +22,7 @@ import ( ) func Run(ch chan<- error, command string, args []string, env []string) { - log.Printf("Running command: %s with args: %v and env vars: %v", command, args, env) + // log.Printf("Running command: %s with args: %v and env vars: %v", command, args, env) cmd := exec.Command(command, args...) cmd.Env = append(os.Environ(), env...) diff --git a/internal/messaging/messaging.go b/internal/messaging/messaging.go new file mode 100644 index 0000000..ad16a2f --- /dev/null +++ b/internal/messaging/messaging.go @@ -0,0 +1,27 @@ +package messaging + +import ( + amqp "github.com/interconnectedcloud/go-amqp" +) + +type ConnectionFactory interface { + Connect() (Connection, error) + Url() string +} + +type Connection interface { + Sender(address string) (Sender, error) + Receiver(address string, credit uint32) (Receiver, error) + Close() +} + +type Sender interface { + Send(msg *amqp.Message) error + Close() error +} + +type Receiver interface { + Receive() (*amqp.Message, error) + Accept(*amqp.Message) error + Close() error +} diff --git a/internal/qdr/amqp_mgmt.go b/internal/qdr/amqp_mgmt.go new file mode 100644 index 0000000..ea66780 --- /dev/null +++ b/internal/qdr/amqp_mgmt.go @@ -0,0 +1,1379 @@ +package qdr + +import ( + "context" + "encoding/json" + "fmt" + "log" + "os" + "strconv" + "strings" + "time" + + amqp "github.com/interconnectedcloud/go-amqp" + "github.com/datasance/router/internal/resources/types" + "github.com/datasance/router/internal/config" + "github.com/datasance/router/internal/utils" +) + +type RouterNode struct { + Id string `json:"id"` + Name string `json:"name"` + NextHop string `json:"nextHop"` + Address string `json:"address"` +} + +func (r *RouterNode) IsSelf() bool { + return r.NextHop == "(self)" +} + +type Connection struct { + Container string `json:"container"` + OperStatus string `json:"operStatus"` + Host string `json:"host"` + Role string `json:"role"` + Active bool `json:"active"` + Dir string `json:"dir"` +} + +type Agent struct { + connection *amqp.Client + session *amqp.Session + sender *amqp.Sender + anonymous *amqp.Sender + receiver *amqp.Receiver + local *Router + closed bool +} + +type Router struct { + Id string + Address string + Edge bool + Site SiteMetadata + Version string + ConnectedTo []string +} + +type SiteMetadata struct { + Id string `json:"id,omitempty"` + Version string `json:"version,omitempty"` + Platform string `json:"platform,omitempty"` +} + +func GetSiteMetadata(metadata string) SiteMetadata { + result := SiteMetadata{} + err := json.Unmarshal([]byte(metadata), &result) + if err != nil { + log.Printf("Assuming old format for router metadata %s: %s", metadata, err) + // assume old format, where metadata just holds site id + result.Id = metadata + } + return result +} + +func getSiteMetadataString(siteId string, version string) string { + siteDetails := SiteMetadata{ + Id: siteId, + Version: version, + Platform: string(config.GetPlatform()), + } + metadata, _ := json.Marshal(siteDetails) + return string(metadata) +} + +type recordType interface { + toRecord() Record +} + +type Record map[string]interface{} + +func (r Record) AsString(field string) string { + if value, ok := r[field].(string); ok { + return value + } else { + return "" + } +} + +func (r Record) AsBool(field string) bool { + if value, ok := r[field].(bool); ok { + return value + } else { + return false + } +} + +func (r Record) AsInt(field string) int { + value, _ := AsInt(r[field]) + return value +} + +func (r Record) AsUint64(field string) uint64 { + value, _ := AsUint64(r[field]) + return value +} + +func (r Record) AsRecord(field string) Record { + if value, ok := r[field].(map[string]interface{}); ok { + return value + } else { + return nil + } +} + +func asTcpEndpoint(record Record) TcpEndpoint { + endpoint := TcpEndpoint{ + Name: record.AsString("name"), + Host: record.AsString("host"), + Port: record.AsString("port"), + Address: record.AsString("address"), + SiteId: record.AsString("siteId"), + SslProfile: record.AsString("sslProfile"), + ProcessID: record.AsString("processId"), + } + if value, ok := record["verifyHostname"]; ok { + if verify, ok := value.(bool); ok { + endpoint.VerifyHostname = &verify + } + } + return endpoint +} + +func asConnection(record Record) Connection { + return Connection{ + Role: record.AsString("role"), + Container: record.AsString("container"), + Host: record.AsString("host"), + OperStatus: record.AsString("operStatus"), + Dir: record.AsString("dir"), + Active: record.AsBool("active"), + } +} + +func asRouterNode(record Record) RouterNode { + return RouterNode{ + Id: record.AsString("id"), + Name: record.AsString("name"), + Address: record.AsString("address"), + NextHop: record.AsString("nextHop"), + } +} + +func asRouter(record Record) *Router { + r := Router{ + Id: record.AsString("id"), + Site: GetSiteMetadata(record.AsString("metadata")), + Version: record.AsString("version"), + } + if record.AsString("mode") == "edge" { + r.Edge = true + } else { + r.Edge = false + } + r.Address = GetRouterAgentAddress(r.Id, r.Edge) + return &r +} + +func (node *RouterNode) AsRouter() *Router { + return &Router{ + Id: node.Id, + // SiteId ??? + Address: node.Address, + Edge: false, /*RouterNode is always an interior*/ + } +} + +type AgentPool struct { + url string + config TlsConfigRetriever + pool chan *Agent +} + +func NewAgentPool(url string, config TlsConfigRetriever) *AgentPool { + return &AgentPool{ + url: url, + config: config, + pool: make(chan *Agent, 10), + } +} + +func (p *AgentPool) Get() (*Agent, error) { + var a *Agent + var err error + select { + case a = <-p.pool: + default: + a, err = Connect(p.url, p.config) + } + return a, err +} + +func (p *AgentPool) Put(a *Agent) { + if !a.closed { + select { + case p.pool <- a: + default: + a.Close() + } + } +} + +func Connect(url string, config TlsConfigRetriever) (*Agent, error) { + factory := ConnectionFactory{ + url: url, + config: config, + } + return newAgent(&factory) +} + +func newAgent(factory *ConnectionFactory) (*Agent, error) { + client, err := factory.Connect() + if err != nil { + return nil, fmt.Errorf("Failed to create connection: %s", err) + } + connection := client.(*AmqpConnection) + receiver, err := connection.session.NewReceiver( + amqp.LinkSourceAddress(""), + amqp.LinkAddressDynamic(), + amqp.LinkCredit(10), + ) + if err != nil { + return nil, fmt.Errorf("Failed to create receiver: %s", err) + } + sender, err := connection.session.NewSender( + amqp.LinkTargetAddress("$management"), + ) + if err != nil { + return nil, fmt.Errorf("Failed to create sender: %s", err) + } + anonymous, err := connection.session.NewSender() + if err != nil { + return nil, fmt.Errorf("Failed to create anonymous sender: %s", err) + } + a := &Agent{ + connection: connection.client, + session: connection.session, + sender: sender, + anonymous: anonymous, + receiver: receiver, + } + a.local, err = a.GetLocalRouter() + if err != nil { + return a, fmt.Errorf("Failed to lookup local router details: %s", err) + } + return a, nil +} + +func (a *Agent) newReceiver(address string) (*amqp.Receiver, error) { + return a.session.NewReceiver( + amqp.LinkSourceAddress(address), + amqp.LinkCredit(10), + ) +} + +func (a *Agent) Close() error { + a.closed = true + return a.connection.Close() +} + +func isOk(code int) bool { + return code >= 200 && code < 300 +} + +func cleanup(input interface{}) interface{} { + switch input.(type) { + case map[interface{}]interface{}: + m := make(map[string]interface{}) + for k, v := range input.(map[interface{}]interface{}) { + m[k.(string)] = cleanup(v) + } + return m + case map[string]interface{}: + m := input.(map[string]interface{}) + for k, v := range m { + m[k] = cleanup(v) + } + return m + default: + return input + } +} + +func makeRecord(fields []string, values []interface{}) Record { + record := Record{} + for i, name := range fields { + record[name] = cleanup(values[i]) + } + return record +} + +func stringify(items []interface{}) []string { + s := make([]string, len(items)) + for i := range items { + s[i] = fmt.Sprintf("%v", items[i]) + } + return s +} + +func GetRouterAgentAddress(id string, edge bool) string { + if edge { + return "amqp:/_edge/" + id + "/$management" + } else { + return "amqp:/_topo/0/" + id + "/$management" + } +} + +func GetRouterAddress(id string, edge bool) string { + if edge { + return "amqp:/_edge/" + id + } else { + return "amqp:/_topo/0/" + id + } +} + +func (a *Agent) request(operation string, typename string, name string, attributes map[string]interface{}) error { + ctx, cancel := context.WithTimeout(context.TODO(), 5*time.Second) + defer cancel() + + var request amqp.Message + var properties amqp.MessageProperties + properties.ReplyTo = a.receiver.Address() + properties.CorrelationID = uint64(1) + request.Properties = &properties + request.ApplicationProperties = make(map[string]interface{}) + request.ApplicationProperties["operation"] = operation + request.ApplicationProperties["type"] = typename + request.ApplicationProperties["name"] = name + if attributes != nil { + request.Value = attributes + } + + if err := a.sender.Send(ctx, &request); err != nil { + a.Close() + return fmt.Errorf("Could not send request: %s", err) + } + + response, err := a.receiver.Receive(ctx) + if err != nil { + a.Close() + return fmt.Errorf("Failed to receive response: %s", err) + } + response.Accept() + if status, ok := AsInt(response.ApplicationProperties["statusCode"]); !ok && !isOk(status) { + return fmt.Errorf("Query failed with: %s", response.ApplicationProperties["statusDescription"]) + } + return nil +} + +func (a *Agent) Create(typename string, name string, entity recordType) error { + attributes := entity.toRecord() + log.Println("CREATE", typename, name, attributes) + return a.request("CREATE", typename, name, attributes) +} + +func (a *Agent) Update(typename string, name string, entity recordType) error { + attributes := entity.toRecord() + log.Println("UPDATE", typename, name, attributes) + return a.request("UPDATE", typename, name, attributes) +} + +func (a *Agent) Delete(typename string, name string) error { + if name == "" { + return fmt.Errorf("Cannot delete entity of type %s with no name", typename) + } + log.Println("DELETE", typename, name) + return a.request("DELETE", typename, name, nil) +} + +func (a *Agent) Query(typename string, attributes []string) ([]Record, error) { + return a.QueryRouterNode(typename, attributes, nil) +} + +func (a *Agent) QueryRouterNode(typename string, attributes []string, node *RouterNode) ([]Record, error) { + var address string + if node != nil { + address = node.Address + } + return a.QueryByAgentAddress(typename, attributes, address) +} + +func AsInt(value interface{}) (int, bool) { + switch value.(type) { + case uint8: + return int(value.(uint8)), true + case uint16: + return int(value.(uint16)), true + case uint32: + return int(value.(uint32)), true + case uint64: + return int(value.(uint64)), true + case int8: + return int(value.(int8)), true + case int16: + return int(value.(int16)), true + case int32: + return int(value.(int32)), true + case int64: + return int(value.(int64)), true + case int: + return value.(int), true + default: + return 0, false + } +} + +func AsUint64(value interface{}) (uint64, bool) { + switch value.(type) { + case uint8: + return uint64(value.(uint8)), true + case uint16: + return uint64(value.(uint16)), true + case uint32: + return uint64(value.(uint32)), true + case uint64: + return value.(uint64), true + case int8: + return uint64(value.(int8)), true + case int16: + return uint64(value.(int16)), true + case int32: + return uint64(value.(int32)), true + case int64: + return uint64(value.(int64)), true + case int: + return uint64(value.(int)), true + default: + return 0, false + } +} + +func (a *Agent) QueryByAgentAddress(typename string, attributes []string, agent string) ([]Record, error) { + ctx, cancel := context.WithTimeout(context.TODO(), 5*time.Second) + defer cancel() + + var request amqp.Message + var properties amqp.MessageProperties + properties.ReplyTo = a.receiver.Address() + properties.CorrelationID = uint64(1) + request.Properties = &properties + request.ApplicationProperties = make(map[string]interface{}) + request.ApplicationProperties["operation"] = "QUERY" + request.ApplicationProperties["entityType"] = typename + var body = make(map[string]interface{}) + body["attributeNames"] = attributes + request.Value = body + + var err error + if agent == "" { + err = a.sender.Send(ctx, &request) + } else { + request.Properties.To = agent + err = a.anonymous.Send(ctx, &request) + } + if err != nil { + a.Close() + return nil, fmt.Errorf("Could not send request: %s", err) + } + + response, err := a.receiver.Receive(ctx) + if err != nil { + a.Close() + return nil, fmt.Errorf("Failed to receive response: %s", err) + } + response.Accept() + if status, ok := AsInt(response.ApplicationProperties["statusCode"]); ok && isOk(status) { + if top, ok := response.Value.(map[string]interface{}); ok { + records := []Record{} + fields := stringify(top["attributeNames"].([]interface{})) + results := top["results"].([]interface{}) + for _, r := range results { + o := r.([]interface{}) + records = append(records, makeRecord(fields, o)) + } + return records, nil + } else { + return nil, fmt.Errorf("Bad response: %s", response.Value) + } + } else { + return nil, fmt.Errorf("Query failed with: %s", response.ApplicationProperties["statusDescription"]) + } +} + +type Query struct { + typename string + attributes []string + agent string +} + +func queryAllAgents(typename string, agents []string) []Query { + queries := make([]Query, len(agents)) + for i, a := range agents { + queries[i].typename = typename + queries[i].attributes = []string{} + queries[i].agent = a + } + return queries +} + +func queryAllTypes(typenames []string, agent string) []Query { + queries := make([]Query, len(typenames)) + for i, t := range typenames { + queries[i].typename = t + queries[i].attributes = []string{} + queries[i].agent = agent + } + return queries +} + +func queryAllAgentsForAllTypes(typenames []string, agents []string) []Query { + queries := make([]Query, len(agents)*len(typenames)) + i := 0 + for _, t := range typenames { + for _, a := range agents { + queries[i].typename = t + queries[i].attributes = []string{} + queries[i].agent = a + i++ + } + } + return queries +} + +func (a *Agent) BatchQuery(queries []Query) ([][]Record, error) { + fmt.Printf("BatchQuery(%v)\n", queries) + ctx, cancel := context.WithTimeout(context.TODO(), 5*time.Second) + defer cancel() + + batchResults := make([][]Record, len(queries)) + for i, q := range queries { + var request amqp.Message + var properties amqp.MessageProperties + properties.ReplyTo = a.receiver.Address() + properties.CorrelationID = uint64(i) + request.Properties = &properties + request.ApplicationProperties = make(map[string]interface{}) + request.ApplicationProperties["operation"] = "QUERY" + request.ApplicationProperties["entityType"] = q.typename + var body = make(map[string]interface{}) + body["attributeNames"] = q.attributes + request.Value = body + + var err error + if q.agent == "" { + err = a.sender.Send(ctx, &request) + } else { + request.Properties.To = q.agent + err = a.anonymous.Send(ctx, &request) + } + if err != nil { + a.Close() + return nil, fmt.Errorf("Could not send request: %s", err) + } + } + errors := []string{} + for i := 0; i < len(queries); i++ { + fmt.Printf("Waiting for response %d of %d\n", (i + 1), len(queries)) + response, err := a.receiver.Receive(ctx) + if err != nil { + a.Close() + return nil, fmt.Errorf("Failed to receive response: %s", err) + } + response.Accept() + responseIndex, ok := response.Properties.CorrelationID.(uint64) + if !ok { + errors = append(errors, fmt.Sprintf("Could not get correct correlation id from response: %#v (%T)", response.Properties.CorrelationID, response.Properties.CorrelationID)) + } else { + if status, ok := AsInt(response.ApplicationProperties["statusCode"]); ok && isOk(status) { + if top, ok := response.Value.(map[string]interface{}); ok { + records := []Record{} + fields := stringify(top["attributeNames"].([]interface{})) + results := top["results"].([]interface{}) + for _, r := range results { + o := r.([]interface{}) + records = append(records, makeRecord(fields, o)) + } + batchResults[responseIndex] = records + } else { + errors = append(errors, fmt.Sprintf("Bad response: %s", response.Value)) + } + } else { + errors = append(errors, fmt.Sprintf("Query failed with: %s", response.ApplicationProperties["statusDescription"])) + } + } + } + if len(errors) > 0 { + return nil, fmt.Errorf(strings.Join(errors, ", ")) + } + return batchResults, nil +} + +func (a *Agent) GetInteriorNodes() ([]RouterNode, error) { + var address string + var err error + if a.isEdgeRouter() { + address, err = a.getInteriorAddressForUplink() + if err != nil { + return nil, fmt.Errorf("Could not determine interior agent address for edge router: %s", err) + } + } + records, err := a.QueryByAgentAddress("io.skupper.router.router.node", []string{}, address) + if err != nil { + return nil, err + } + fmt.Printf("Interior nodes are %v\n", records) + nodes := make([]RouterNode, len(records)) + for i, r := range records { + nodes[i] = asRouterNode(r) + } + return nodes, nil +} + +func (a *Agent) GetConnections() ([]Connection, error) { + return a.GetConnectionsFor("") +} + +func (a *Agent) GetConnectionsFor(agent string) ([]Connection, error) { + records, err := a.Query("io.skupper.router.connection", []string{}) + if err != nil { + return nil, err + } + connections := make([]Connection, len(records)) + for i, r := range records { + connections[i] = asConnection(r) + } + return connections, nil +} + +func getAddressesFor(routers []Router) []string { + agents := make([]string, len(routers)) + for i, r := range routers { + agents[i] = r.Address + "/$management" + } + return agents +} + +func getBridgeServerAddressesFor(routers []Router) []string { + agents := make([]string, len(routers)) + for i, r := range routers { + agents[i] = r.Id + "/bridge-server/$management" + } + return agents +} + +func GetRoutersForSite(routers []Router, siteId string) []Router { + list := []Router{} + for _, r := range routers { + if r.Site.Id == siteId { + list = append(list, r) + } + } + return list +} + +func (a *Agent) GetAllRouters() ([]Router, error) { + nodes, err := a.GetInteriorNodes() + if err != nil { + return nil, err + } + routers := []Router{} + routersFiltered := []Router{} + for _, n := range nodes { + routers = append(routers, *n.AsRouter()) + } + edges, err := a.getAllEdgeRouters(getAddressesFor(routers)) + if err != nil { + return nil, err + } + for _, e := range edges { + routers = append(routers, e) + } + err = a.getSiteIds(routers) + if err != nil { + return nil, err + } + isSvcRouter := func(edgeId string) bool { + for _, r := range routers { + if r.Edge { + continue + } + // podman svc + if strings.HasPrefix(edgeId, r.Site.Id+"-") { + return true + } else if strings.HasSuffix(edgeId, "-"+r.Site.Id) { + return true + } + } + return false + } + for _, r := range routers { + if !r.Edge || !isSvcRouter(r.Site.Id) { + routersFiltered = append(routersFiltered, r) + } + } + routers = routersFiltered + err = a.getConnectedTo(routers) + if err != nil { + return nil, err + } + return routers, nil +} + +func (a *Agent) getConnectionsForAll(agents []string) ([]Connection, error) { + connections := []Connection{} + results, err := a.BatchQuery(queryAllAgents("io.skupper.router.connection", agents)) + if err != nil { + return nil, err + } + for _, records := range results { + for _, r := range records { + connections = append(connections, asConnection(r)) + } + } + return connections, nil +} + +func (a *Agent) getSiteIds(routers []Router) error { + results, err := a.BatchQuery(queryAllAgents("io.skupper.router.router", getAddressesFor(routers))) + if err != nil { + return err + } + for i, records := range results { + if len(records) == 1 { + routers[i].Site = GetSiteMetadata(records[0].AsString("metadata")) + } else { + return fmt.Errorf("Unexpected number of router records: %d", len(records)) + } + } + return nil +} + +func (a *Agent) getConnectedTo(routers []Router) error { + results, err := a.BatchQuery(queryAllAgents("io.skupper.router.connection", getAddressesFor(routers))) + if err != nil { + return err + } + for i, records := range results { + routers[i].ConnectedTo = []string{} + for _, r := range records { + c := asConnection(r) + if c.Dir == "out" && (c.Role == "edge" || c.Role == "inter-router") { + routers[i].ConnectedTo = append(routers[i].ConnectedTo, c.Container) + } + } + } + return nil +} + +func getBridgeTypes() []string { + return []string{ + "io.skupper.router.tcpConnector", + "io.skupper.router.tcpListener", + "io.skupper.router.httpConnector", + "io.skupper.router.httpListener", + } +} + +type TcpEndpointFilter func(*TcpEndpoint) bool + +func asTcpEndpoints(records []Record, filter TcpEndpointFilter) []TcpEndpoint { + endpoints := []TcpEndpoint{} + for _, record := range records { + endpoint := asTcpEndpoint(record) + if filter == nil || filter(&endpoint) { + endpoints = append(endpoints, endpoint) + } + } + return endpoints +} + +func (a *Agent) getLocalTcpEndpoints(typename string, filter TcpEndpointFilter) ([]TcpEndpoint, error) { + results, err := a.Query(typename, []string{}) + if err != nil { + return nil, err + } + records := asTcpEndpoints(results, filter) + return records, nil +} + +func (a *Agent) GetConnectorByName(name string) (*Connector, error) { + + results, err := a.Query("io.skupper.router.connector", []string{}) + if err != nil { + return nil, err + } + for _, record := range results { + + result := asConnector(record) + + if result.Name == name { + return &result, nil + } + } + + return nil, nil +} + +func (a *Agent) GetSslProfileByName(name string) (*SslProfile, error) { + + results, err := a.Query("io.skupper.router.sslProfile", []string{}) + if err != nil { + return nil, err + } + for _, record := range results { + + result := asSslProfile(record) + + if result.Name == name { + return &result, nil + } + } + + return nil, nil +} + +func (a *Agent) GetSslProfiles() (map[string]SslProfile, error) { + results, err := a.Query("io.skupper.router.sslProfile", []string{}) + if err != nil { + return nil, err + } + profiles := map[string]SslProfile{} + for _, record := range results { + profile := asSslProfile(record) + profiles[profile.Name] = profile + } + + return profiles, nil +} + +func (a *Agent) GetLocalTcpListeners(filter TcpEndpointFilter) ([]TcpEndpoint, error) { + return a.getLocalTcpEndpoints("io.skupper.router.tcpListener", filter) +} + +func (a *Agent) GetLocalTcpConnectors(filter TcpEndpointFilter) ([]TcpEndpoint, error) { + return a.getLocalTcpEndpoints("io.skupper.router.tcpConnector", filter) +} + +func (a *Agent) GetLocalBridgeConfig() (*BridgeConfig, error) { + config := NewBridgeConfig() + + results, err := a.Query("io.skupper.router.tcpConnector", []string{}) + if err != nil { + return nil, err + } + for _, record := range results { + config.AddTcpConnector(asTcpEndpoint(record)) + } + + results, err = a.Query("io.skupper.router.tcpListener", []string{}) + if err != nil { + return nil, err + } + for _, record := range results { + config.AddTcpListener(asTcpEndpoint(record)) + } + + return &config, nil +} + +func (a *Agent) UpdateLocalBridgeConfig(changes *BridgeConfigDifference) error { + for _, deleted := range changes.TcpConnectors.Deleted { + if err := a.Delete("io.skupper.router.tcpConnector", deleted); err != nil { + return fmt.Errorf("Error deleting tcp connectors: %s", err) + } + } + for _, deleted := range changes.TcpListeners.Deleted { + if err := a.Delete("io.skupper.router.tcpListener", deleted); err != nil { + return fmt.Errorf("Error deleting tcp listeners: %s", err) + } + } + for _, added := range changes.TcpConnectors.Added { + if err := a.Create("io.skupper.router.tcpConnector", added.Name, added); err != nil { + return fmt.Errorf("Error adding tcp connectors: %s", err) + } + } + for _, added := range changes.TcpListeners.Added { + if err := a.Create("io.skupper.router.tcpListener", added.Name, added); err != nil { + return fmt.Errorf("Error adding tcp listeners: %s", err) + } + } + return nil +} + +func (a *Agent) GetBridges(routers []Router) ([]BridgeConfig, error) { + configs := []BridgeConfig{} + agents := getAddressesFor(routers) + for _, agent := range agents { + config := NewBridgeConfig() + + results, err := a.QueryByAgentAddress("io.skupper.router.tcpConnector", []string{}, agent) + if err != nil { + return nil, err + } + for _, record := range results { + config.AddTcpConnector(asTcpEndpoint(record)) + } + results, err = a.QueryByAgentAddress("io.skupper.router.tcpListener", []string{}, agent) + if err != nil { + return nil, err + } + for _, record := range results { + config.AddTcpListener(asTcpEndpoint(record)) + } + + configs = append(configs, config) + } + return configs, nil +} + +const ( + DirectionIn string = "in" + DirectionOut string = "out" +) + +type TcpConnection struct { + Name string `json:"name"` + Host string `json:"host"` + Address string `json:"address"` + Direction string `json:"direction"` + BytesIn int `json:"bytesIn"` + BytesOut int `json:"bytesOut"` + Uptime uint64 `json:"uptimeSeconds"` + LastIn uint64 `json:"lastInSeconds"` + LastOut uint64 `json:"lastOutSeconds"` +} + +func getTcpConnectionsFromRecords(records []Record) ([]TcpConnection, error) { + conns := []TcpConnection{} + for _, record := range records { + var conn TcpConnection + if err := convert(record, &conn); err != nil { + return conns, fmt.Errorf("Failed to convert to TcpConnection: %s", err) + } + conns = append(conns, conn) + } + return conns, nil +} + +func (a *Agent) GetTcpConnections(routers []Router) ([][]TcpConnection, error) { + queries := queryAllAgents("io.skupper.router.tcpConnection", getAddressesFor(routers)) + results, err := a.BatchQuery(queries) + if err != nil { + return nil, err + } + converted := [][]TcpConnection{} + for _, records := range results { + conns, err := getTcpConnectionsFromRecords(records) + if err != nil { + return converted, err + } + converted = append(converted, conns) + } + return converted, nil +} + +func (a *Agent) GetLocalTcpConnections() ([]TcpConnection, error) { + records, err := a.Query("io.skupper.router.tcpConnection", []string{}) + if err != nil { + return nil, err + } + return getTcpConnectionsFromRecords(records) +} + +func (a *Agent) getAllEdgeRouters(agents []string) ([]Router, error) { + edges := []Router{} + + connections, err := a.getConnectionsForAll(agents) + if err != nil { + return nil, err + } + for _, c := range connections { + if c.Role == "edge" && c.Dir == DirectionIn { + router := Router{ + Id: c.Container, + Edge: true, + Address: GetRouterAddress(c.Container, true), + } + edges = append(edges, router) + } + } + return edges, nil +} + +func (a *Agent) getEdgeRouters(agent string) ([]Router, error) { + connections, err := a.GetConnectionsFor(agent) + if err != nil { + return nil, err + } + edges := []Router{} + for _, c := range connections { + if c.Role == "edge" && c.Dir == DirectionIn { + router := Router{ + Id: c.Container, + Edge: true, + Address: GetRouterAddress(c.Container, true), + } + edges = append(edges, router) + } + } + return edges, nil +} + +func (a *Agent) GetLocalGateways() ([]Router, error) { + gateways := []Router{} + connections, err := a.GetConnections() + if err != nil { + return gateways, err + } + for _, c := range connections { + if c.Role == "edge" && c.Dir == DirectionIn && isGateway(c.Container) { + router := Router{ + Id: c.Container, + Edge: true, + Address: GetRouterAddress(c.Container, true), + } + gateways = append(gateways, router) + } + } + err = a.getSiteIds(gateways) + return gateways, err +} + +func (a *Agent) GetLocalRouter() (*Router, error) { + records, err := a.Query("io.skupper.router.router", []string{}) + if err != nil { + return nil, err + } + if len(records) == 1 { + return asRouter(records[0]), nil + } else { + return nil, fmt.Errorf("Unexpected number of router records: %d", len(records)) + } +} + +func (a *Agent) isEdgeRouter() bool { + return a.local.Edge +} + +func (a *Agent) getInteriorAddressForUplink() (string, error) { + connections, err := a.GetConnections() + if err != nil { + return "", err + } + return GetInteriorAddressForUplink(connections) +} + +func GetInteriorAddressForUplink(connections []Connection) (string, error) { + for _, c := range connections { + if c.Role == "edge" && c.Dir == "out" { + return GetRouterAgentAddress(c.Container, false), nil + } + } + return "", fmt.Errorf("Could not find uplink connection") +} + +type ConnectorStatus struct { + Name string + Host string + Port string + Role string + Cost int + Status string + Description string +} + +func asConnectorStatus(record Record) ConnectorStatus { + return ConnectorStatus{ + Name: record.AsString("name"), + Host: record.AsString("host"), + Port: record.AsString("port"), + Role: record.AsString("role"), + Cost: record.AsInt("cost"), + Status: record.AsString("connectionStatus"), + Description: record.AsString("connectionMsg"), + } +} + +func asConnector(record Record) Connector { + return Connector{ + Name: record.AsString("name"), + Host: record.AsString("host"), + Port: record.AsString("port"), + RouteContainer: record.AsBool("routeContainer"), + VerifyHostname: record.AsBool("verifyHostname"), + SslProfile: record.AsString("sslProfile"), + } +} + +func asInt32(s string) int32 { + ival, _ := strconv.Atoi(s) + return int32(ival) +} + +func asListener(record Record) Listener { + return Listener{ + Name: record.AsString("name"), + Role: asRole(record.AsString("role")), + Host: record.AsString("host"), + Port: asInt32(record.AsString("port")), + Cost: int32(record.AsInt("cost")), + LinkCapacity: int32(record.AsInt("linkCapacity")), + AuthenticatePeer: record.AsBool("authenticatePeer"), + SaslMechanisms: record.AsString("saslMechanisms"), + RouteContainer: record.AsBool("routeContainer"), + Http: record.AsBool("http"), + HttpRootDir: record.AsString("httpRootDir"), + Websockets: record.AsBool("websockets"), + Healthz: record.AsBool("healthz"), + Metrics: record.AsBool("metrics"), + SslProfile: record.AsString("sslProfile"), + MaxFrameSize: record.AsInt("maxFrameSize"), + MaxSessionFrames: record.AsInt("maxSessionFrames"), + } +} + +func asSslProfile(record Record) SslProfile { + return SslProfile{ + Name: record.AsString("name"), + CertFile: record.AsString("certFile"), + PrivateKeyFile: record.AsString("privateKeyFile"), + CaCertFile: record.AsString("caCertFile"), + } +} + +func (a *Agent) UpdateConnectorConfig(changes *ConnectorDifference) error { + for _, deleted := range changes.Deleted { + if err := a.Delete("io.skupper.router.connector", deleted.Name); err != nil { + return fmt.Errorf("Error deleting connectors: %s", err) + } + } + + for _, added := range changes.Added { + + if len(added.Host) == 0 { + return fmt.Errorf("No host specified while creating a connector") + } + + if len(added.Port) == 0 { + return fmt.Errorf("No port specified while creating a connector") + } + + if len(added.SslProfile) > 0 { + sslProfile, err := a.GetSslProfileByName(added.SslProfile) + if err != nil { + return err + } + + if sslProfile.CaCertFile != "" { + _, err = os.Stat(sslProfile.CaCertFile) + if err != nil { + return err + } + } + + if sslProfile.CertFile != "" { + _, err = os.Stat(sslProfile.CertFile) + if err != nil { + return err + } + } + + if sslProfile.PrivateKeyFile != "" { + _, err = os.Stat(sslProfile.PrivateKeyFile) + if err != nil { + return err + } + } + } + + if err := a.Create("io.skupper.router.connector", added.Name, added); err != nil { + return fmt.Errorf("Error adding connectors: %s", err) + } + + } + + return nil +} + +func (a *Agent) GetLocalConnectorStatus() (map[string]ConnectorStatus, error) { + results, err := a.Query("io.skupper.router.connector", []string{}) + if err != nil { + return nil, err + } + connectors := map[string]ConnectorStatus{} + for _, record := range results { + c := asConnectorStatus(record) + connectors[c.Name] = c + } + return connectors, nil +} + +func (a *Agent) GetLocalConnectors() (map[string]Connector, error) { + results, err := a.Query("io.skupper.router.connector", []string{}) + if err != nil { + return nil, err + } + connectors := map[string]Connector{} + for _, record := range results { + c := asConnector(record) + connectors[c.Name] = c + } + return connectors, nil +} + +func (a *Agent) UpdateListenerConfig(changes *ListenerDifference) error { + for _, deleted := range changes.Deleted { + if err := a.Delete("io.skupper.router.listener", deleted.Name); err != nil { + return fmt.Errorf("Error deleting listeners: %s", err) + } + } + + for _, added := range changes.Added { + if err := a.Create("io.skupper.router.listener", added.Name, added); err != nil { + return fmt.Errorf("Error adding listeners: %s", err) + } + + } + + return nil +} + +func (a *Agent) GetLocalListeners() (map[string]Listener, error) { + results, err := a.Query("io.skupper.router.listener", []string{}) + if err != nil { + return nil, err + } + listeners := map[string]Listener{} + for _, record := range results { + l := asListener(record) + listeners[l.Name] = l + } + return listeners, nil +} + +func (a *Agent) Request(request *Request) (*Response, error) { + ctx, cancel := context.WithTimeout(context.TODO(), 10*time.Second) + defer cancel() + + requestMsg := amqp.Message{ + Properties: &amqp.MessageProperties{ + To: request.Address, + Subject: request.Type, + ReplyTo: a.receiver.Address(), + }, + ApplicationProperties: map[string]interface{}{}, + Value: nil, + } + if request.Body != "" { + requestMsg.Value = request.Body + } + for k, v := range request.Properties { + requestMsg.ApplicationProperties[k] = v + } + requestMsg.ApplicationProperties[VersionProperty] = request.Version + + err := a.anonymous.Send(ctx, &requestMsg) + if err != nil { + a.Close() + return nil, fmt.Errorf("Could not send %s request: %s", request.Type, err) + } + responseMsg, err := a.receiver.Receive(ctx) + if err != nil { + a.Close() + return nil, fmt.Errorf("Failed to receive response: %s", err) + } + responseMsg.Accept() + + response := Response{ + Type: responseMsg.Properties.Subject, + } + for k, v := range responseMsg.ApplicationProperties { + if k == VersionProperty { + if version, ok := v.(string); ok { + response.Version = version + } + } else { + response.Properties[k] = v + } + } + if body, ok := responseMsg.Value.(string); ok { + response.Body = body + } + return &response, nil +} + +func (r *Router) IsGateway() bool { + return isGateway(r.Id) +} + +func isGateway(routerId string) bool { + return strings.HasPrefix(routerId, "skupper-gateway-") +} + +func GetSiteNameForGateway(gateway *Router) string { + return strings.TrimPrefix(gateway.Id, "skupper-gateway-") +} + +func (a *Agent) CreateSslProfile(profile SslProfile) error { + + result, err := a.GetSslProfileByName(profile.Name) + if err != nil { + return err + } + + // Trying to create a ssl profile that already exists will generate an error in the router. + if result != nil { + return nil + } + + if err := a.Create("io.skupper.router.sslProfile", profile.Name, profile); err != nil { + return fmt.Errorf("Error adding SSL Profile: %s", err) + } + + return nil +} + +func (a *Agent) ReloadSslProfile(name string) error { + + profile, err := a.GetSslProfileByName(name) + if err != nil { + return err + } + + // A profile is expected to be returned + if profile == nil { + return fmt.Errorf("No SSL Profile with name %s found", name) + } + + if err := a.Update("io.skupper.router.sslProfile", profile.Name, profile); err != nil { + return fmt.Errorf("Error updating SSL Profile: %s", err) + } + + return nil +} + +func ConnectedSitesInfo(selfId string, routers []Router) types.TransportConnectedSites { + var connectedSites types.TransportConnectedSites + var self *Router + for _, r := range routers { + if r.Site.Id == selfId { + self = &r + break + } + } + if self == nil { + return connectedSites + } + for _, r := range routers { + if r.Edge && len(r.ConnectedTo) > 1 { + connectedSites.Warnings = append(connectedSites.Warnings, "There are edge uplinks to distinct networks, please verify topology (connected counts may not be accurate).") + } + if utils.StringSliceContains(r.ConnectedTo, self.Id) { + connectedSites.Direct += 1 + } + } + connectedSites.Total = len(routers) - 1 + connectedSites.Direct += len(self.ConnectedTo) + connectedSites.Indirect = connectedSites.Total - connectedSites.Direct + return connectedSites +} diff --git a/internal/qdr/messaging.go b/internal/qdr/messaging.go new file mode 100644 index 0000000..48ac4db --- /dev/null +++ b/internal/qdr/messaging.go @@ -0,0 +1,113 @@ +package qdr + +import ( + "context" + "crypto/tls" + + amqp "github.com/interconnectedcloud/go-amqp" + + "github.com/datasance/router/internal/messaging" +) + +type TlsConfigRetriever interface { + GetTlsConfig() (*tls.Config, error) +} + +type ConnectionFactory struct { + url string + config TlsConfigRetriever +} + +func (f *ConnectionFactory) Connect() (messaging.Connection, error) { + if f.config == nil { + return dial(f.url, amqp.ConnMaxFrameSize(4294967295)) + } else { + tlsConfig, err := f.config.GetTlsConfig() + if err != nil { + return nil, err + } + return dial(f.url, amqp.ConnSASLExternal(), amqp.ConnMaxFrameSize(4294967295), amqp.ConnTLSConfig(tlsConfig)) + } +} + +func dial(addr string, opts ...amqp.ConnOption) (*AmqpConnection, error) { + client, err := amqp.Dial(addr, opts...) + if err != nil { + return nil, err + } + session, err := client.NewSession() + if err != nil { + client.Close() + return nil, err + } + return &AmqpConnection{client: client, session: session}, nil +} + +func (f *ConnectionFactory) Url() string { + return f.url +} + +func NewConnectionFactory(url string, config TlsConfigRetriever) *ConnectionFactory { + return &ConnectionFactory{ + url: url, + config: config, + } +} + +type AmqpConnection struct { + client *amqp.Client + session *amqp.Session +} + +type AmqpSender struct { + connection *AmqpConnection + sender *amqp.Sender +} + +type AmqpReceiver struct { + connection *AmqpConnection + receiver *amqp.Receiver +} + +func (c *AmqpConnection) Close() { + c.client.Close() +} + +func (c *AmqpConnection) Sender(address string) (messaging.Sender, error) { + sender, err := c.session.NewSender(amqp.LinkTargetAddress(address)) + if err != nil { + return nil, err + } + return &AmqpSender{connection: c, sender: sender}, nil +} + +func (c *AmqpConnection) Receiver(address string, credit uint32) (messaging.Receiver, error) { + receiver, err := c.session.NewReceiver( + amqp.LinkSourceAddress(address), + amqp.LinkCredit(credit), + ) + if err != nil { + return nil, err + } + return &AmqpReceiver{connection: c, receiver: receiver}, nil +} + +func (s *AmqpSender) Send(msg *amqp.Message) error { + return s.sender.Send(context.Background(), msg) +} + +func (s *AmqpSender) Close() error { + return s.sender.Close(context.Background()) +} + +func (s *AmqpReceiver) Receive() (*amqp.Message, error) { + return s.receiver.Receive(context.Background()) +} + +func (s *AmqpReceiver) Accept(msg *amqp.Message) error { + return msg.Accept() +} + +func (s *AmqpReceiver) Close() error { + return s.receiver.Close(context.Background()) +} diff --git a/internal/qdr/qdr.go b/internal/qdr/qdr.go new file mode 100644 index 0000000..b4c3a3f --- /dev/null +++ b/internal/qdr/qdr.go @@ -0,0 +1,1244 @@ +package qdr + +import ( + "encoding/json" + "fmt" + "log" + "net" + path_ "path" + "reflect" + "strconv" + "strings" + + corev1 "k8s.io/api/core/v1" + + "github.com/datasance/router/internal/resources/types" +) + +type RouterConfig struct { + Metadata RouterMetadata + SslProfiles map[string]SslProfile + Listeners map[string]Listener + Connectors map[string]Connector + Addresses map[string]Address + LogConfig map[string]LogConfig + SiteConfig *SiteConfig + Bridges BridgeConfig +} + +type RouterConfigHandler interface { + GetRouterConfig() (*RouterConfig, error) + SaveRouterConfig(*RouterConfig) error + RemoveRouterConfig() error +} + +type TcpEndpointMap map[string]TcpEndpoint + +type BridgeConfig struct { + TcpListeners TcpEndpointMap + TcpConnectors TcpEndpointMap +} + +func InitialConfig(id string, siteId string, version string, edge bool, helloAge int) RouterConfig { + config := RouterConfig{ + Metadata: RouterMetadata{ + Id: id, + HelloMaxAgeSeconds: strconv.Itoa(helloAge), + Metadata: getSiteMetadataString(siteId, version), + }, + Addresses: map[string]Address{}, + SslProfiles: map[string]SslProfile{}, + Listeners: map[string]Listener{}, + Connectors: map[string]Connector{}, + LogConfig: map[string]LogConfig{}, + Bridges: BridgeConfig{ + TcpListeners: map[string]TcpEndpoint{}, + TcpConnectors: map[string]TcpEndpoint{}, + }, + } + if edge { + config.Metadata.Mode = ModeEdge + } else { + config.Metadata.Mode = ModeInterior + } + return config +} + +func (r *RouterConfig) AddHealthAndMetricsListener(port int32) { + r.AddListener(Listener{ + Port: port, + Role: "normal", + Http: true, + HttpRootDir: "disabled", + Websockets: false, + Healthz: true, + Metrics: true, + }) +} + +func NewBridgeConfig() BridgeConfig { + return BridgeConfig{ + TcpListeners: map[string]TcpEndpoint{}, + TcpConnectors: map[string]TcpEndpoint{}, + } +} + +func NewBridgeConfigCopy(src BridgeConfig) BridgeConfig { + newBridges := NewBridgeConfig() + for k, v := range src.TcpListeners { + newBridges.TcpListeners[k] = v + } + for k, v := range src.TcpConnectors { + newBridges.TcpConnectors[k] = v + } + return newBridges +} + +func (r *RouterConfig) AddListener(l Listener) bool { + if l.Name == "" { + l.Name = fmt.Sprintf("%s@%d", l.Host, l.Port) + } + if original, ok := r.Listeners[l.Name]; ok && original == l { + return false + } + r.Listeners[l.Name] = l + return true +} + +func (r *RouterConfig) RemoveListener(name string) (bool, Listener) { + c, ok := r.Listeners[name] + if ok { + delete(r.Listeners, name) + return true, c + } else { + return false, Listener{} + } +} + +func (r *RouterConfig) AddConnector(c Connector) bool { + if original, ok := r.Connectors[c.Name]; ok && original == c { + return false + } + r.Connectors[c.Name] = c + return true +} + +func (r *RouterConfig) RemoveConnector(name string) (bool, Connector) { + c, ok := r.Connectors[name] + if ok { + delete(r.Connectors, name) + return true, c + } else { + return false, Connector{} + } +} + +func (r *RouterConfig) IsEdge() bool { + return r.Metadata.Mode == ModeEdge +} + +const SSL_PROFILE_PATH = "/etc/skupper-router-certs" + +func ConfigureSslProfile(name string, path string, clientAuth bool) SslProfile { + profile := SslProfile{ + Name: name, + CaCertFile: path_.Join(path, name, "ca.crt"), + } + if clientAuth { + profile.CertFile = path_.Join(path, name, "tls.crt") + profile.PrivateKeyFile = path_.Join(path, name, "tls.key") + } + return profile +} + +func (r *RouterConfig) AddSslProfile(s SslProfile) bool { + if original, ok := r.SslProfiles[s.Name]; ok && original == s { + return false + } + r.SslProfiles[s.Name] = s + return true +} + +func (r *RouterConfig) RemoveSslProfile(name string) bool { + _, ok := r.SslProfiles[name] + if ok { + delete(r.SslProfiles, name) + return true + } else { + return false + } +} + +func (r *RouterConfig) RemoveUnreferencedSslProfiles() bool { + unreferenced := r.UnreferencedSslProfiles() + changed := false + for _, profile := range unreferenced { + if r.RemoveSslProfile(profile.Name) { + changed = true + } + } + return changed +} + +func (r *RouterConfig) UnreferencedSslProfiles() map[string]SslProfile { + results := map[string]SslProfile{} + for _, profile := range r.SslProfiles { + results[profile.Name] = profile + } + //remove any that are referenced + for _, o := range r.Listeners { + delete(results, o.SslProfile) + } + for _, o := range r.Connectors { + delete(results, o.SslProfile) + } + for _, o := range r.Bridges.TcpListeners { + delete(results, o.SslProfile) + } + for _, o := range r.Bridges.TcpConnectors { + delete(results, o.SslProfile) + } + + return results +} + +func (r *RouterConfig) AddAddress(a Address) { + r.Addresses[a.Prefix] = a +} + +func (r *RouterConfig) AddTcpConnector(e TcpEndpoint) { + r.Bridges.AddTcpConnector(e) +} + +func (r *RouterConfig) RemoveTcpConnector(name string) (bool, TcpEndpoint) { + return r.Bridges.RemoveTcpConnector(name) +} + +func (r *RouterConfig) AddTcpListener(e TcpEndpoint) { + r.Bridges.AddTcpListener(e) +} + +func (r *RouterConfig) RemoveTcpListener(name string) (bool, TcpEndpoint) { + return r.Bridges.RemoveTcpListener(name) +} + +func (r *RouterConfig) UpdateBridgeConfig(desired BridgeConfig) bool { + if reflect.DeepEqual(r.Bridges, desired) { + return false + } else { + r.Bridges = desired + return true + } +} + +func (r *RouterConfig) GetSiteMetadata() SiteMetadata { + return GetSiteMetadata(r.Metadata.Metadata) +} + +func (r *RouterConfig) SetSiteMetadata(site *SiteMetadata) { + r.Metadata.Metadata = getSiteMetadataString(site.Id, site.Version) +} + +func (bc *BridgeConfig) AddTcpConnector(e TcpEndpoint) { + bc.TcpConnectors[e.Name] = e +} + +func (bc *BridgeConfig) RemoveTcpConnector(name string) (bool, TcpEndpoint) { + tc, ok := bc.TcpConnectors[name] + if ok { + delete(bc.TcpConnectors, name) + return true, tc + } else { + return false, TcpEndpoint{} + } +} + +func (bc *BridgeConfig) AddTcpListener(e TcpEndpoint) { + bc.TcpListeners[e.Name] = e +} + +func (bc *BridgeConfig) RemoveTcpListener(name string) (bool, TcpEndpoint) { + tc, ok := bc.TcpListeners[name] + if ok { + delete(bc.TcpListeners, name) + return true, tc + } else { + return false, TcpEndpoint{} + } +} + +func GetTcpConnectors(bridges []BridgeConfig) []TcpEndpoint { + connectors := []TcpEndpoint{} + for _, bridge := range bridges { + for _, connector := range bridge.TcpConnectors { + connectors = append(connectors, connector) + } + } + return connectors +} + +func (r *RouterConfig) SetLogLevel(module string, level string) bool { + if level != "" { + config := LogConfig{ + Module: module, + Enable: level, + } + if module == "" { + config.Module = "DEFAULT" + } + if !strings.HasSuffix(level, "+") { + config.Enable = level + "+" + } + if r.LogConfig == nil { + r.LogConfig = map[string]LogConfig{} + } + if r.LogConfig[config.Module] != config { + r.LogConfig[config.Module] = config + return true + } + } + return false +} + +func (r *RouterConfig) SetLogLevels(levels map[string]string) bool { + keys := map[string]bool{} + for k, _ := range levels { + if k == "" { + keys["DEFAULT"] = true + } else { + keys[k] = true + } + } + changed := false + for name, level := range levels { + if r.SetLogLevel(name, level) { + changed = true + } + } + for key, _ := range r.LogConfig { + if _, ok := keys[key]; !ok { + delete(r.LogConfig, key) + changed = true + } + } + return changed +} + +type Role string + +const ( + RoleInterRouter Role = "inter-router" + RoleEdge = "edge" + RoleNormal = "normal" + RoleDefault = "" +) + +func asRole(name string) Role { + if name == "edge" { + return RoleEdge + } + if name == "inter-router" { + return RoleInterRouter + } + if name == "normal" { + return RoleNormal + } + return RoleDefault +} + +func GetRole(name string) Role { + if name == "edge" { + return RoleEdge + } else if name == "normal" { + return RoleNormal + } + return RoleInterRouter +} + +type Mode string + +const ( + ModeInterior Mode = "interior" + ModeEdge = "edge" +) + +type RouterMetadata struct { + Id string `json:"id,omitempty"` + Mode Mode `json:"mode,omitempty"` + HelloMaxAgeSeconds string `json:"helloMaxAgeSeconds,omitempty"` + DataConnectionCount string `json:"dataConnectionCount,omitempty"` + Metadata string `json:"metadata,omitempty"` +} + +type SslProfile struct { + Name string `json:"name,omitempty"` + CertFile string `json:"certFile,omitempty"` + PrivateKeyFile string `json:"privateKeyFile,omitempty"` + CaCertFile string `json:"caCertFile,omitempty"` +} + +func (p SslProfile) toRecord() Record { + result := make(map[string]any) + if p.Name != "" { + result["name"] = p.Name + } + if p.CertFile != "" { + result["certFile"] = p.CertFile + } + if p.PrivateKeyFile != "" { + result["privateKeyFile"] = p.PrivateKeyFile + } + if p.CaCertFile != "" { + result["caCertFile"] = p.CaCertFile + } + return result +} + +type LogConfig struct { + Module string `json:"module"` + Enable string `json:"enable"` +} + +type Listener struct { + Name string `json:"name,omitempty" yaml:"name,omitempty"` + Role Role `json:"role,omitempty" yaml:"role,omitempty"` + Host string `json:"host,omitempty" yaml:"host,omitempty"` + Port int32 `json:"port" yaml:"port,omitempty"` + RouteContainer bool `json:"routeContainer,omitempty" yaml:"route-container,omitempty"` + Http bool `json:"http,omitempty" yaml:"http,omitempty"` + Cost int32 `json:"cost,omitempty" yaml:"cost,omitempty"` + SslProfile string `json:"sslProfile,omitempty" yaml:"ssl-profile,omitempty"` + SaslMechanisms string `json:"saslMechanisms,omitempty" yaml:"sasl-mechanisms,omitempty"` + AuthenticatePeer bool `json:"authenticatePeer,omitempty" yaml:"authenticate-peer,omitempty"` + LinkCapacity int32 `json:"linkCapacity,omitempty" yaml:"link-capacity,omitempty"` + HttpRootDir string `json:"httpRootDir,omitempty" yaml:"http-rootdir,omitempty"` + Websockets bool `json:"websockets,omitempty" yaml:"web-sockets,omitempty"` + Healthz bool `json:"healthz,omitempty" yaml:"healthz,omitempty"` + Metrics bool `json:"metrics,omitempty" yaml:"metrics,omitempty"` + MaxFrameSize int `json:"maxFrameSize,omitempty" yaml:"max-frame-size,omitempty"` + MaxSessionFrames int `json:"maxSessionFrames,omitempty" yaml:"max-session-frames,omitempty"` +} + +func (listener Listener) toRecord() Record { + record := map[string]any{} + record["name"] = listener.Name + record["role"] = string(listener.Role) + record["host"] = listener.Host + record["port"] = strconv.Itoa(int(listener.Port)) + if listener.Cost > 0 { + record["cost"] = listener.Cost + } + if listener.LinkCapacity > 0 { + record["linkCapacity"] = listener.LinkCapacity + } + if len(listener.SslProfile) > 0 { + record["sslProfile"] = listener.SslProfile + } + if listener.AuthenticatePeer { + record["authenticatePeer"] = listener.AuthenticatePeer + } + if len(listener.SaslMechanisms) > 0 { + record["saslMechanisms"] = listener.SaslMechanisms + } + if listener.MaxFrameSize > 0 { + record["maxFrameSize"] = listener.MaxFrameSize + } + if listener.MaxSessionFrames > 0 { + record["maxSessionFrames"] = listener.MaxSessionFrames + } + if listener.RouteContainer { + record["routeContainer"] = listener.RouteContainer + } + if listener.Http { + record["http"] = listener.Http + } + if len(listener.HttpRootDir) > 0 { + record["httpRootDir"] = listener.HttpRootDir + } + if listener.Websockets { + record["websockets"] = listener.Websockets + } + if listener.Healthz { + record["healthz"] = listener.Healthz + } + if listener.Metrics { + record["metrics"] = listener.Metrics + } + + return record +} +func (l *Listener) SetMaxFrameSize(value int) { + l.MaxFrameSize = value +} + +func (l *Listener) SetMaxSessionFrames(value int) { + l.MaxSessionFrames = value +} + +type Connector struct { + Name string `json:"name,omitempty"` + Role Role `json:"role,omitempty"` + Host string `json:"host"` + Port string `json:"port"` + RouteContainer bool `json:"routeContainer,omitempty"` + Cost int32 `json:"cost,omitempty"` + VerifyHostname bool `json:"verifyHostname,omitempty"` + SslProfile string `json:"sslProfile,omitempty"` + LinkCapacity int32 `json:"linkCapacity,omitempty"` + MaxFrameSize int `json:"maxFrameSize,omitempty"` + MaxSessionFrames int `json:"maxSessionFrames,omitempty"` +} + +func (connector Connector) toRecord() Record { + record := map[string]any{} + record["name"] = connector.Name + record["role"] = string(connector.Role) + record["host"] = connector.Host + record["port"] = connector.Port + if connector.Cost > 0 { + record["cost"] = connector.Cost + } + if len(connector.SslProfile) > 0 { + record["sslProfile"] = connector.SslProfile + } + if connector.MaxFrameSize > 0 { + record["maxFrameSize"] = connector.MaxFrameSize + } + if connector.MaxSessionFrames > 0 { + record["maxSessionFrames"] = connector.MaxSessionFrames + } + return record +} + +func (c *Connector) SetMaxFrameSize(value int) { + c.MaxFrameSize = value +} + +func (c *Connector) SetMaxSessionFrames(value int) { + c.MaxSessionFrames = value +} + +type Distribution string + +const ( + DistributionBalanced Distribution = "balanced" + DistributionMulticast = "multicast" + DistributionClosest = "closest" +) + +type Address struct { + Prefix string `json:"prefix,omitempty"` + Distribution string `json:"distribution,omitempty"` +} + +type TcpEndpoint struct { + Name string `json:"name,omitempty"` + Host string `json:"host,omitempty"` + Port string `json:"port,omitempty"` + Address string `json:"address,omitempty"` + SiteId string `json:"siteId,omitempty"` + SslProfile string `json:"sslProfile,omitempty"` + VerifyHostname *bool `json:"verifyHostname,omitempty"` + ProcessID string `json:"processId,omitempty"` +} + +func (e TcpEndpoint) toRecord() Record { + result := make(map[string]any) + if e.Name != "" { + result["name"] = e.Name + } + if e.Host != "" { + result["host"] = e.Host + } + if e.Port != "" { + result["port"] = e.Port + } + if e.Address != "" { + result["address"] = e.Address + } + if e.SiteId != "" { + result["siteId"] = e.SiteId + } + if e.SslProfile != "" { + result["sslProfile"] = e.SslProfile + } + if e.VerifyHostname != nil { + result["verifyHostname"] = e.VerifyHostname + } + if e.ProcessID != "" { + result["processId"] = e.ProcessID + } + return result +} + +type SiteConfig struct { + Name string `json:"name,omitempty"` + Location string `json:"location,omitempty"` + Provider string `json:"provider,omitempty"` + Platform string `json:"platform,omitempty"` + Namespace string `json:"namespace,omitempty"` + Version string `json:"version,omitempty"` +} + +func convert(from interface{}, to interface{}) error { + data, err := json.Marshal(from) + if err != nil { + return err + } + err = json.Unmarshal(data, to) + if err != nil { + return err + } + return nil +} + +func RouterConfigEquals(actual, desired string) bool { + actualConfig, err := UnmarshalRouterConfig(actual) + if err != nil { + return false + } + desiredConfig, err := UnmarshalRouterConfig(desired) + if err != nil { + return false + } + return reflect.DeepEqual(actualConfig, desiredConfig) +} + +func UnmarshalRouterConfig(config string) (RouterConfig, error) { + result := RouterConfig{ + Metadata: RouterMetadata{}, + Addresses: map[string]Address{}, + SslProfiles: map[string]SslProfile{}, + Listeners: map[string]Listener{}, + Connectors: map[string]Connector{}, + LogConfig: map[string]LogConfig{}, + Bridges: BridgeConfig{ + TcpListeners: map[string]TcpEndpoint{}, + TcpConnectors: map[string]TcpEndpoint{}, + }, + } + var obj interface{} + err := json.Unmarshal([]byte(config), &obj) + if err != nil { + return result, err + } + elements, ok := obj.([]interface{}) + if !ok { + return result, fmt.Errorf("Invalid JSON for router configuration, expected array at top level got %#v", obj) + } + for _, e := range elements { + element, ok := e.([]interface{}) + if !ok || len(element) != 2 { + return result, fmt.Errorf("Invalid JSON for router configuration, expected array with type and value got %#v", e) + } + entityType, ok := element[0].(string) + if !ok { + return result, fmt.Errorf("Invalid JSON for router configuration, expected entity type as string got %#v", element[0]) + } + switch entityType { + case "router": + metadata := RouterMetadata{} + err = convert(element[1], &metadata) + if err != nil { + return result, fmt.Errorf("Invalid %s element got %#v", entityType, element[1]) + } + result.Metadata = metadata + case "address": + address := Address{} + err = convert(element[1], &address) + if err != nil { + return result, fmt.Errorf("Invalid %s element got %#v", entityType, element[1]) + } + result.Addresses[address.Prefix] = address + case "connector": + connector := Connector{} + err = convert(element[1], &connector) + if err != nil { + return result, fmt.Errorf("Invalid %s element got %#v", entityType, element[1]) + } + result.Connectors[connector.Name] = connector + case "listener": + listener := Listener{} + err = convert(element[1], &listener) + if err != nil { + return result, fmt.Errorf("Invalid %s element got %#v", entityType, element[1]) + } + result.Listeners[listener.Name] = listener + case "sslProfile": + sslProfile := SslProfile{} + err = convert(element[1], &sslProfile) + if err != nil { + return result, fmt.Errorf("Invalid %s element got %#v", entityType, element[1]) + } + result.SslProfiles[sslProfile.Name] = sslProfile + case "log": + logConfig := LogConfig{} + err = convert(element[1], &logConfig) + if err != nil { + return result, fmt.Errorf("Invalid %s element got %#v", entityType, element[1]) + } + result.LogConfig[logConfig.Module] = logConfig + case "site": + siteConfig := &SiteConfig{} + err = convert(element[1], siteConfig) + if err != nil { + return result, fmt.Errorf("Invalid %s element got %#v", entityType, element[1]) + } + result.SiteConfig = siteConfig + case "tcpConnector": + connector := TcpEndpoint{} + err = convert(element[1], &connector) + if err != nil { + return result, fmt.Errorf("Invalid %s element got %#v", entityType, element[1]) + } + result.Bridges.TcpConnectors[connector.Name] = connector + case "tcpListener": + listener := TcpEndpoint{} + err = convert(element[1], &listener) + if err != nil { + return result, fmt.Errorf("Invalid %s element got %#v", entityType, element[1]) + } + result.Bridges.TcpListeners[listener.Name] = listener + default: + } + } + return result, nil +} + +func MarshalRouterConfig(config RouterConfig) (string, error) { + elements := [][]interface{}{} + tuple := []interface{}{ + "router", + config.Metadata, + } + elements = append(elements, tuple) + for _, e := range config.SslProfiles { + tuple := []interface{}{ + "sslProfile", + e, + } + elements = append(elements, tuple) + } + for _, e := range config.Connectors { + tuple := []interface{}{ + "connector", + e, + } + elements = append(elements, tuple) + } + for _, e := range config.Listeners { + tuple := []interface{}{ + "listener", + e, + } + elements = append(elements, tuple) + } + for _, e := range config.Addresses { + tuple := []interface{}{ + "address", + e, + } + elements = append(elements, tuple) + } + for _, e := range config.Bridges.TcpConnectors { + tuple := []interface{}{ + "tcpConnector", + e, + } + elements = append(elements, tuple) + } + for _, e := range config.Bridges.TcpListeners { + tuple := []interface{}{ + "tcpListener", + e, + } + elements = append(elements, tuple) + } + for _, e := range config.LogConfig { + tuple := []interface{}{ + "log", + e, + } + elements = append(elements, tuple) + } + if config.SiteConfig != nil { + tuple := []interface{}{ + "site", + *config.SiteConfig, + } + elements = append(elements, tuple) + } + data, err := json.MarshalIndent(elements, "", " ") + if err != nil { + return "", err + } + return string(data), nil +} + +func AsConfigMapData(config string) map[string]string { + return map[string]string{ + types.TransportConfigFile: config, + } +} + +func (r *RouterConfig) AsConfigMapData() (map[string]string, error) { + result := map[string]string{} + marshalled, err := MarshalRouterConfig(*r) + if err != nil { + return result, err + } + result[types.TransportConfigFile] = marshalled + return result, nil +} + +func (r *RouterConfig) WriteToConfigMap(configmap *corev1.ConfigMap) error { + var err error + configmap.Data, err = r.AsConfigMapData() + return err +} + +func (r *RouterConfig) UpdateConfigMap(configmap *corev1.ConfigMap) (bool, error) { + if configmap.Data != nil && configmap.Data[types.TransportConfigFile] != "" { + existing, err := UnmarshalRouterConfig(configmap.Data[types.TransportConfigFile]) + if err != nil { + return false, err + } + if reflect.DeepEqual(existing, *r) { + return false, nil + } + } + err := r.WriteToConfigMap(configmap) + if err != nil { + return false, err + } + return true, nil +} + +type ListenerPredicate func(Listener) bool + +func IsNotNormalListener(l Listener) bool { + return l.Role != "normal" && l.Role != "" +} + +func FilterListeners(in map[string]Listener, predicate ListenerPredicate) map[string]Listener { + results := map[string]Listener{} + for key, listener := range in { + if predicate(listener) { + results[key] = listener + } + } + return results +} + +func (config *RouterConfig) GetMatchingListeners(predicate ListenerPredicate) map[string]Listener { + return FilterListeners(config.Listeners, predicate) +} + +func (b *BridgeConfig) UpdateConfigMap(configmap *corev1.ConfigMap) (bool, error) { + if configmap.Data != nil && configmap.Data[types.TransportConfigFile] != "" { + existing, err := UnmarshalRouterConfig(configmap.Data[types.TransportConfigFile]) + if err != nil { + return false, err + } + if reflect.DeepEqual(existing.Bridges, *b) { + return false, nil + } else { + existing.Bridges = *b + configmap.Data, err = existing.AsConfigMapData() + if err != nil { + return false, err + } + return true, nil + } + } else { + return false, fmt.Errorf("Router config not defined") + } +} + +func GetRouterConfigFromConfigMap(configmap *corev1.ConfigMap) (*RouterConfig, error) { + if configmap.Data == nil || configmap.Data[types.TransportConfigFile] == "" { + return nil, nil + } else { + routerConfig, err := UnmarshalRouterConfig(configmap.Data[types.TransportConfigFile]) + if err != nil { + return nil, err + } + return &routerConfig, nil + } +} + +func GetBridgeConfigFromConfigMap(configmap *corev1.ConfigMap) (*BridgeConfig, error) { + routerConfig, err := GetRouterConfigFromConfigMap(configmap) + if err != nil { + return nil, err + } + return &routerConfig.Bridges, nil +} + +type ConnectorDifference struct { + Deleted []Connector + Added []Connector + AddedSslProfiles map[string]SslProfile +} + +type TcpEndpointDifference struct { + Deleted []string + Added []TcpEndpoint +} + +type BridgeConfigDifference struct { + TcpListeners TcpEndpointDifference + TcpConnectors TcpEndpointDifference + AddedSslProfiles []string + DeletedSSlProfiles []string +} + +func isAddrAny(host string) bool { + ip := net.ParseIP(host) + return ip.Equal(net.IPv4zero) || ip.Equal(net.IPv6zero) +} + +func equivalentHost(a string, b string) bool { + if a == b { + return true + } else if a == "" { + return isAddrAny(b) + } else if b == "" { + return isAddrAny(a) + } else { + return false + } +} + +func (a TcpEndpoint) equivalentVerifyHostname(b TcpEndpoint) bool { + if a.VerifyHostname == nil { + return b.VerifyHostname == nil || *b.VerifyHostname == true + } + if b.VerifyHostname == nil { + return a.VerifyHostname == nil || *a.VerifyHostname == true + } + return *a.VerifyHostname == *b.VerifyHostname +} + +func (a TcpEndpoint) Equivalent(b TcpEndpoint) bool { + if !equivalentHost(a.Host, b.Host) || a.Port != b.Port || a.Address != b.Address || + a.SiteId != b.SiteId || a.ProcessID != b.ProcessID || !a.equivalentVerifyHostname(b) { + return false + } + return true +} + +func (a TcpEndpointMap) Difference(b TcpEndpointMap) TcpEndpointDifference { + result := TcpEndpointDifference{} + for key, v1 := range b { + v2, ok := a[key] + if !ok { + result.Added = append(result.Added, v1) + } else if !v1.Equivalent(v2) { + result.Deleted = append(result.Deleted, v1.Name) + result.Added = append(result.Added, v1) + } + } + for key, v1 := range a { + _, ok := b[key] + if !ok { + result.Deleted = append(result.Deleted, v1.Name) + } + } + return result +} + +func (a *BridgeConfig) Difference(b *BridgeConfig) *BridgeConfigDifference { + result := BridgeConfigDifference{ + TcpConnectors: a.TcpConnectors.Difference(b.TcpConnectors), + TcpListeners: a.TcpListeners.Difference(b.TcpListeners), + } + + result.AddedSslProfiles, result.DeletedSSlProfiles = getSslProfilesDifference(a, b) + + return &result +} + +type AddedSslProfiles []string +type DeletedSslProfiles []string + +func getSslProfilesDifference(before *BridgeConfig, desired *BridgeConfig) (AddedSslProfiles, DeletedSslProfiles) { + var addedProfiles AddedSslProfiles + var deletedProfiles DeletedSslProfiles + + originalSslConfig := make(map[string]string) + newSslConfig := make(map[string]string) + + for _, tcpConnector := range before.TcpConnectors { + originalSslConfig[tcpConnector.SslProfile] = tcpConnector.SslProfile + } + for _, tcpListener := range before.TcpListeners { + originalSslConfig[tcpListener.SslProfile] = tcpListener.SslProfile + } + + for _, tcpConnector := range desired.TcpConnectors { + newSslConfig[tcpConnector.SslProfile] = tcpConnector.SslProfile + } + for _, tcpListener := range desired.TcpListeners { + newSslConfig[tcpListener.SslProfile] = tcpListener.SslProfile + } + + //Auto-generated Skupper certs will be deleted if they are not used in the desired configuration + for key, name := range originalSslConfig { + _, ok := newSslConfig[key] + + if !ok && isGeneratedBySkupper(name) { + deletedProfiles = append(deletedProfiles, name) + } + } + + //New profiles associated with http or tcp connectors/listeners will be created in the router + for key, name := range newSslConfig { + _, ok := originalSslConfig[key] + + if !ok && name != types.ServiceClientSecret { + addedProfiles = append(addedProfiles, name) + } + } + + return addedProfiles, deletedProfiles +} + +func isGeneratedBySkupper(name string) bool { + return strings.HasPrefix(name, types.SkupperServiceCertPrefix) && name != types.ServiceClientSecret +} + +func (a *TcpEndpointDifference) Empty() bool { + return len(a.Deleted) == 0 && len(a.Added) == 0 +} + +func (a *BridgeConfigDifference) Empty() bool { + return a.TcpConnectors.Empty() && a.TcpListeners.Empty() +} + +func (a *BridgeConfigDifference) Print() { + log.Printf("TcpConnectors added=%v, deleted=%v", a.TcpConnectors.Added, a.TcpConnectors.Deleted) + log.Printf("TcpListeners added=%v, deleted=%v", a.TcpListeners.Added, a.TcpListeners.Deleted) + log.Printf("SslProfiles added=%v, deleted=%v", a.AddedSslProfiles, a.DeletedSSlProfiles) +} + +func ConnectorsDifference(actual map[string]Connector, desired *RouterConfig, ignorePrefix *string) *ConnectorDifference { + result := ConnectorDifference{} + result.AddedSslProfiles = make(map[string]SslProfile) + for key, v1 := range desired.Connectors { + _, ok := actual[key] + if !ok { + result.Added = append(result.Added, v1) + result.AddedSslProfiles[v1.SslProfile] = desired.SslProfiles[v1.SslProfile] + } + } + for key, v1 := range actual { + _, ok := desired.Connectors[key] + + allowedToDelete := true + if ignorePrefix != nil && len(*ignorePrefix) > 0 { + allowedToDelete = !strings.HasPrefix(v1.Name, *ignorePrefix) + } + + if !ok && allowedToDelete { + result.Deleted = append(result.Deleted, v1) + } + } + return &result +} + +func (a *ConnectorDifference) Empty() bool { + return len(a.Deleted) == 0 && len(a.Added) == 0 +} + +type ListenerDifference struct { + Deleted []Listener + Added []Listener +} + +func (desired Listener) Equivalent(actual Listener) bool { + return desired.Name == actual.Name && + desired.Role == actual.Role && + desired.Host == actual.Host && + desired.Port == actual.Port && + desired.RouteContainer == actual.RouteContainer && + desired.Http == actual.Http && + desired.SslProfile == actual.SslProfile && + desired.SaslMechanisms == actual.SaslMechanisms && + desired.AuthenticatePeer == actual.AuthenticatePeer && + (desired.Cost == 0 || desired.Cost == actual.Cost) && + (desired.MaxFrameSize == 0 || desired.MaxFrameSize == actual.MaxFrameSize) && + (desired.MaxSessionFrames == 0 || desired.MaxSessionFrames == actual.MaxSessionFrames) && + (desired.LinkCapacity == 0 || desired.LinkCapacity == actual.LinkCapacity) && + (desired.HttpRootDir == "" || desired.HttpRootDir == actual.HttpRootDir) + //Skip check for Websockets, Healthz and Metrics as they are + //always coming back as true at present and are not used where + //this method is required at present. +} + +func ListenersDifference(actual map[string]Listener, desired map[string]Listener) *ListenerDifference { + result := ListenerDifference{} + for key, desiredValue := range desired { + if actualValue, ok := actual[key]; ok { + if !desiredValue.Equivalent(actualValue) { + log.Printf("Listener definition does not match. Have %v want %v", actualValue, desiredValue) + // handle change as delete then add, so it also works over management protocol + result.Deleted = append(result.Deleted, desiredValue) + result.Added = append(result.Added, desiredValue) + } + } else { + result.Added = append(result.Added, desiredValue) + } + } + for key, value := range actual { + if _, ok := desired[key]; !ok { + result.Deleted = append(result.Deleted, value) + } + } + return &result +} + +func (a *ListenerDifference) Empty() bool { + return len(a.Deleted) == 0 && len(a.Added) == 0 +} + +// func GetRouterConfigForHeadlessProxy(definition types.ServiceInterface, siteId string, version string, namespace string, profilePath string) (string, error) { +// config := InitialConfig("${HOSTNAME}-"+siteId, siteId, version, true, 3) +// // add edge-connector +// config.AddSslProfile(ConfigureSslProfile(types.InterRouterProfile, profilePath, true)) +// config.AddConnector(Connector{ +// Name: "uplink", +// SslProfile: types.InterRouterProfile, +// Host: types.TransportServiceName + "." + namespace + ".svc.cluster.local", +// Port: strconv.Itoa(int(types.EdgeListenerPort)), +// Role: RoleEdge, +// }) +// config.AddListener(Listener{ +// Name: "amqp", +// Host: "localhost", +// Port: 5672, +// }) +// svcPorts := definition.Ports +// ports := map[int]int{} +// if len(definition.Targets) > 0 { +// ports = definition.Targets[0].TargetPorts +// } else { +// for _, sp := range svcPorts { +// ports[sp] = sp +// } +// } +// for iPort, ePort := range ports { +// address := fmt.Sprintf("%s-%s:%d", definition.Address, "${POD_ID}", iPort) +// if definition.IsOfLocalOrigin() { +// name := fmt.Sprintf("egress:%d", ePort) +// host := definition.Headless.Name + "-${POD_ID}." + definition.Address + "." + namespace +// // in the originating site, just have egress bindings +// switch definition.Protocol { +// case "tcp": +// config.AddTcpConnector(TcpEndpoint{ +// Name: name, +// Host: host, +// Port: strconv.Itoa(ePort), +// Address: address, +// SiteId: siteId, +// }) +// default: +// } +// } else { +// name := fmt.Sprintf("ingress:%d", ePort) +// // in all other sites, just have ingress bindings +// switch definition.Protocol { +// case "tcp": +// config.AddTcpListener(TcpEndpoint{ +// Name: name, +// Port: strconv.Itoa(iPort), +// Address: address, +// SiteId: siteId, +// }) +// default: +// } +// } +// } +// return MarshalRouterConfig(config) +// } + +func disableMutualTLS(l *Listener) { + l.SaslMechanisms = "" + l.AuthenticatePeer = false +} + +func InteriorListener(options types.RouterOptions) Listener { + l := Listener{ + Name: "interior-listener", + Role: RoleInterRouter, + Port: types.InterRouterListenerPort, + SslProfile: types.InterRouterProfile, // The skupper-internal profile needs to be filtered by the config-sync sidecar, in order to avoid deleting automesh connectors + SaslMechanisms: "EXTERNAL", + AuthenticatePeer: true, + MaxFrameSize: options.MaxFrameSize, + MaxSessionFrames: options.MaxSessionFrames, + } + if options.DisableMutualTLS { + disableMutualTLS(&l) + } + return l +} + +func EdgeListener(options types.RouterOptions) Listener { + l := Listener{ + Name: "edge-listener", + Role: RoleEdge, + Port: types.EdgeListenerPort, + SslProfile: types.InterRouterProfile, + SaslMechanisms: "EXTERNAL", + AuthenticatePeer: true, + MaxFrameSize: options.MaxFrameSize, + MaxSessionFrames: options.MaxSessionFrames, + } + if options.DisableMutualTLS { + disableMutualTLS(&l) + } + return l +} + +func GetInterRouterOrEdgeConnection(host string, connections []Connection) *Connection { + for _, c := range connections { + if (c.Role == "inter-router" || c.Role == "edge") && c.Host == host { + return &c + } + } + return nil +} + +func GetLinkStatus(s *corev1.Secret, edge bool, connections []Connection) types.LinkStatus { + link := types.LinkStatus{ + Name: s.ObjectMeta.Name, + } + if s.ObjectMeta.Labels[types.SkupperTypeQualifier] == types.TypeClaimRequest { + link.Url = s.ObjectMeta.Annotations[types.ClaimUrlAnnotationKey] + if desc, ok := s.ObjectMeta.Annotations[types.StatusAnnotationKey]; ok { + link.Description = "Failed to redeem claim: " + desc + } + link.Configured = false + } else { + if edge { + link.Url = fmt.Sprintf("%s:%s", s.ObjectMeta.Annotations["edge-host"], s.ObjectMeta.Annotations["edge-port"]) + } else { + link.Url = fmt.Sprintf("%s:%s", s.ObjectMeta.Annotations["inter-router-host"], s.ObjectMeta.Annotations["inter-router-port"]) + } + link.Configured = true + if connection := GetInterRouterOrEdgeConnection(link.Url, connections); connection != nil && connection.Active { + link.Connected = true + link.Cost, _ = strconv.Atoi(s.ObjectMeta.Annotations[types.TokenCost]) + link.Created = s.ObjectMeta.CreationTimestamp.String() + } + if s.ObjectMeta.Labels[types.SkupperDisabledQualifier] == "true" { + link.Description = "Destination host is not allowed" + } + } + return link +} + +type ConfigUpdate interface { + Apply(config *RouterConfig) bool +} diff --git a/internal/qdr/request.go b/internal/qdr/request.go new file mode 100644 index 0000000..6ef44bb --- /dev/null +++ b/internal/qdr/request.go @@ -0,0 +1,125 @@ +package qdr + +import ( + "context" + "fmt" + + amqp "github.com/interconnectedcloud/go-amqp" +) + +const ( + VersionProperty string = "version" +) + +type Request struct { + Address string + Type string + Version string + Properties map[string]interface{} + Body string +} + +type Response struct { + Type string + Version string + Properties map[string]interface{} + Body string +} + +type RequestResponse interface { + Request(request *Request) (*Response, error) +} + +type RequestServer struct { + pool *AgentPool + address string + handler RequestResponse +} + +func NewRequestServer(address string, handler RequestResponse, pool *AgentPool) *RequestServer { + return &RequestServer{ + pool, address, handler, + } +} + +func (s *RequestServer) Run(ctx context.Context) error { + agent, err := s.pool.Get() + if err != nil { + return fmt.Errorf("Could not get management agent: %s", err) + } + defer agent.Close() + + receiver, err := agent.newReceiver(s.address) + if err != nil { + return fmt.Errorf("Could not open receiver for %s: %s", s.address, err) + } + for { + err = s.serve(ctx, receiver, agent.anonymous) + if err != nil { + return fmt.Errorf("Error handling request for %s: %s", s.address, err) + } + } +} + +func (s *RequestServer) serve(ctx context.Context, receiver *amqp.Receiver, sender *amqp.Sender) error { + for { + requestMsg, err := receiver.Receive(ctx) + if err != nil { + return fmt.Errorf("Failed reading request from %s: %s", s.address, err.Error()) + } + + request := Request{ + Address: requestMsg.Properties.To, + Type: requestMsg.Properties.Subject, + Properties: map[string]interface{}{}, + } + for k, v := range requestMsg.ApplicationProperties { + if k == VersionProperty { + if version, ok := v.(string); ok { + request.Version = version + } + } else { + request.Properties[k] = v + } + } + if body, ok := requestMsg.Value.(string); ok { + request.Body = body + } + + response, err := s.handler.Request(&request) + if err != nil { + requestMsg.Reject(&amqp.Error{ + Condition: amqp.ErrorInternalError, + Description: err.Error(), + }) + return err + } else { + requestMsg.Accept() + responseMsg := amqp.Message{ + Properties: &amqp.MessageProperties{ + To: requestMsg.Properties.ReplyTo, + Subject: response.Type, + }, + ApplicationProperties: map[string]interface{}{}, + Value: response.Body, + } + correlationId, ok := AsUint64(requestMsg.Properties.CorrelationID) + if !ok { + responseMsg.Properties.CorrelationID = correlationId + } + for k, v := range response.Properties { + responseMsg.ApplicationProperties[k] = v + } + responseMsg.ApplicationProperties[VersionProperty] = response.Version + + err = sender.Send(ctx, &responseMsg) + if err != nil { + requestMsg.Reject(&amqp.Error{ + Condition: amqp.ErrorInternalError, + Description: "Could not send response: " + err.Error(), + }) + return fmt.Errorf("Could not send response: %s", err) + } + } + } +} diff --git a/internal/qdr/router_logging.go b/internal/qdr/router_logging.go new file mode 100644 index 0000000..73a9cb6 --- /dev/null +++ b/internal/qdr/router_logging.go @@ -0,0 +1,129 @@ +package qdr + +import ( + "fmt" + "strings" + + "github.com/datasance/router/internal/resources/types" +) + +func RouterLogConfigToString(config []types.RouterLogConfig) string { + items := []string{} + for _, l := range config { + if l.Module != "" && l.Level != "" { + items = append(items, l.Module+":"+l.Level) + } else if l.Level != "" { + items = append(items, l.Level) + } + } + return strings.Join(items, ",") +} + +func ConfigureRouterLogging(routerConfig *RouterConfig, logConfig []types.RouterLogConfig) bool { + levels := map[string]string{} + for _, l := range logConfig { + levels[l.Module] = l.Level + } + return routerConfig.SetLogLevels(levels) +} + +func GetRouterLogging(routerConfig *RouterConfig) []types.RouterLogConfig { + var ret []types.RouterLogConfig + if routerConfig == nil || len(routerConfig.LogConfig) == 0 { + return ret + } + for module, logConfig := range routerConfig.LogConfig { + lc := types.RouterLogConfig{Level: logConfig.Enable} + if module != "DEFAULT" || len(routerConfig.LogConfig) > 1 { + lc.Module = logConfig.Module + } + ret = append(ret, lc) + } + return ret +} + +func ParseRouterLogConfig(config string) ([]types.RouterLogConfig, error) { + items := strings.Split(config, ",") + parsed := []types.RouterLogConfig{} + for _, item := range items { + parts := strings.Split(item, ":") + var mod string + var level string + if len(parts) > 1 { + mod = parts[0] + level = parts[1] + } else if len(parts) > 0 { + level = parts[0] + } + err := checkLoggingModule(mod) + if err != nil { + return nil, err + } + err = checkLoggingLevel(level) + if err != nil { + return nil, err + } + parsed = append(parsed, types.RouterLogConfig{ + Module: mod, + Level: level, + }) + } + return parsed, nil +} + +var LoggingModules []string = []string{ + "", /*implies DEFAULT*/ + "ROUTER", + "ROUTER_CORE", + "ROUTER_HELLO", + "ROUTER_LS", + "ROUTER_MA", + "MESSAGE", + "SERVER", + "AGENT", + "AUTHSERVICE", + "CONTAINER", + "ERROR", + "POLICY", + "HTTP", + "CONN_MGR", + "PYTHON", + "PROTOCOL", + "TCP_ADAPTOR", + "HTTP_ADAPTOR", + "DEFAULT", +} +var LoggingLevels []string = []string{ + "trace", + "debug", + "info", + "notice", + "warning", + "error", + "critical", + "trace+", + "debug+", + "info+", + "notice+", + "warning+", + "error+", + "critical+", +} + +func checkLoggingModule(mod string) error { + for _, m := range LoggingModules { + if mod == m { + return nil + } + } + return fmt.Errorf("Invalid logging module for router: %s", mod) +} + +func checkLoggingLevel(level string) error { + for _, l := range LoggingLevels { + if level == l { + return nil + } + } + return fmt.Errorf("Invalid logging level for router: %s", level) +} diff --git a/internal/resources/types/types.go b/internal/resources/types/types.go new file mode 100644 index 0000000..ecd9e70 --- /dev/null +++ b/internal/resources/types/types.go @@ -0,0 +1,409 @@ +/* +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package types + +import ( + "time" +) + +const ( + ENV_PLATFORM = "SKUPPER_PLATFORM" +) + +const ( + // NamespaceDefault means the VAN is in the skupper namespace which is applied when not specified by clients + NamespaceDefault string = "skupper" + DefaultVanName string = "skupper" + DefaultSiteName string = "skupper-site" + ClusterLocalPostfix string = ".svc.cluster.local" + SiteConfigMapName string = "skupper-site" + NetworkStatusConfigMapName string = "skupper-network-status" + SiteLeaderLockName string = "skupper-site-leader" +) + +const DefaultTimeoutDuration = time.Second * 120 + +// TransportMode describes how a qdr is intended to be deployed, either interior or edge +type TransportMode string + +const ( + // TransportModeInterior means the qdr will participate in inter-router protocol exchanges + TransportModeInterior TransportMode = "interior" + // TransportModeEdge means that the qdr will connect to interior routers for network access + TransportModeEdge TransportMode = "edge" +) + +// Transport constants +const ( + TransportDeploymentName string = "skupper-router" + TransportComponentName string = "router" + TransportContainerName string = "router" + ConfigSyncContainerName string = "config-sync" + TransportLivenessPort int32 = 9090 + TransportServiceAccountName string = "skupper-router" + TransportRoleName string = "skupper-router" + TransportRoleBindingName string = "skupper-router" + TransportEnvConfig string = "QDROUTERD_CONF" + TransportSaslConfig string = "skupper-sasl-config" + TransportConfigFile string = "skrouterd.json" + TransportConfigMapName string = "skupper-internal" + TransportServiceName string = "skupper-router" + LocalTransportServiceName string = "skupper-router-local" + RouterMaxFrameSizeDefault int = 16384 + RouterMaxSessionFramesDefault int = 640 +) + + +// Controller and Collector constants +const ( + ControllerDeploymentName string = "skupper-service-controller" + ControllerComponentName string = "service-controller" + ControllerContainerName string = "service-controller" + ControllerServiceAccountName string = "skupper-service-controller" + ControllerRoleBindingName string = "skupper-service-controller" + ControllerClusterRoleBindingNsFormat string = "skupper-service-controller-%s" + ControllerRoleName string = "skupper-service-controller" + ControllerClusterRoleName string = "skupper-service-controller-basic" + ControllerExtendedClusterRoleName string = "skupper-service-controller-extended" + ControllerConfigPath string = "/etc/messaging/" + ControllerServiceName string = "skupper" + ControllerPodmanContainerName string = "skupper-controller-podman" + FlowCollectorContainerName string = "flow-collector" + PrometheusDeploymentName string = "skupper-prometheus" + PrometheusComponentName string = "prometheus" + PrometheusContainerName string = "prometheus-server" + PrometheusServiceAccountName string = "skupper-prometheus" + PrometheusServiceName string = "skupper-prometheus" + PrometheusRoleBindingName string = "skupper-prometheus" + PrometheusRoleName string = "skupper-prometheus" +) + +// Certificates/Secrets constants +const ( + LocalClientSecret string = "skupper-local-client" + LocalServerSecret string = "skupper-local-server" + LocalCaSecret string = "skupper-local-ca" + SiteServerSecret string = "skupper-site-server" + SiteCaSecret string = "skupper-site-ca" + ConsoleServerSecret string = "skupper-console-certs" + ConsoleUsersSecret string = "skupper-console-users" + PrometheusServerSecret string = "skupper-prometheus-certs" + OauthRouterConsoleSecret string = "skupper-router-console-certs" + ServiceCaSecret string = "skupper-service-ca" + ServiceClientSecret string = "skupper-service-client" // Secret that is used in sslProfiles for all http2 connectors with tls enabled +) + +// Skupper qualifiers +const ( + BaseQualifier string = "skupper.io" + InternalQualifier string = "internal." + BaseQualifier + AddressQualifier string = BaseQualifier + "/address" + PortQualifier string = BaseQualifier + "/port" + ProxyQualifier string = BaseQualifier + "/proxy" + TargetServiceQualifier string = BaseQualifier + "/target" + HeadlessQualifier string = BaseQualifier + "/headless" + IngressModeQualifier string = BaseQualifier + "/ingress" + CpuRequestAnnotation string = BaseQualifier + "/cpu-request" + MemoryRequestAnnotation string = BaseQualifier + "/memory-request" + CpuLimitAnnotation string = BaseQualifier + "/cpu-limit" + MemoryLimitAnnotation string = BaseQualifier + "/memory-limit" + AffinityAnnotation string = BaseQualifier + "/affinity" + AntiAffinityAnnotation string = BaseQualifier + "/anti-affinity" + NodeSelectorAnnotation string = BaseQualifier + "/node-selector" + ControlledQualifier string = InternalQualifier + "/controlled" + ServiceQualifier string = InternalQualifier + "/service" + OriginQualifier string = InternalQualifier + "/origin" + OriginalSelectorQualifier string = InternalQualifier + "/originalSelector" + OriginalTargetPortQualifier string = InternalQualifier + "/originalTargetPort" + OriginalAssignedQualifier string = InternalQualifier + "/originalAssignedPort" + InternalTypeQualifier string = InternalQualifier + "/type" + InternalMetadataQualifier string = InternalQualifier + "/metadata" + SkupperTypeQualifier string = BaseQualifier + "/type" + TypeProxyQualifier string = InternalTypeQualifier + "=proxy" + SkupperDisabledQualifier string = InternalQualifier + "/disabled" + TypeToken string = "connection-token" + TypeClaimRecord string = "token-claim-record" + TypeClaimRequest string = "token-claim" + TypeGatewayToken string = "gateway-connection-token" + TypeTokenQualifier string = BaseQualifier + "/type=connection-token" + TypeTokenRequestQualifier string = BaseQualifier + "/type=connection-token-request" + TokenGeneratedBy string = BaseQualifier + "/generated-by" + SiteVersion string = BaseQualifier + "/site-version" + SiteId string = BaseQualifier + "/site-id" + TokenCost string = BaseQualifier + "/cost" + TokenTemplate string = BaseQualifier + "/token-template" + UpdatedAnnotation string = InternalQualifier + "/updated" + AnnotationExcludes string = BaseQualifier + "/exclude-annotations" + LabelExcludes string = BaseQualifier + "/exclude-labels" + ServiceLabels string = BaseQualifier + "/service-labels" + ServiceAnnotations string = BaseQualifier + "/service-annotations" + ComponentAnnotation string = BaseQualifier + "/component" + SiteControllerIgnore string = InternalQualifier + "/site-controller-ignore" + RouterComponent string = "router" + ClaimExpiration string = BaseQualifier + "/claim-expiration" + ClaimsRemaining string = BaseQualifier + "/claims-remaining" + ClaimsMade string = BaseQualifier + "/claims-made" + ClaimUrlAnnotationKey string = BaseQualifier + "/url" + ClaimPasswordDataKey string = "password" + ClaimCaCertDataKey string = "ca.crt" + ClaimRequestSelector string = SkupperTypeQualifier + "=" + TypeClaimRequest + LastFailedAnnotationKey string = InternalQualifier + "/last-failed" + StatusAnnotationKey string = InternalQualifier + "/status" + GatewayQualifier string = InternalQualifier + "/gateway" + IngressOnlyQualifier string = BaseQualifier + "/ingress-only" + TlsCertQualifier string = BaseQualifier + "/tls-cert" + TlsTrustQualifier string = BaseQualifier + "/tls-trust" +) + +// Console and vFlow Collector constants +const ( + ConsolePortName string = "console" + ConsoleDefaultServicePort int32 = 8080 + ConsoleDefaultServiceTargetPort int32 = 8080 + FlowCollectorDefaultServicePort int32 = 8010 + FlowCollectorDefaultServiceTargetPort int32 = 8010 + ConsoleOpenShiftServicePort int32 = 8888 + ConsoleOpenShiftOauthServicePort int32 = 443 + ConsoleOpenShiftOauthServiceTargetPort int32 = 8443 + ConsoleRouteName string = "skupper" + RouterConsoleServiceName string = "skupper-router-console" +) + +const DefaultFlowTimeoutDuration = time.Minute * 15 + +type Platform string + +const ( + PlatformKubernetes Platform = "kubernetes" + PlatformPodman Platform = "podman" + PlatformDocker Platform = "docker" + PlatformLinux Platform = "linux" +) + +func (p Platform) IsKubernetes() bool { + return p == "" || p == PlatformKubernetes +} + +func (p Platform) IsContainerEngine() bool { + return p == PlatformDocker || p == PlatformPodman +} + +type ConsoleAuthMode string + +const ( + ConsoleAuthModeOpenshift ConsoleAuthMode = "openshift" + ConsoleAuthModeInternal ConsoleAuthMode = "internal" + ConsoleAuthModeUnsecured ConsoleAuthMode = "unsecured" +) + +const ( + ClaimRedemptionPort int32 = 8081 + ClaimRedemptionPortName string = "claims" + ClaimRedemptionRouteName string = "skupper-claims" +) + +type PrometheusAuthMode string + +const ( + PrometheusAuthModeTls PrometheusAuthMode = "tls" + PrometheusAuthModeBasic PrometheusAuthMode = "basic" + PrometheusAuthModeUnsecured PrometheusAuthMode = "unsecured" +) + +// Prometheus server constants (note: use console auth mode) +const ( + PrometheusPortName string = "prometheus" + PrometheusServerDefaultServicePort int32 = 9090 + PrometheusServerDefaultServiceTargetPort int32 = 9090 + PrometheusRouteName string = "skupper-prometheus" +) + +// Assembly constants +const ( + AmqpDefaultPort int32 = 5672 + AmqpsDefaultPort int32 = 5671 + EdgeRole string = "edge" + EdgeRouteName string = "skupper-edge" + EdgeListenerPort int32 = 45671 + InterRouterRole string = "inter-router" + InterRouterListenerPort int32 = 55671 + InterRouterRouteName string = "skupper-inter-router" + InterRouterProfile string = "skupper-internal" + IngressName string = "skupper" +) + +// Service Sync constants +const ( + ServiceSyncAddress = "mc/$skupper-service-sync" +) + +const ( + SkupperServiceCertPrefix string = "skupper-tls-" +) + +// // RouterSpec is the specification of VAN network with router, controller and assembly +// type RouterSpec struct { +// Name string `json:"name,omitempty"` +// Namespace string `json:"namespace,omitempty"` +// AuthMode ConsoleAuthMode `json:"authMode,omitempty"` +// Transport DeploymentSpec `json:"transport,omitempty"` +// ConfigSync DeploymentSpec `json:"configSync,omitempty"` +// Controller DeploymentSpec `json:"controller,omitempty"` +// Collector DeploymentSpec `json:"collector,omitempty"` +// PrometheusServer DeploymentSpec `json:"prometheusServer,omitempty"` +// RouterConfig string `json:"routerConfig,omitempty"` +// Users []User `json:"users,omitempty"` +// CertAuthoritys []CertAuthority `json:"certAuthoritys,omitempty"` +// TransportCredentials []Credential `json:"transportCredentials,omitempty"` +// ControllerCredentials []Credential `json:"controllerCredentials,omitempty"` +// PrometheusCredentials []Credential `json:"prometheusCredentials,omitempty"` +// } + + +// AssemblySpec for the links and connectors that form the VAN topology +type AssemblySpec struct { + Name string `json:"name,omitempty"` + Mode string `json:"mode,omitempty"` + Listeners []Listener `json:"listeners,omitempty"` + InterRouterListeners []Listener `json:"interRouterListeners,omitempty"` + EdgeListeners []Listener `json:"edgeListeners,omitempty"` + SslProfiles []SslProfile `json:"sslProfiles,omitempty"` + Connectors []Connector `json:"connectors,omitempty"` + InterRouterConnectors []Connector `json:"interRouterConnectors,omitempty"` + EdgeConnectors []Connector `json:"edgeConnectors,omitempty"` +} + +type RouterStatusSpec struct { + SiteName string `json:"siteName,omitempty"` + Mode string `json:"mode,omitempty"` + TransportReadyReplicas int32 `json:"transportReadyReplicas,omitempty"` + ConnectedSites TransportConnectedSites `json:"connectedSites,omitempty"` + BindingsCount int `json:"bindingsCount,omitempty"` +} + +type Listener struct { + Name string `json:"name,omitempty"` + Host string `json:"host,omitempty"` + Port int32 `json:"port"` + RouteContainer bool `json:"routeContainer,omitempty"` + Http bool `json:"http,omitempty"` + Cost int32 `json:"cost,omitempty"` + SslProfile string `json:"sslProfile,omitempty"` + SaslMechanisms string `json:"saslMechanisms,omitempty"` + AuthenticatePeer bool `json:"authenticatePeer,omitempty"` + LinkCapacity int32 `json:"linkCapacity,omitempty"` +} + +type SslProfile struct { + Name string `json:"name,omitempty"` + Cert string `json:"cert,omitempty"` + Key string `json:"key,omitempty"` + CaCert string `json:"caCert,omitempty"` +} + +type ConnectorRole string + +const ( + ConnectorRoleInterRouter ConnectorRole = "inter-router" + ConnectorRoleEdge ConnectorRole = "edge" +) + +const ( + ConsoleIngressPrefix = "skupper-console" + ClaimsIngressPrefix = "skupper-claims" + InterRouterIngressPrefix = "skupper-inter-router" + EdgeIngressPrefix = "skupper-edge" + PrometheusIngressPrefix = "skupper-prometheus" +) + +type Connector struct { + Name string `json:"name,omitempty"` + Role string `json:"role,omitempty"` + Host string `json:"host"` + Port string `json:"port"` + RouteContainer bool `json:"routeContainer,omitempty"` + Cost int32 `json:"cost,omitempty"` + VerifyHostname bool `json:"verifyHostname,omitempty"` + SslProfile string `json:"sslProfile,omitempty"` + LinkCapacity int32 `json:"linkCapacity,omitempty"` +} + +type Credential struct { + CA string + Name string + Subject string + Hosts []string + ConnectJson bool + Post bool + Data map[string][]byte + Simple bool `default:"false"` + Labels map[string]string + Expiration time.Duration +} + +type CertAuthority struct { + Name string + Labels map[string]string +} + +type User struct { + Name string + Password string +} + +type TransportConnectedSites struct { + Direct int + Indirect int + Total int + Warnings []string +} + +type RouterLogConfig struct { + Module string + Level string +} + +type Tuning struct { + NodeSelector string + Affinity string + AntiAffinity string + Cpu string + Memory string + CpuLimit string + MemoryLimit string +} + +type RouterOptions struct { + Tuning + Logging []RouterLogConfig + MaxFrameSize int + MaxSessionFrames int + DataConnectionCount string + IngressHost string + ServiceAnnotations map[string]string + PodAnnotations map[string]string + LoadBalancerIp string + DisableMutualTLS bool +} + +type LinkStatus struct { + Name string + Url string + Cost int + Connected bool + Configured bool + Description string + Created string +} \ No newline at end of file diff --git a/internal/router/router.go b/internal/router/router.go index 88100c0..707b96a 100644 --- a/internal/router/router.go +++ b/internal/router/router.go @@ -15,34 +15,19 @@ package router import ( "encoding/base64" + "encoding/json" "fmt" - "github.com/datasance/router/internal/exec" - "io/ioutil" + // "io/ioutil" "log" "os" "path/filepath" -) - -type Listener struct { - Role string `json:"role"` - Host string `json:"host"` - Port int `json:"port"` - SaslMechanisms string `json:"saslMechanisms"` - AuthenticatePeer string `json:"authenticatePeer"` - SslProfile string `json:"sslProfile"` - RequireSsl string `json:"requireSsl"` -} + "time" -type Connector struct { - Name string `json:"name"` - Role string `json:"role"` - Host string `json:"host"` - Port int `json:"port"` - SaslMechanisms string `json:"saslMechanisms"` - SslProfile string `json:"sslProfile"` -} + "github.com/datasance/router/internal/exec" + "github.com/datasance/router/internal/qdr" +) -type SslProfile struct { +type IncomingSslProfile struct { Name string `json:"name"` TlsCert string `json:"tlsCert"` TlsKey string `json:"tlsKey"` @@ -50,333 +35,310 @@ type SslProfile struct { } type Config struct { - Mode string `json:"mode"` - Name string `json:"id"` - Listeners []Listener `json:"listeners"` - Connectors []Connector `json:"connectors"` - SslProfiles []SslProfile `json:"sslProfiles"` + Metadata qdr.RouterMetadata + SslProfiles map[string]IncomingSslProfile + ConvertedProfiles map[string]qdr.SslProfile + Listeners map[string]qdr.Listener + Connectors map[string]qdr.Connector + Addresses map[string]qdr.Address + LogConfig map[string]qdr.LogConfig + SiteConfig *qdr.SiteConfig + Bridges qdr.BridgeConfig } type Router struct { - listeners map[string]Listener - connectors map[string]Connector - sslProfiles map[string]SslProfile - Config *Config -} - -func skmanage(args []string) { - exitChannel := make(chan error) - go exec.Run(exitChannel, "skmanage", args, []string{}) - for { - select { - case <-exitChannel: - return - } - } -} - -func listenerName(listener Listener) string { - return fmt.Sprintf("listener-%s-%s-%d", listener.Role, listener.Host, listener.Port) -} - -func connectorName(connector Connector) string { - return fmt.Sprintf("connector-%s-%s-%s-%d", connector.Name, connector.Role, connector.Host, connector.Port) -} - -func sslProfileName(sslProfile SslProfile) string { - return fmt.Sprintf("%s", sslProfile.Name) -} - -func deleteEntity(name string) { - args := []string{ - "delete", - fmt.Sprintf("--name=%s", name), - } - skmanage(args) + Config *Config } -func (router *Router) createListener(listener Listener) { - if listener.SaslMechanisms == "" { - listener.SaslMechanisms = "ANONYMOUS" - } - if listener.AuthenticatePeer == "" { - listener.AuthenticatePeer = "no" - } - if listener.RequireSsl == "" { - listener.RequireSsl = "no" +func (router *Router) handleTLSFiles(sslProfile IncomingSslProfile) (qdr.SslProfile, error) { + log.Printf("DEBUG: Processing SSL profile: %s", sslProfile.Name) + + // Create base certs directory first with full permissions for non-root user + baseCertDir := "/home/runner/skupper-router-certs" + if err := os.MkdirAll(baseCertDir, 0777); err != nil { + log.Printf("ERROR: Failed to create base cert directory: %v", err) + return qdr.SslProfile{}, fmt.Errorf("failed to create base cert directory: %v", err) } - args := []string{ - "create", - "--type=listener", - fmt.Sprintf("port=%d", listener.Port), - fmt.Sprintf("role=%s", listener.Role), - fmt.Sprintf("host=%s", listener.Host), - fmt.Sprintf("name=%s", listenerName(listener)), + // Create profile-specific directory with full permissions + certDir := fmt.Sprintf("%s/%s", baseCertDir, sslProfile.Name) + if err := os.MkdirAll(certDir, 0777); err != nil { + log.Printf("ERROR: Failed to create cert directory: %v", err) + return qdr.SslProfile{}, fmt.Errorf("failed to create cert directory: %v", err) } - if listener.SaslMechanisms != "" { - args = append(args, fmt.Sprintf("sslProfile=%s", listener.SaslMechanisms)) + // Create a new qdr.SslProfile with the name + profile := qdr.SslProfile{ + Name: sslProfile.Name, } - if listener.AuthenticatePeer != "" { - args = append(args, fmt.Sprintf("requireSsl=%s", listener.AuthenticatePeer)) - } - - if listener.SslProfile != "" { - args = append(args, fmt.Sprintf("sslProfile=%s", listener.SslProfile)) - } - - if listener.RequireSsl != "" { - args = append(args, fmt.Sprintf("requireSsl=%s", listener.RequireSsl)) - } - - skmanage(args) - router.listeners[listenerName(listener)] = listener -} - -func (router *Router) createConnector(connector Connector) { - if connector.SaslMechanisms == "" { - connector.SaslMechanisms = "ANONYMOUS" - } - - args := []string{ - "create", - "--type=connector", - fmt.Sprintf("port=%d", connector.Port), - fmt.Sprintf("role=%s", connector.Role), - fmt.Sprintf("host=%s", connector.Host), - fmt.Sprintf("name=%s", connectorName(connector)), - } - - if connector.SaslMechanisms != "" { - args = append(args, fmt.Sprintf("sslProfile=%s", connector.SaslMechanisms)) - } - - if connector.SslProfile != "" { - args = append(args, fmt.Sprintf("sslProfile=%s", connector.SslProfile)) - } - - skmanage(args) - router.connectors[connectorName(connector)] = connector -} - -func (router *Router) deleteListener(listener Listener) { - deleteEntity(listenerName(listener)) - delete(router.listeners, listenerName(listener)) -} - -func (router *Router) deleteConnector(connector Connector) { - deleteEntity(connectorName(connector)) - delete(router.connectors, connectorName(connector)) -} - -func (router *Router) handleTLSFiles(sslProfile SslProfile) error { - certDir := fmt.Sprintf("/home/runner/%s-cert", sslProfile.Name) - log.Printf("Creating directory: %s", certDir) - caCert := sslProfile.CaCert - tlsCert := sslProfile.TlsCert - tlsKey := sslProfile.TlsKey - if err := os.MkdirAll(certDir, 0755); err != nil { - return fmt.Errorf("failed to create config directory: %v", err) - } - log.Printf("Handling TLS files in directory: %s", certDir) - - if caCert != "" { - log.Printf("Processing CaCert for %s", sslProfile.Name) - caPath := filepath.Join(certDir, "ca.crt") - if err := decodeCertToFile(sslProfile.CaCert, caPath); err != nil { - return fmt.Errorf("failed to decode CaCert: %v", err) + // Convert base64 encoded strings to files and update the profile + if sslProfile.TlsCert != "" { + log.Printf("DEBUG: Processing TLS certificate for profile %s", sslProfile.Name) + certPath := filepath.Join(certDir, "tls.crt") + if err := decodeCertToFile(sslProfile.TlsCert, certPath); err != nil { + log.Printf("ERROR: Failed to write TLS certificate: %v", err) + return qdr.SslProfile{}, fmt.Errorf("failed to write TLS certificate: %v", err) } + profile.CertFile = certPath } - - if tlsCert != "" { - log.Printf("Processing TlsCert for %s", sslProfile.Name) - tlsCertPath := filepath.Join(certDir, "tls.crt") - if err := decodeCertToFile(sslProfile.TlsCert, tlsCertPath); err != nil { - return fmt.Errorf("failed to decode TlsCert: %v", err) + + if sslProfile.TlsKey != "" { + log.Printf("DEBUG: Processing TLS key for profile %s", sslProfile.Name) + keyPath := filepath.Join(certDir, "tls.key") + if err := decodeCertToFile(sslProfile.TlsKey, keyPath); err != nil { + log.Printf("ERROR: Failed to write TLS key: %v", err) + return qdr.SslProfile{}, fmt.Errorf("failed to write TLS key: %v", err) } + profile.PrivateKeyFile = keyPath } - - if tlsKey != "" { - log.Printf("Processing TlsKey for %s", sslProfile.Name) - tlsKeyPath := filepath.Join(certDir, "tls.key") - if err := decodeCertToFile(sslProfile.TlsKey, tlsKeyPath); err != nil { - return fmt.Errorf("failed to decode TlsKey: %v", err) + + if sslProfile.CaCert != "" { + log.Printf("DEBUG: Processing CA certificate for profile %s", sslProfile.Name) + caPath := filepath.Join(certDir, "ca.crt") + if err := decodeCertToFile(sslProfile.CaCert, caPath); err != nil { + log.Printf("ERROR: Failed to write CA certificate: %v", err) + return qdr.SslProfile{}, fmt.Errorf("failed to write CA certificate: %v", err) } + profile.CaCertFile = caPath } - return nil + log.Printf("DEBUG: Successfully processed SSL profile: %s", sslProfile.Name) + return profile, nil } func decodeCertToFile(certString string, outputPath string) error { - log.Printf("Starting decodeCertToFile for outputPath: %s", outputPath) - - // Decode the base64 data - decodedData, err := base64.StdEncoding.DecodeString(certString) + log.Printf("DEBUG: Decoding certificate to file: %s", outputPath) + + decoded, err := base64.StdEncoding.DecodeString(certString) if err != nil { - log.Fatalf("Failed to decode base64 data: %v", err) + log.Printf("ERROR: Failed to decode certificate: %v", err) + return fmt.Errorf("failed to decode certificate: %v", err) } - - // Write the decoded data to the file - if err := ioutil.WriteFile(outputPath, decodedData, 0644); err != nil { - log.Printf("Failed to write PEM data to file %s: %v", outputPath, err) - return fmt.Errorf("failed to write PEM data to file: %v", err) + + if err := os.WriteFile(outputPath, decoded, 0644); err != nil { + log.Printf("ERROR: Failed to write certificate file: %v", err) + return fmt.Errorf("failed to write certificate file: %v", err) } - - log.Printf("Successfully wrote PEM file to: %s", outputPath) + + log.Printf("DEBUG: Successfully wrote certificate to %s", outputPath) return nil } -func (router *Router) UpdateRouter(newConfig *Config) { - newListeners := make(map[string]Listener) - newConnectors := make(map[string]Connector) - - for _, listener := range newConfig.Listeners { - newListeners[listenerName(listener)] = listener - if _, ok := router.listeners[listenerName(listener)]; !ok { - router.createListener(listener) +func (router *Router) UpdateRouter(newConfig *Config) error { + log.Printf("DEBUG: Starting router configuration update") + // log.Printf("DEBUG: New configuration: %+v", newConfig) + + // Handle SSL profiles first and convert profiles + convertedProfiles := make(map[string]qdr.SslProfile) + for name, profile := range newConfig.SslProfiles { + log.Printf("DEBUG: Converting SSL profile: %s", name) + convertedProfile, err := router.handleTLSFiles(profile) + if err != nil { + log.Printf("ERROR: Failed to handle TLS files: %v", err) + return fmt.Errorf("failed to handle TLS files: %v", err) } + convertedProfiles[name] = convertedProfile } + newConfig.ConvertedProfiles = convertedProfiles - for _, connector := range newConfig.Connectors { - newConnectors[connectorName(connector)] = connector - if _, ok := router.connectors[connectorName(connector)]; !ok { - router.createConnector(connector) - } + // Create agent pool and get client + log.Printf("DEBUG: Creating agent pool") + agentPool := qdr.NewAgentPool("amqp://localhost:5672", nil) + client, err := agentPool.Get() + if err != nil { + log.Printf("ERROR: Failed to get client from pool: %v", err) + return fmt.Errorf("failed to get client from pool: %v", err) } - for _, sslProfile := range newConfig.SslProfiles { - if err := router.handleTLSFiles(sslProfile); err != nil { - } + // Get current bridge configuration + log.Printf("DEBUG: Getting current bridge configuration") + currentBridgeConfig, err := client.GetLocalBridgeConfig() + if err != nil { + log.Printf("ERROR: Failed to get current bridge config: %v", err) + return fmt.Errorf("failed to get current bridge config: %v", err) } - for _, listener := range router.listeners { - if _, ok := newListeners[listenerName(listener)]; !ok { - router.deleteListener(listener) - } + // Calculate differences using qdr's built-in Difference method + log.Printf("DEBUG: Calculating bridge configuration differences") + changes := currentBridgeConfig.Difference(&newConfig.Bridges) + log.Printf("DEBUG: Bridge config changes: %+v", changes) + + // Update via AMQP management using qdr's built-in function + log.Printf("DEBUG: Updating bridge configuration") + if err := client.UpdateLocalBridgeConfig(changes); err != nil { + log.Printf("ERROR: Failed to update bridge config: %v", err) + return fmt.Errorf("failed to update bridge config: %v", err) } - for _, connector := range router.connectors { - if _, ok := newConnectors[connectorName(connector)]; !ok { - router.deleteConnector(connector) - } + // Update the configuration file with the new TLS file paths + log.Printf("DEBUG: Updating router configuration file") + configJSON := router.GetRouterConfig() + configPath := "/home/runner/skupper-router-certs/skrouterd.json" + if err := os.WriteFile(configPath, []byte(configJSON), 0644); err != nil { + log.Printf("ERROR: Failed to write router configuration: %v", err) + return fmt.Errorf("failed to write router configuration: %v", err) } + // Update the in-memory configuration router.Config = newConfig + + // Return client to the pool instead of closing it + if client != nil { + agentPool.Put(client) + } + + log.Printf("DEBUG: Router configuration update completed successfully") + return nil } func (router *Router) GetRouterConfig() string { - listenersConfig := "" - for _, listener := range router.listeners { - // Handle default values for listener fields - saslMechanisms := listener.SaslMechanisms - if saslMechanisms == "" { - saslMechanisms = "ANONYMOUS" + config := router.Config + configElements := [][]interface{}{} + + // Add router metadata + configElements = append(configElements, []interface{}{ + "router", + config.Metadata, + }) + + // Add SSL profiles with updated file paths + for _, profile := range config.ConvertedProfiles { + // Create a clean sslProfile entry for the config file + sslProfile := qdr.SslProfile{ + Name: profile.Name, + CertFile: profile.CertFile, + PrivateKeyFile: profile.PrivateKeyFile, + CaCertFile: profile.CaCertFile, } + configElements = append(configElements, []interface{}{ + "sslProfile", + sslProfile, + }) + } - authenticatePeer := listener.AuthenticatePeer - if authenticatePeer == "" { - authenticatePeer = "no" - } + // Add listeners + for _, listener := range config.Listeners { + configElements = append(configElements, []interface{}{ + "listener", + listener, + }) + } - requireSsl := "" - if listener.RequireSsl != "" { - requireSsl = fmt.Sprintf(" requireSsl: %s\\n", listener.RequireSsl) - } + // Add connectors + for _, connector := range config.Connectors { + configElements = append(configElements, []interface{}{ + "connector", + connector, + }) + } - sslProfile := "" - if listener.SslProfile != "" { - sslProfile = fmt.Sprintf(" sslProfile: %s\\n", listener.SslProfile) - } + // Add TCP listeners + for _, listener := range config.Bridges.TcpListeners { + configElements = append(configElements, []interface{}{ + "tcpListener", + listener, + }) + } - listenersConfig += fmt.Sprintf( - "\\nlistener {\\n name: %s\\n role: %s\\n host: %s\\n port: %d\\n saslMechanisms: %s\\n authenticatePeer: %s\\n%s%s}", - listenerName(listener), - listener.Role, - listener.Host, - listener.Port, - saslMechanisms, - authenticatePeer, - sslProfile, - requireSsl, - ) + // Add TCP connectors + for _, connector := range config.Bridges.TcpConnectors { + configElements = append(configElements, []interface{}{ + "tcpConnector", + connector, + }) } - connectorsConfig := "" - for _, connector := range router.connectors { - // Handle default values for connector fields - saslMechanisms := connector.SaslMechanisms - if saslMechanisms == "" { - saslMechanisms = "ANONYMOUS" - } + // Add addresses + for _, address := range config.Addresses { + configElements = append(configElements, []interface{}{ + "address", + address, + }) + } - sslProfile := "" - if connector.SslProfile != "" { - sslProfile = fmt.Sprintf(" sslProfile: %s\\n", connector.SslProfile) - } + // Add log configs + for _, logConfig := range config.LogConfig { + configElements = append(configElements, []interface{}{ + "log", + logConfig, + }) + } - connectorsConfig += fmt.Sprintf( - "\\nconnector {\\n name: %s\\n host: %s\\n port: %d\\n role: %s\\n saslMechanisms: %s\\n%s}", - connectorName(connector), - connector.Host, - connector.Port, - connector.Role, - saslMechanisms, - sslProfile, - ) + // Add site config if present + if config.SiteConfig != nil { + configElements = append(configElements, []interface{}{ + "site", + *config.SiteConfig, + }) } - sslProfilesConfig := "" - for _, sslProfile := range router.sslProfiles { - if sslProfile.CaCert == "" { - sslProfilesConfig += fmt.Sprintf( - "\\nsslProfile {\\n name: %s\\n caCertFile: /home/runner/%s-cert/tls.crt\\n certFile: /home/runner/%s-cert/tls.crt\\n privateKeyFile: /home/runner/%s-cert/tls.key\\n}", - sslProfileName(sslProfile), - sslProfileName(sslProfile), - sslProfileName(sslProfile), - sslProfileName(sslProfile), - ) - } else { - sslProfilesConfig += fmt.Sprintf( - "\\nsslProfile {\\n name: %s\\n caCertFile: /home/runner/%s-cert/ca.crt\\n certFile: /home/runner/%s-cert/tls.crt\\n privateKeyFile: /home/runner/%s-cert/tls.key\\n}", - sslProfileName(sslProfile), - sslProfileName(sslProfile), - sslProfileName(sslProfile), - sslProfileName(sslProfile), - ) - } + // Marshal to JSON + data, err := json.MarshalIndent(configElements, "", " ") + if err != nil { + log.Printf("Error marshaling router config: %v", err) + return "" } - return fmt.Sprintf( - "router {\\n mode: %s\\n id: %s\\n saslConfigDir: /etc/sasl2/\\n}%s%s%s", - router.Config.Mode, - router.Config.Name, - listenersConfig, - connectorsConfig, - sslProfilesConfig, - ) + return string(data) } func (router *Router) StartRouter(ch chan<- error) { - router.listeners = make(map[string]Listener) - router.connectors = make(map[string]Connector) - router.sslProfiles = make(map[string]SslProfile) - - for _, listener := range router.Config.Listeners { - router.listeners[listenerName(listener)] = listener + log.Printf("DEBUG: Starting router with configuration") + // log.Printf("DEBUG: Router config: %+v", router.Config) + + // Handle TLS files first and convert profiles + convertedProfiles := make(map[string]qdr.SslProfile) + for name, profile := range router.Config.SslProfiles { + log.Printf("DEBUG: Converting SSL profile: %s", name) + convertedProfile, err := router.handleTLSFiles(profile) + if err != nil { + log.Printf("ERROR: Failed to handle TLS files: %v", err) + ch <- fmt.Errorf("failed to handle TLS files: %v", err) + return + } + convertedProfiles[name] = convertedProfile } - - for _, connector := range router.Config.Connectors { - router.connectors[connectorName(connector)] = connector + router.Config.ConvertedProfiles = convertedProfiles + + // Create initial configuration + log.Printf("DEBUG: Creating initial router configuration") + config := router.GetRouterConfig() + configPath := "/home/runner/skupper-router-certs/skrouterd.json" + + // Ensure directory exists + log.Printf("DEBUG: Ensuring configuration directory exists") + if err := os.MkdirAll(filepath.Dir(configPath), 0755); err != nil { + log.Printf("ERROR: Failed to create configuration directory: %v", err) + ch <- fmt.Errorf("failed to create configuration directory: %v", err) + return } - for _, sslProfile := range router.Config.SslProfiles { - router.sslProfiles[sslProfileName(sslProfile)] = sslProfile - if err := router.handleTLSFiles(sslProfile); err != nil { - } + // Write initial configuration + log.Printf("DEBUG: Writing initial configuration to %s", configPath) + if err := os.WriteFile(configPath, []byte(config), 0644); err != nil { + log.Printf("ERROR: Failed to write initial configuration: %v", err) + ch <- fmt.Errorf("failed to write initial configuration: %v", err) + return } - routerConfig := router.GetRouterConfig() - exec.Run(ch, "/home/skrouterd/bin/launch.sh", []string{}, []string{"QDROUTERD_CONF=" + routerConfig}) + // Start router with JSON configuration + exitChannel := make(chan error) + log.Printf("DEBUG: Starting router process") + go exec.Run(ch, "/home/skrouterd/bin/launch.sh", []string{}, []string{}) + + // Monitor for configuration updates + go func() { + for { + select { + case err := <-exitChannel: + log.Printf("ERROR: Router process exited with error: %v", err) + ch <- fmt.Errorf("router process exited with error: %v", err) + return + default: + // Check for configuration updates from ioFog-agent + time.Sleep(5 * time.Second) + } + } + }() } diff --git a/internal/utils/command.go b/internal/utils/command.go new file mode 100644 index 0000000..8f5e3e9 --- /dev/null +++ b/internal/utils/command.go @@ -0,0 +1,29 @@ +package utils + +import ( + "bytes" +) + +const ( + maxLineLength = 80 + indent = " " + newLinePrefix = " \\\n" + indent +) + +func PrettyPrintCommand(command string, args []string) string { + var lineLength int + buf := new(bytes.Buffer) + buf.WriteString(command) + lineLength = len(command) + for i, arg := range args { + buf.WriteString(" ") + buf.WriteString(arg) + lineLength += len(arg) + 1 + if lineLength > maxLineLength && i < len(args)-1 { + buf.WriteString(newLinePrefix) + lineLength = len(indent) + } + } + buf.WriteString("\n") + return buf.String() +} diff --git a/internal/utils/command_test.go b/internal/utils/command_test.go new file mode 100644 index 0000000..78ef1bd --- /dev/null +++ b/internal/utils/command_test.go @@ -0,0 +1,32 @@ +package utils + +import ( + "testing" + + "gotest.tools/v3/assert" +) + +const ( + expectedOutput = `sample_command argument1 argument2 argument3 argument4 argument5 argument6 argument7 \ + argument8 argument9 argument10 argument11 argument12 argument13 argument14 argument15 \ + argument16 argument17 argument18 argument19 argument20 argument21 argument22 argument23 \ + argument24 argument25 argument26 argument27 argument28 argument29 +` +) + +var ( + commandArgs = []string{ + "sample_command", "argument1", "argument2", "argument3", "argument4", "argument5", + "argument6", "argument7", "argument8", "argument9", "argument10", "argument11", "argument12", + "argument13", "argument14", "argument15", "argument16", "argument17", "argument18", + "argument19", "argument20", "argument21", "argument22", "argument23", "argument24", + "argument25", "argument26", "argument27", "argument28", "argument29", + } +) + +func TestPrettyPrintCommand(t *testing.T) { + output := PrettyPrintCommand(commandArgs[0], commandArgs[1:]) + assert.Equal(t, expectedOutput, output) + shortCommand := PrettyPrintCommand("command", []string{"arg1", "arg2", "arg3"}) + assert.Equal(t, "command arg1 arg2 arg3\n", shortCommand) +} diff --git a/internal/utils/files.go b/internal/utils/files.go new file mode 100644 index 0000000..0be97bb --- /dev/null +++ b/internal/utils/files.go @@ -0,0 +1,43 @@ +package utils + +import ( + "fmt" + "os" + "path" +) + +type FilenameFilter func(string) bool +type DirectoryReader struct{} + +func (f *DirectoryReader) ReadDir(dirname string, filter FilenameFilter) ([]string, error) { + dir, err := os.Open(dirname) + if err != nil { + return nil, err + } + dirInfo, err := dir.Stat() + if err != nil { + return nil, err + } + if !dirInfo.IsDir() { + return nil, fmt.Errorf("%s is not a directory", dirname) + } + files, err := dir.ReadDir(0) + if err != nil { + return nil, err + } + var fileNames []string + for _, file := range files { + if file.IsDir() { + recursiveFiles, err := f.ReadDir(path.Join(dirname, file.Name()), filter) + if err != nil { + return nil, err + } + fileNames = append(fileNames, recursiveFiles...) + } else { + if filter == nil || filter(file.Name()) { + fileNames = append(fileNames, path.Join(dirname, file.Name())) + } + } + } + return fileNames, nil +} diff --git a/internal/utils/files_test.go b/internal/utils/files_test.go new file mode 100644 index 0000000..e25be98 --- /dev/null +++ b/internal/utils/files_test.go @@ -0,0 +1,108 @@ +package utils + +import ( + "os" + "path" + "slices" + "strings" + "testing" + + "gotest.tools/v3/assert" +) + +func TestDirectoryReader(t *testing.T) { + for _, test := range []struct { + description string + files int + directories int + levels int + filter func(string) bool + expectedFiles []string + }{ + { + description: "empty-directory", + files: 0, + directories: 0, + levels: 0, + expectedFiles: []string{}, + }, + { + description: "empty-directory-tree", + files: 0, + directories: 3, + levels: 3, + }, + { + description: "full-directory", + files: 3, + directories: 0, + levels: 1, + expectedFiles: []string{ + "file.1", + "file.2", + "file.3", + }, + }, + { + description: "full-directory-tree", + files: 3, + directories: 3, + levels: 1, + expectedFiles: []string{ + "file.1", + "file.2", + "file.3", + "dir1/file.1", + "dir1/file.2", + "dir1/file.3", + "dir2/file.1", + "dir2/file.2", + "dir2/file.3", + "dir3/file.1", + "dir3/file.2", + "dir3/file.3", + }, + }, + { + description: "full-directory-tree-filtered", + files: 3, + directories: 3, + levels: 1, + filter: func(name string) bool { + return strings.HasSuffix(name, ".3") + }, + expectedFiles: []string{ + "file.3", + "dir1/file.3", + "dir2/file.3", + "dir3/file.3", + }, + }, + } { + t.Run(test.description, func(t *testing.T) { + baseDir, err := os.MkdirTemp("", "testdirreader.*") + assert.Assert(t, err) + defer func() { + assert.Assert(t, os.RemoveAll(baseDir)) + }() + // Generating files and asserting generation was successful + tree := generateDirectoryTree(test.directories, test.levels) + err = createFiles(baseDir, test.files, []byte("sample data"), tree) + assert.Assert(t, err, "unable to create files") + + r := new(DirectoryReader) + files, err := r.ReadDir(baseDir, test.filter) + assert.NilError(t, err, "error reading directory") + if test.filter == nil { + assert.Equal(t, len(files), expectedCreatedFiles(test.directories, test.levels, test.files)) + } else { + if len(test.expectedFiles) > 0 { + assert.Equal(t, len(files), len(test.expectedFiles)) + } + } + for _, expectedFile := range test.expectedFiles { + assert.Assert(t, slices.Contains(files, path.Join(baseDir, expectedFile))) + } + }) + } +} diff --git a/internal/utils/retry.go b/internal/utils/retry.go new file mode 100644 index 0000000..ba24718 --- /dev/null +++ b/internal/utils/retry.go @@ -0,0 +1,141 @@ +/* +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package utils + +import ( + "context" + "fmt" + "time" +) + +type ConditionFunc func() (bool, error) + +type CheckedFunc func() error + +// Retry retries f every interval until after maxRetries. +// +// The interval won't be affected by how long f takes. +// For example, if interval is 3s, f takes 1s, another f will be called 2s later. +// However, if f takes longer than interval, it will be delayed. +// +// If an error is received from f, fail immediately and return that error (no +// further retries). +// +// Keep in mind that the second argument is for max _retries_. So, with a value +// of 1, f() will run at most 2 times (one try and one _retry_). +func Retry(interval time.Duration, maxRetries int, f ConditionFunc) error { + if maxRetries <= 0 { + return fmt.Errorf("maxRetries (%d) should be > 0", maxRetries) + } + tick := time.NewTicker(interval) + defer tick.Stop() + + for i := 0; ; i++ { + ok, err := f() + if err != nil { + return err + } + if ok { + return nil + } + if i == maxRetries { + return fmt.Errorf("still failing after %d retries", i) + } + <-tick.C + } +} + +// This is similar to Retry(), but it will not fail immediately on errors, and +// if the retries are exhausted and f() still failing, it will return f()'s error +func RetryError(interval time.Duration, maxRetries int, f CheckedFunc) error { + if maxRetries <= 0 { + return fmt.Errorf("maxRetries (%d) should be > 0", maxRetries) + } + tick := time.NewTicker(interval) + defer tick.Stop() + + for i := 0; ; i++ { + err := f() + if err == nil { + return nil + } + if i == maxRetries { + return err + } + <-tick.C + } +} + +type Result struct { + Value interface{} + Error error +} + +type ResultFunc func() Result + +func TryUntil(maxWindowTime time.Duration, f ResultFunc) (interface{}, error) { + + result := make(chan Result, 1) + + go func() { + result <- f() + }() + select { + case <-time.After(maxWindowTime): + return nil, fmt.Errorf("timed out") + case result := <-result: + return result.Value, result.Error + } +} + +// RetryWithContext retries f every interval until the specified context times out. +func RetryWithContext(ctx context.Context, interval time.Duration, f ConditionFunc) error { + tick := time.NewTicker(interval) + defer tick.Stop() + + for { + select { + case <-ctx.Done(): + return context.DeadlineExceeded + case <-tick.C: + r, err := f() + if err != nil { + return err + } + if r { + return nil + } + } + } +} + +// RetryErrorWithContext will retry as long the checked function is returning an error and the context does not time out +func RetryErrorWithContext(ctx context.Context, interval time.Duration, f CheckedFunc) error { + tick := time.NewTicker(interval) + defer tick.Stop() + + for { + select { + case <-ctx.Done(): + return context.DeadlineExceeded + + case <-tick.C: + err := f() + if err == nil { + return nil + } + } + } +} diff --git a/internal/utils/retry_test.go b/internal/utils/retry_test.go new file mode 100644 index 0000000..e4cc08c --- /dev/null +++ b/internal/utils/retry_test.go @@ -0,0 +1,407 @@ +package utils + +import ( + "context" + "fmt" + "testing" + "time" + + "gotest.tools/v3/assert" +) + +// f() return: Retry() +// +// n ok err maxRetries return (error) +// -- ----- ----- ----------- -------------- +// 1 true, nil no nil +// 2 true, !nil no err from f() +// 3 false, nil no retry +// 4 false, !nil no err from f() +// +// 5 true, nil yes nil +// 6 true, !nil yes err from f() +// 7 false, nil yes RetryError +// 8 false, !nil yes err from f() +// +// Or: +// +// - If function produces an error, fail immediately with that error +// - Else, if ok is true, return nil and succeed +// - Otherwise: +// - if before maximum retry: retry +// - if on maximum retry: return a Retry error and fail + +type RetryTestItem struct { + // These configure what the f() function will respond + err error + okOnTry int // OkOnTry = 1 make it ok right away; OkOnTry = 0 will never be ok + errorOnTry int + nilOnTry int + // This configures Retry itself + maxRetries int + // And those are what we're expecting the actual result to look like + expectedTries int + expectedResponse error + + // these change the normal response of f() until a specific try +} + +func TestRetry(t *testing.T) { + + testTable := []RetryTestItem{ + { // #1 + okOnTry: 1, + err: nil, + maxRetries: 3, + expectedTries: 1, + expectedResponse: nil, + }, { // #2 + okOnTry: 1, + err: fmt.Errorf("app error"), + maxRetries: 3, + expectedTries: 1, + expectedResponse: fmt.Errorf("app error"), + }, { // #3, #7 + okOnTry: 0, + err: nil, + maxRetries: 3, + expectedTries: 4, + expectedResponse: fmt.Errorf("still failing after 3 retries"), + }, { // #4 + okOnTry: 0, + err: fmt.Errorf("app error"), + maxRetries: 3, + expectedTries: 1, + expectedResponse: fmt.Errorf("app error"), + }, { // #3, #1 + okOnTry: 2, + err: nil, + maxRetries: 3, + expectedTries: 2, + expectedResponse: nil, + }, { // #3, #2 + okOnTry: 2, + err: fmt.Errorf("app error"), + maxRetries: 3, + expectedTries: 2, + expectedResponse: fmt.Errorf("app error"), + errorOnTry: 2, + }, { // #3, #4 + okOnTry: 0, + err: fmt.Errorf("app error"), + maxRetries: 3, + expectedTries: 2, + expectedResponse: fmt.Errorf("app error"), + errorOnTry: 2, + }, { // #3, #5 + okOnTry: 4, + err: nil, + maxRetries: 3, + expectedTries: 4, + expectedResponse: nil, + }, { // #3, #6 + okOnTry: 4, + err: fmt.Errorf("app error"), + maxRetries: 3, + expectedTries: 4, + expectedResponse: fmt.Errorf("app error"), + errorOnTry: 4, + }, { // #3, #8 + okOnTry: 0, + err: fmt.Errorf("app error"), + maxRetries: 3, + expectedTries: 4, + expectedResponse: fmt.Errorf("app error"), + errorOnTry: 4, + }, { + okOnTry: 1, + err: nil, + maxRetries: -1, + expectedTries: 0, + expectedResponse: fmt.Errorf("maxRetries (%d) should be > 0", -1), + }, { + okOnTry: 1, + err: nil, + maxRetries: 0, + expectedTries: 0, + expectedResponse: fmt.Errorf("maxRetries (%d) should be > 0", 0), + }, + } + + for _, item := range testTable { + name := fmt.Sprintf("okOnTry:%v err:%v expectedTries:%v maxRetries:%v errorOnTry:%v nilOnTry: %v", + item.okOnTry, item.err, item.expectedTries, item.maxRetries, item.errorOnTry, item.nilOnTry) + + var currentTry int + t.Run(name, func(t *testing.T) { + + retryErr := Retry(time.Microsecond, item.maxRetries, func() (ok bool, err error) { + currentTry++ + if currentTry > item.maxRetries+1 { + // This is a protection for infinite loops + t.Fatalf("Retry %v > maxRetries %v + 1", currentTry, item.maxRetries) + } + + ok = item.okOnTry > 0 && currentTry >= item.okOnTry + + if item.errorOnTry > 0 { + if currentTry >= item.errorOnTry { + err = item.err + } else { + err = nil + } + } else { + err = item.err + } + + if item.nilOnTry > 0 && currentTry >= item.nilOnTry { + err = nil + } + + return + + }) + + if item.expectedResponse != nil { + if retryErr != nil { + if retryErr.Error() != item.expectedResponse.Error() { + t.Error("Received error:", retryErr) + } + } else { + t.Error("Received error:", retryErr) + } + } else { + if retryErr != nil { + t.Error("Received error:", retryErr) + } + } + + if currentTry != item.expectedTries { + t.Errorf("%v != %v", currentTry, item.expectedTries) + } + + }) + } + +} + +type TestRetryErrorItem struct { + workOnTry int + expectedTries int + maxRetries int + expectSuccess bool +} + +func TestRetryError(t *testing.T) { + testTable := []TestRetryErrorItem{ + { + workOnTry: 1, + expectedTries: 1, + maxRetries: 3, + expectSuccess: true, + }, { + workOnTry: 2, + expectedTries: 2, + maxRetries: 3, + expectSuccess: true, + }, { + workOnTry: 4, + expectedTries: 4, + maxRetries: 3, + expectSuccess: true, + }, { + workOnTry: 5, + expectedTries: 4, + maxRetries: 3, + expectSuccess: false, + }, + } + + for _, item := range testTable { + name := fmt.Sprintf("workOnTry: %v expectedTries: %v maxRetries: %v expectSuccess: %v", + item.workOnTry, item.expectedTries, item.maxRetries, item.expectSuccess) + t.Run(name, func(t *testing.T) { + var currentTry int + + resp := RetryError(time.Microsecond, item.maxRetries, func() (err error) { + currentTry++ + if currentTry >= item.workOnTry { + return nil + } + return fmt.Errorf("Still not working") + }) + + if item.expectSuccess != (resp == nil) { + t.Errorf("Received error: %v", resp) + } + + if item.expectedTries != currentTry { + t.Errorf("Returned in %d tries", currentTry) + } + + }) + } + +} + +type TestTryUntilItem struct { + workOnSecond time.Duration + funcError error + funcValue interface{} + maxDuration time.Duration + expectTimeout bool +} + +func TestTryUntil(t *testing.T) { + testTable := []TestTryUntilItem{ + { + workOnSecond: 100 * time.Millisecond, + funcError: nil, + funcValue: []string{"first", "second", "third"}, + maxDuration: 500 * time.Millisecond, + expectTimeout: false, + }, + { + workOnSecond: 400 * time.Millisecond, + funcError: nil, + funcValue: 5, + maxDuration: 1000 * time.Millisecond, + expectTimeout: false, + }, + { + workOnSecond: 100 * time.Millisecond, + funcError: fmt.Errorf("function is not working"), + funcValue: nil, + maxDuration: 5 * time.Second, + expectTimeout: false, + }, + { + workOnSecond: 30 * time.Second, + funcError: nil, + funcValue: nil, + maxDuration: 500 * time.Millisecond, + expectTimeout: true, + }, + } + + for _, item := range testTable { + name := fmt.Sprintf("workOnSecond: %v maxDuration: %v expectTimeout: %v", + item.workOnSecond, item.maxDuration, item.expectTimeout) + item := item + t.Run(name, func(t *testing.T) { + t.Parallel() + resp, err := TryUntil(item.maxDuration, func() Result { + time.Sleep(item.workOnSecond) + return Result{ + Value: item.funcValue, + Error: item.funcError, + } + }) + + fmt.Printf("result: %v", resp) + fmt.Println() + + if item.expectTimeout && err.Error() != "timed out" { + t.Errorf("It was expected a timeout but it did not happen") + } + + if item.funcValue != nil && resp == nil { + t.Errorf("It was expected to receive a value") + } + + if !item.expectTimeout && item.funcError != err { + t.Errorf("Received wrong error: %s", err) + } + + }) + } + +} + +type TestRetryErrorWithContextItem struct { + doc string + timeout time.Duration + workOnTry int + expectedTries int + expectSuccess bool + expectedError string +} + +func TestRetryErrorWithContext(t *testing.T) { + testTable := []TestRetryErrorWithContextItem{ + { + doc: "The execution should work at the first try", + timeout: time.Millisecond * 200, + workOnTry: 1, + expectedTries: 1, + expectSuccess: true, + }, { + doc: "The execution should work at the second try", + timeout: time.Millisecond * 300, + workOnTry: 2, + expectedTries: 2, + expectSuccess: true, + }, { + doc: "The execution should work after many tries", + timeout: time.Millisecond * 500, + workOnTry: 4, + expectedTries: 4, + expectSuccess: true, + }, { + doc: "The execution should time out after many retries due the context", + timeout: time.Millisecond * 400, + workOnTry: 5, + expectedTries: 4, + expectSuccess: false, + expectedError: "context deadline exceeded", + }, + } + + for _, item := range testTable { + item := item + + t.Run(item.doc, func(t *testing.T) { + t.Parallel() + var currentTry int + + ctx, cancel := context.WithTimeout(context.Background(), item.timeout) + defer cancel() + + start := time.Now() + + resp := RetryErrorWithContext(ctx, 100*time.Millisecond, func() (err error) { + currentTry++ + if currentTry == item.workOnTry { + return nil + } + return fmt.Errorf("Still not working") + }) + + elapsed := time.Since(start) + + if item.expectSuccess { + // Expecting success: check that the function completed successfully + if resp != nil { + t.Errorf("Expected success, but got error: %v", resp) + } + if elapsed > item.timeout { + t.Errorf("Expected to complete before timeout, but took %v", elapsed) + } + } else { + assert.Assert(t, resp != nil) + if item.expectedError != "" { + assert.Equal(t, item.expectedError, resp.Error()) + } + if elapsed <= item.timeout { + t.Logf("The execution should have timed out, but it did not. Elapsed %d ms, timeout %d ms.", elapsed/time.Millisecond, item.timeout/time.Millisecond) + } else if elapsed > item.timeout+20*time.Millisecond { + t.Logf("The execution should have timed out, but it took too long. Elapsed %d ms, timeout %d ms.", elapsed/time.Millisecond, item.timeout/time.Millisecond) + } + } + if item.expectedTries != currentTry { + t.Errorf("Returned in %d tries (expected %d)", currentTry, item.expectedTries) + } + }) + } + +} diff --git a/internal/utils/tarball.go b/internal/utils/tarball.go new file mode 100644 index 0000000..b9efd96 --- /dev/null +++ b/internal/utils/tarball.go @@ -0,0 +1,255 @@ +package utils + +import ( + "archive/tar" + "bytes" + "compress/gzip" + "fmt" + "io" + "os" + "path" + "strings" + "sync" + "time" +) + +type Tarball struct { + buf *bytes.Buffer + gz *gzip.Writer + tw *tar.Writer + lastAdded time.Time + basePath string + mutex *sync.Mutex +} + +// NewTarball returns an initialized Tarball +func NewTarball() *Tarball { + tb := &Tarball{} + tb.buf = new(bytes.Buffer) + tb.gz = gzip.NewWriter(tb.buf) + tb.tw = tar.NewWriter(tb.gz) + tb.mutex = &sync.Mutex{} + return tb +} + +// Save saves tarball based on added directories. +// The provided filename will be created or truncated +// if it already exists. +// Once Save method is executed, this instance cannot +// be used anymore. +func (t *Tarball) Save(filename string) error { + var err error + err = t.close() + if err != nil { + return err + } + err = os.WriteFile(filename, t.buf.Bytes(), 0644) + if err != nil { + return err + } + return nil +} + +// SaveData saves written files and returns the tarball data. +// Once SaveData method is executed, this instance cannot +// be used anymore. +func (t *Tarball) SaveData() ([]byte, error) { + err := t.close() + if err != nil { + return nil, err + } + return t.buf.Bytes(), nil +} + +func (t *Tarball) close() error { + t.mutex.Lock() + defer t.mutex.Unlock() + var err error + err = t.tw.Flush() + if err != nil { + return err + } + err = t.tw.Close() + if err != nil { + return err + } + err = t.gz.Close() + if err != nil { + return err + } + return nil +} + +// AddFiles adds all files (recursively) based on the +// provided directory. +func (t *Tarball) AddFiles(dir string, includesOnly ...string) error { + t.mutex.Lock() + defer t.mutex.Unlock() + t.lastAdded = time.Now() + if !strings.HasSuffix(dir, "/") { + dir = dir + "/" + } + t.basePath = dir + var err error + if len(includesOnly) > 0 { + for _, inc := range includesOnly { + err = t.addFiles(path.Join(dir, inc)) + } + } else { + err = t.addFiles(dir) + } + t.lastAdded = time.Time{} + t.basePath = "" + return err +} + +func (t *Tarball) addFiles(dir string) error { + entries, err := os.ReadDir(dir) + if err != nil { + return err + } + innerDir := dir[len(t.basePath):] + for _, entry := range entries { + fileStat, err := os.Stat(path.Join(dir, entry.Name())) + if err != nil { + return err + } + if entry.IsDir() { + t.tw.WriteHeader(&tar.Header{ + Name: path.Join(innerDir, entry.Name()) + "/", + Mode: int64(fileStat.Mode()), + Typeflag: tar.TypeDir, + ModTime: fileStat.ModTime(), + }) + err = t.addFiles(path.Join(dir, entry.Name())) + if err != nil { + return err + } + } else { + fileName := path.Join(innerDir, entry.Name()) + err = t.tw.WriteHeader(&tar.Header{ + Name: fileName, + Mode: int64(fileStat.Mode()), + Size: fileStat.Size(), + ModTime: fileStat.ModTime(), + }) + if err != nil { + return err + } + data, err := os.ReadFile(path.Join(dir, entry.Name())) + if err != nil { + return err + } + _, err = t.tw.Write(data) + if err != nil { + return err + } + err = t.tw.Flush() + if err != nil { + return err + } + } + } + return nil +} + +func (t *Tarball) AddFileData(fileName string, mode int64, mod time.Time, data []byte) error { + var err error + err = t.tw.WriteHeader(&tar.Header{ + Name: fileName, + Mode: mode, + Size: int64(len(data)), + ModTime: mod, + }) + if err != nil { + return err + } + _, err = t.tw.Write(data) + if err != nil { + return err + } + err = t.tw.Flush() + if err != nil { + return err + } + return nil +} + +func (t *Tarball) validateOutputPathoutputPath(outputPath string) error { + // Validating outputPath + if outputPath == "" { + return fmt.Errorf("outputPath is empty") + } + outputPathStat, err := os.Stat(outputPath) + if err != nil { + err = os.MkdirAll(outputPath, 0755) + if err != nil { + return err + } + } else if !outputPathStat.Mode().IsDir() { + return fmt.Errorf("outputPath is not a directory") + } + return nil +} + +func (t *Tarball) ExtractData(data []byte, outputPath string) error { + err := t.validateOutputPathoutputPath(outputPath) + if err != nil { + return err + } + reader := bytes.NewReader(data) + return t.extract(reader, outputPath) +} + +// Extract extracts the given tar.gz filename into the provided outputPath +func (t *Tarball) Extract(fileName, outputPath string) error { + err := t.validateOutputPathoutputPath(outputPath) + if err != nil { + return err + } + tgzFile, err := os.Open(fileName) + if err != nil { + return err + } + return t.extract(tgzFile, outputPath) +} + +func (t *Tarball) extract(tgzReader io.Reader, outputPath string) error { + gzipReader, err := gzip.NewReader(tgzReader) + if err != nil { + return err + } + tarReader := tar.NewReader(gzipReader) + for { + header, err := tarReader.Next() + switch { + case err == io.EOF: + return nil + case err != nil: + return err + case header == nil: + continue + } + targetFilePath := path.Join(outputPath, header.Name) + switch header.Typeflag { + case tar.TypeDir: + if _, err := os.Stat(targetFilePath); err != nil { + if err := os.MkdirAll(targetFilePath, 0755); err != nil { + return err + } + } + case tar.TypeReg: + file, err := os.OpenFile(targetFilePath, os.O_CREATE|os.O_RDWR, os.FileMode(header.Mode)) + if err != nil { + return err + } + if _, err := io.Copy(file, tarReader); err != nil { + return err + } + err = file.Close() + if err != nil { + return err + } + } + } +} diff --git a/internal/utils/tarball_test.go b/internal/utils/tarball_test.go new file mode 100644 index 0000000..f90f720 --- /dev/null +++ b/internal/utils/tarball_test.go @@ -0,0 +1,230 @@ +package utils + +import ( + "fmt" + "math" + "os" + "path" + "strings" + "testing" + "time" + + "gotest.tools/v3/assert" +) + +func TestTarball(t *testing.T) { + const testFileContent = "test data" + + for _, tc := range []struct { + description string + files int + directories int + levels int + }{ + { + description: "only-files", + files: 10, + directories: 0, + levels: 1, + }, + { + description: "files-and-directories", + files: 10, + directories: 3, + levels: 1, + }, + { + description: "files-and-directories-multiple-levels", + files: 10, + directories: 3, + levels: 3, + }, + } { + t.Run(tc.description, func(t *testing.T) { + var cleanupList []string + tree := generateDirectoryTree(tc.directories, tc.levels) + baseDir, err := os.MkdirTemp("", "testtarball.*") + assert.Assert(t, err) + defer func() { + assert.Assert(t, os.RemoveAll(baseDir)) + for _, fileOrDir := range cleanupList { + assert.Assert(t, os.RemoveAll(fileOrDir)) + } + }() + now := time.Now() + // Generating files and asserting generation was successful + err = createFiles(baseDir, tc.files, []byte(testFileContent), tree) + assert.Assert(t, err, "unable to create files") + generatedFilesExpected := tc.files * (len(tree) + 1) + dirReader := new(DirectoryReader) + filesFound, err := dirReader.ReadDir(baseDir, nil) + assert.Assert(t, err) + assert.Equal(t, generatedFilesExpected, len(filesFound)) + for _, file := range filesFound { + data, err := os.ReadFile(file) + assert.Assert(t, err) + assert.Equal(t, string(data), testFileContent) + } + var savedData, savedDataExtra []byte + var savedFile = baseDir + ".tar.gz" + var savedFileExtra = baseDir + "-extra.tar.gz" + t.Run(tc.description+"-SaveData", func(t *testing.T) { + // Compressing generated files + tb := NewTarball() + assert.Assert(t, tb != nil) + assert.Assert(t, tb.AddFiles(baseDir)) + savedData, err = tb.SaveData() + assert.Assert(t, err) + assert.Assert(t, len(savedData) > 0) + }) + t.Run(tc.description+"-Save", func(t *testing.T) { + // Compressing generated files + tb := NewTarball() + assert.Assert(t, tb != nil) + assert.Assert(t, tb.AddFiles(baseDir)) + err = tb.Save(savedFile) + assert.Assert(t, err) + cleanupList = append(cleanupList, savedFile) + savedFileStat, err := os.Stat(savedFile) + assert.Assert(t, err) + assert.Assert(t, savedFileStat.Size() == int64(len(savedData))) + }) + t.Run(tc.description+"-AddFileData-SaveData", func(t *testing.T) { + // Compressing generated files adding an extra file + tb := NewTarball() + assert.Assert(t, tb != nil) + assert.Assert(t, tb.AddFiles(baseDir)) + assert.Assert(t, tb.AddFileData("sample.file", 0755, now, []byte(testFileContent))) + savedDataExtra, err = tb.SaveData() + assert.Assert(t, err) + assert.Assert(t, len(savedDataExtra) > 0) + }) + t.Run(tc.description+"-AddFileData-Save", func(t *testing.T) { + // Compressing generated files adding an extra file + tb := NewTarball() + assert.Assert(t, tb != nil) + assert.Assert(t, tb.AddFiles(baseDir)) + assert.Assert(t, tb.AddFileData("sample.file", 0755, now, []byte(testFileContent))) + err = tb.Save(savedFileExtra) + assert.Assert(t, err) + cleanupList = append(cleanupList, savedFileExtra) + savedFileData, err := os.ReadFile(savedFileExtra) + assert.Assert(t, err) + assert.DeepEqual(t, savedFileData, savedDataExtra) + assert.Assert(t, len(savedDataExtra) > len(savedData)) + }) + t.Run(tc.description+"-Uncompress", func(t *testing.T) { + baseDirCopy := baseDir + ".copy" + tb := NewTarball() + err = tb.Extract(savedFile, baseDirCopy) + assert.Assert(t, err) + cleanupList = append(cleanupList, baseDirCopy) + filesFoundCopy, err := dirReader.ReadDir(baseDirCopy, nil) + assert.Assert(t, err) + assert.Equal(t, generatedFilesExpected, len(filesFoundCopy)) + for _, file := range filesFoundCopy { + data, err := os.ReadFile(file) + assert.Assert(t, err) + assert.Equal(t, string(data), testFileContent) + } + }) + t.Run(tc.description+"-UncompressData", func(t *testing.T) { + baseDirCopy := baseDir + ".data" + tb := NewTarball() + err = tb.ExtractData(savedData, baseDirCopy) + assert.Assert(t, err) + cleanupList = append(cleanupList, baseDirCopy) + filesFoundCopy, err := dirReader.ReadDir(baseDirCopy, nil) + assert.Assert(t, err) + assert.Equal(t, generatedFilesExpected, len(filesFoundCopy)) + for _, file := range filesFoundCopy { + data, err := os.ReadFile(file) + assert.Assert(t, err) + assert.Equal(t, string(data), testFileContent) + } + }) + t.Run(tc.description+"-UncompressExtra", func(t *testing.T) { + baseDirExtra := baseDir + ".extra" + tb := NewTarball() + err = tb.Extract(savedFileExtra, baseDirExtra) + assert.Assert(t, err) + cleanupList = append(cleanupList, baseDirExtra) + filesFoundExtra, err := dirReader.ReadDir(baseDirExtra, nil) + assert.Assert(t, err) + assert.Equal(t, generatedFilesExpected+1, len(filesFoundExtra)) + for _, file := range filesFoundExtra { + data, err := os.ReadFile(file) + assert.Assert(t, err) + assert.Equal(t, string(data), testFileContent) + if strings.HasSuffix(file, "sample.file") { + extraFileStat, err := os.Stat(file) + assert.Assert(t, err) + assert.Assert(t, extraFileStat.Mode() == os.FileMode(0755)) + } + } + }) + }) + } +} + +// createFiles iterates through the directory "tree" list, +// creating the given amount of "files" using the provided +// "content" into the "baseDir". +func createFiles(baseDir string, files int, content []byte, tree []string) error { + tree = append(tree, "") + for _, dir := range tree { + err := os.MkdirAll(path.Join(baseDir, dir), 0755) + if err != nil { + return err + } + for i := 1; i <= files; i++ { + filename := path.Join(baseDir, dir, fmt.Sprintf("file.%d", i)) + err = os.WriteFile(filename, content, 0644) + if err != nil { + return err + } + } + } + return nil +} + +// expectedCreatedFiles returns the amount of tiles expected +// to be created based on the provided arguments. +func expectedCreatedFiles(dirs, levels, files int) int { + /* + total of files is: (sum(dirs^!levels))*files + i.e: dir=3,levels=4,files=10 + (3^4 + 3^3 + 3^2 + 3^1) * 10 + */ + sum := 0 + for level := 1; level <= levels; level++ { + sum += int(math.Pow(float64(dirs), float64(level))) + } + sum *= files + return sum + files +} + +func generateDirectoryTree(dirs, levels int) []string { + var tree []string + indexSum := func(level int) int { + sum := 0 + for l := level; l >= 1; l-- { + sum += int(math.Pow(float64(dirs), float64(l))) + } + return sum + } + for level := 1; level <= levels; level++ { + var baseDirs = []string{""} + if level > 1 { + initialIdx := indexSum(level - 2) + finalIdx := indexSum(level - 1) + baseDirs = tree[initialIdx:finalIdx] + } + for _, baseDir := range baseDirs { + for dir := 1; dir <= dirs; dir++ { + tree = append(tree, path.Join(baseDir, fmt.Sprintf("dir%d", dir))) + } + } + } + return tree +} diff --git a/internal/utils/tcp.go b/internal/utils/tcp.go new file mode 100644 index 0000000..7ecd768 --- /dev/null +++ b/internal/utils/tcp.go @@ -0,0 +1,28 @@ +package utils + +import ( + "fmt" + "net" + "strconv" +) + +func TcpPortInUse(host string, port int) bool { + address := net.JoinHostPort(host, strconv.Itoa(port)) + listener, err := net.Listen("tcp", address) + if err != nil { + return true + } + if listener != nil { + _ = listener.Close() + } + return false +} + +func TcpPortNextFree(startPort int) (int, error) { + for port := startPort; port <= 65535; port++ { + if !TcpPortInUse("", port) { + return port, nil + } + } + return 0, fmt.Errorf("no available ports found") +} diff --git a/internal/utils/tcp_test.go b/internal/utils/tcp_test.go new file mode 100644 index 0000000..2fb0db8 --- /dev/null +++ b/internal/utils/tcp_test.go @@ -0,0 +1,63 @@ +package utils + +import ( + "context" + "fmt" + "net" + "sync" + "testing" + + "gotest.tools/v3/assert" +) + +func TestTcpPortNextFree(t *testing.T) { + minPort, err := TcpPortNextFree(1024) + assert.Assert(t, err, "no available tcp ports found") + + ctx, cancel := context.WithCancel(context.Background()) + + // listening on minPort to validate if it reports as in use + wg := listenTcpPort(ctx, minPort) + // waiting on port to be bound + wg.Wait() + + // assert TcpPortNextFree shows a different port + newMinPort, err := TcpPortNextFree(minPort) + assert.Assert(t, err, "no more available tcp ports found") + assert.Assert(t, newMinPort > minPort, "expected next free port available to be higher than %d but got %d", minPort, newMinPort) + cancel() +} + +func TestTcpPortInUse(t *testing.T) { + minPort, err := TcpPortNextFree(1024) + assert.Assert(t, err, "no available tcp ports found") + + ctx := context.Background() + + // listening on minPort to validate if it reports as in use + wg := listenTcpPort(ctx, minPort) + // waiting on port to be bound + wg.Wait() + + // assert TcpPortInUse reports port as being used + assert.Assert(t, TcpPortInUse("", minPort), "%d expected to be in use", minPort) + + // getting an extra port + nextMinPort, err := TcpPortNextFree(minPort) + assert.Assert(t, err, "no more available tcp ports found") + assert.Assert(t, !TcpPortInUse("", nextMinPort), "tcp port %d expected to be available", nextMinPort) +} + +func listenTcpPort(ctx context.Context, port int) *sync.WaitGroup { + wg := new(sync.WaitGroup) + wg.Add(1) + go func() { + listener, err := net.Listen("tcp", fmt.Sprintf(":%d", port)) + wg.Done() + if err != nil { + <-ctx.Done() + _ = listener.Close() + } + }() + return wg +} diff --git a/internal/utils/tlscfg/tls.go b/internal/utils/tlscfg/tls.go new file mode 100644 index 0000000..44d2ec5 --- /dev/null +++ b/internal/utils/tlscfg/tls.go @@ -0,0 +1,32 @@ +package tlscfg + +import "crypto/tls" + +var ( + tlsCiphers []uint16 +) + +func init() { + tlsCiphers = make([]uint16, len(tls.CipherSuites())) + for i, suite := range tls.CipherSuites() { + tlsCiphers[i] = suite.ID + } +} + +// Modern TLS Configuration for when TLSv1.3 can be assumed (e.g. when only +// internal clients are expected.) +func Modern() *tls.Config { + return &tls.Config{ + MinVersion: tls.VersionTLS13, + } +} + +// Default TLS Configuration excludes cipher suites implemented in crypto/tls +// that have been marked insecure. +func Default() *tls.Config { + suites := make([]uint16, len(tlsCiphers)) + copy(suites, tlsCiphers) + return &tls.Config{ + CipherSuites: suites, + } +} diff --git a/internal/utils/utils.go b/internal/utils/utils.go new file mode 100644 index 0000000..bc7658f --- /dev/null +++ b/internal/utils/utils.go @@ -0,0 +1,173 @@ +/* +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package utils + +import ( + "crypto/rand" + "io" + "os" + "os/user" + "regexp" + "strings" +) + +const alphanumerics = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" + +func RandomId(length int) string { + buffer := make([]byte, length) + rand.Read(buffer) + max := len(alphanumerics) + for i := range buffer { + buffer[i] = alphanumerics[int(buffer[i])%max] + } + return string(buffer) +} + +func StringifySelector(labels map[string]string) string { + result := "" + for k, v := range labels { + if result != "" { + result += "," + } + result += k + result += "=" + result += v + } + return result +} + +// LabelToMap expects label string to be a comma separated +// list of key and value pairs delimited by equals. +func LabelToMap(label string) map[string]string { + m := map[string]string{} + labels := strings.Split(label, ",") + for _, l := range labels { + if !strings.Contains(l, "=") { + continue + } + entry := strings.Split(l, "=") + m[entry[0]] = entry[1] + } + return m +} + +func StringSliceContains(s []string, e string) bool { + for _, a := range s { + if a == e { + return true + } + } + return false +} + +func StringSliceEndsWith(s []string, e string) bool { + for _, a := range s { + if strings.HasSuffix(a, e) { + return true + } + } + return false +} + +func RegexpStringSliceContains(s []string, e string) bool { + for _, re := range s { + match, err := regexp.Match(re, []byte(e)) + if err == nil && match { + return true + } + } + return false +} + +func IntSliceContains(s []int, e int) bool { + for _, a := range s { + if a == e { + return true + } + } + return false +} + +func IsDirEmpty(name string) (bool, error) { + file, err := os.Open(name) + + if err != nil { + return false, err + } + defer file.Close() + + _, err = file.Readdir(1) + + if err == io.EOF { + return true, nil + } + + return false, err +} + +func StringSlicesEqual(a, b []string) bool { + if len(a) != len(b) { + return false + } + for _, v := range a { + if !StringSliceContains(b, v) { + return false + } + } + return true +} + +// DefaultStr returns the first non-empty string +func DefaultStr(values ...string) string { + if len(values) == 1 { + return values[0] + } + if values[0] != "" { + return values[0] + } + return DefaultStr(values[1:]...) +} + +func GetOrDefault(str string, defaultStr string) string { + var result string + if len(str) > 0 { + result = str + } else { + result = defaultStr + } + return result +} + +type Number interface { + int | int32 | int64 | float32 | float64 +} + +func DefaultNumber[T Number](values ...T) T { + if len(values) == 1 { + return values[0] + } + if values[0] > 0 { + return values[0] + } + return DefaultNumber(values[1:]...) +} + +func ReadUsername() string { + u, err := user.Current() + if err != nil { + return DefaultStr(os.Getenv("USER"), os.Getenv("USERNAME")) + } + return strings.Join(strings.Fields(u.Username), "") +} diff --git a/internal/utils/utils_test.go b/internal/utils/utils_test.go new file mode 100644 index 0000000..610af60 --- /dev/null +++ b/internal/utils/utils_test.go @@ -0,0 +1,79 @@ +package utils + +import ( + "gotest.tools/v3/assert" + "reflect" + "testing" +) + +func TestStringifySelector(t *testing.T) { + type test struct { + name string + labels map[string]string + result string + } + + testTable := []test{ + {name: "empty-map", labels: map[string]string{}, result: ""}, + {name: "one-label-map", labels: map[string]string{"label1": "value1"}, result: "label1=value1"}, + {name: "three-label-map", labels: map[string]string{"label1": "value1", "label2": "value2", "label3": "value3"}, result: "label1=value1,label2=value2,label3=value3"}, + } + + for _, test := range testTable { + t.Run(test.name, func(t *testing.T) { + // getting result into a map as labels ordering cannot be guaranteed + expectedResMap := LabelToMap(test.result) + actualResMap := LabelToMap(StringifySelector(test.labels)) + assert.Assert(t, reflect.DeepEqual(expectedResMap, actualResMap)) + }) + } +} + +func TestSliceEquals(t *testing.T) { + type test struct { + name string + sliceA []string + sliceB []string + result bool + } + + testTable := []test{ + {name: "not equals", sliceA: []string{"one", "two"}, sliceB: []string{"two", "three"}, result: false}, + {name: "not equals, one is empty", sliceA: []string{}, sliceB: []string{"two", "three"}, result: false}, + {name: "equals", sliceA: []string{"one", "two"}, sliceB: []string{"one", "two"}, result: true}, + {name: "equals but different order", sliceA: []string{"one", "two"}, sliceB: []string{"two", "one"}, result: true}, + } + + for _, test := range testTable { + t.Run(test.name, func(t *testing.T) { + + expectedResult := test.result + actualResult := StringSlicesEqual(test.sliceA, test.sliceB) + assert.Assert(t, reflect.DeepEqual(actualResult, expectedResult)) + }) + } +} + +func TestGetOrDefault(t *testing.T) { + type test struct { + name string + value string + defaultValue string + result string + } + + testTable := []test{ + {name: "empty string", value: "", defaultValue: "default-value", result: "default-value"}, + {name: "valid value", value: "provided-value", defaultValue: "default-value", result: "provided-value"}, + {name: "both empty", value: "", defaultValue: "", result: ""}, + } + + for _, test := range testTable { + t.Run(test.name, func(t *testing.T) { + + expectedResult := test.result + actualResult := GetOrDefault(test.value, test.defaultValue) + assert.Assert(t, reflect.DeepEqual(actualResult, expectedResult)) + }) + } +} diff --git a/internal/utils/validator/simple_validator.go b/internal/utils/validator/simple_validator.go new file mode 100644 index 0000000..d4acbfe --- /dev/null +++ b/internal/utils/validator/simple_validator.go @@ -0,0 +1,207 @@ +package validator + +import ( + "fmt" + "regexp" + "strings" + "time" +) + +type Validator interface { + Evaluate(value interface{}) (bool, error) +} + +// + +type stringValidator struct { + Expression *regexp.Regexp +} + +func NewStringValidator() *stringValidator { + return &stringValidator{ + Expression: regexp.MustCompile(`^\S*$`), + } +} + +func NewHostStringValidator() *stringValidator { + return &stringValidator{ + Expression: regexp.MustCompile(`^[a-z0-9]+([-.]{1}[a-z0-9]+)*$`), + } +} + +func NewResourceStringValidator() *stringValidator { + return &stringValidator{ + Expression: regexp.MustCompile(`^[a-z0-9]([-a-z0-9]*[a-z0-9])*(\.[a-z0-9]([-a-z0-9]*[a-z0-9])*)*$`), + } +} + +// TBD what are valid characters for selector field +func NewSelectorStringValidator() *stringValidator { + return &stringValidator{ + Expression: regexp.MustCompile(`^[A-Za-z0-9=:./-]+$`), + } +} + +func NewFilePathStringValidator() *stringValidator { + return &stringValidator{ + Expression: regexp.MustCompile(`^[A-Za-z0-9./~-]+$`), + } +} + +func (s stringValidator) Evaluate(value interface{}) (bool, error) { + v, ok := value.(string) + + if !ok { + return false, fmt.Errorf("value is not a string") + } + + if s.Expression.MatchString(v) { + return true, nil + } + + return false, fmt.Errorf("value does not match this regular expression: %s", s.Expression) +} + +// + +type NumberValidator struct { + PositiveInt bool + IncludeZero bool +} + +func NewNumberValidator() *NumberValidator { + return &NumberValidator{ + PositiveInt: true, + IncludeZero: true, + } +} + +func (i NumberValidator) Evaluate(value interface{}) (bool, error) { + + v, ok := value.(int) + + if !ok { + return false, fmt.Errorf("value is not an integer") + } + + if i.PositiveInt { + if v < 0 { + return false, fmt.Errorf("value is not positive") + } + if v > 0 { + return true, nil + } + if v == 0 { + if i.IncludeZero { + return true, nil + } + return false, fmt.Errorf("value 0 is not allowed") + } + } + return true, nil +} + +/// + +type OptionValidator struct { + AllowedOptions []string +} + +func NewOptionValidator(validOptions []string) *OptionValidator { + return &OptionValidator{ + AllowedOptions: validOptions, + } +} + +func (i OptionValidator) Evaluate(value interface{}) (bool, error) { + + v, ok := value.(string) + + if !ok { + return false, fmt.Errorf("value is not a string") + } + + if v == "" { + return false, fmt.Errorf("value must not be empty") + } + + valueFound := false + for _, option := range i.AllowedOptions { + if option == v { + valueFound = true + } + } + + if !valueFound { + return false, fmt.Errorf("value %s not allowed. It should be one of this options: %v", v, i.AllowedOptions) + } + return true, nil +} + +type DurationValidator struct { + MinDuration time.Duration +} + +func NewTimeoutInSecondsValidator() *DurationValidator { + return &DurationValidator{ + MinDuration: time.Second * 10, + } +} + +func NewExpirationInSecondsValidator() *DurationValidator { + return &DurationValidator{ + MinDuration: time.Minute * 1, + } +} + +func (i DurationValidator) Evaluate(value time.Duration) (bool, error) { + + if value < i.MinDuration { + return false, fmt.Errorf("duration must not be less than %v; got %v", i.MinDuration, value) + } + + return true, nil +} + +type WorkloadValidator struct { + Expression *regexp.Regexp + AllowedOptions []string +} + +func NewWorkloadStringValidator(validOptions []string) *WorkloadValidator { + re, err := regexp.Compile("^[A-Za-z0-9._-]+$") + if err != nil { + fmt.Printf("Error compiling regex: %v", err) + return nil + } + return &WorkloadValidator{ + Expression: re, + AllowedOptions: validOptions, + } +} + +func (s WorkloadValidator) Evaluate(value interface{}) (string, string, bool, error) { + + v, ok := value.(string) + + if !ok { + return "", "", false, fmt.Errorf("value is not a string") + } + + // workload has two parts / + resource := strings.Split(v, "/") + if len(resource) != 2 { + return "", "", false, fmt.Errorf("workload must include /") + } + + if s.Expression.MatchString(resource[1]) { + resourceType := strings.ToLower(resource[0]) + for _, option := range s.AllowedOptions { + if option == resourceType { + return option, resource[1], true, nil + } + } + return "", "", false, fmt.Errorf("resource-type does not match expected value: deployment/service/daemonset/statefulset") + } + return "", "", false, fmt.Errorf("value does not match this regular expression: %s", s.Expression) +} diff --git a/internal/utils/validator/simple_validator_test.go b/internal/utils/validator/simple_validator_test.go new file mode 100644 index 0000000..f7c35e2 --- /dev/null +++ b/internal/utils/validator/simple_validator_test.go @@ -0,0 +1,310 @@ +package validator + +import ( + "reflect" + "regexp" + "testing" + "time" + + "gotest.tools/v3/assert" +) + +func TestNewStringValidator(t *testing.T) { + + t.Run("Test String Validator constructor", func(t *testing.T) { + + validRegexp := regexp.MustCompile(`^\S*$`) + expectedResult := &stringValidator{validRegexp} + actualResult := NewStringValidator() + assert.Assert(t, reflect.DeepEqual(actualResult, expectedResult)) + }) +} + +func TestStringValidator_Evaluate(t *testing.T) { + type test struct { + name string + value interface{} + result bool + } + + testTable := []test{ + {name: "empty string", value: "", result: true}, + {name: "valid value", value: "provided-value", result: true}, + {name: "string with spaces", value: "provided value", result: false}, + {name: "string with numbers", value: "site123", result: true}, + {name: "number", value: 123, result: false}, + {name: "nil value", value: nil, result: false}, + } + + for _, test := range testTable { + t.Run(test.name, func(t *testing.T) { + + stringValidator := NewStringValidator() + expectedResult := test.result + actualResult, _ := stringValidator.Evaluate(test.value) + assert.Assert(t, reflect.DeepEqual(actualResult, expectedResult)) + }) + } +} + +func TestNewNumberValidator(t *testing.T) { + + t.Run("Test Positive Int Validator constructor", func(t *testing.T) { + + expectedResult := &NumberValidator{PositiveInt: true, IncludeZero: true} + actualResult := NewNumberValidator() + assert.Assert(t, reflect.DeepEqual(actualResult, expectedResult)) + }) +} + +func TestIntegerValidator_Evaluate(t *testing.T) { + type test struct { + name string + value interface{} + result bool + } + + testTable := []test{ + {name: "empty string", value: "", result: false}, + {name: "value greater than zero", value: 235, result: true}, + {name: "zero value", value: 0, result: true}, + {name: "negative number", value: -2, result: false}, + {name: "not valid characters", value: "abc", result: false}, + {name: "nil value", value: nil, result: false}, + } + + for _, test := range testTable { + t.Run(test.name, func(t *testing.T) { + + numberValidator := NewNumberValidator() + + expectedResult := test.result + actualResult, _ := numberValidator.Evaluate(test.value) + assert.Assert(t, reflect.DeepEqual(actualResult, expectedResult)) + }) + } +} + +func TestTimeoutInSecondsValidator_Evaluate(t *testing.T) { + type test struct { + name string + value time.Duration + result bool + } + + testTable := []test{ + {name: "value less than minimum", value: time.Second * 1, result: false}, + {name: "zero value", value: time.Second * 0, result: false}, + } + + for _, test := range testTable { + t.Run(test.name, func(t *testing.T) { + + numberValidator := NewTimeoutInSecondsValidator() + + expectedResult := test.result + actualResult, _ := numberValidator.Evaluate(test.value) + assert.Assert(t, reflect.DeepEqual(actualResult, expectedResult)) + }) + } +} + +func TestNewOptionValidator(t *testing.T) { + + t.Run("Test Option Validator constructor", func(t *testing.T) { + + expectedResult := &OptionValidator{ + AllowedOptions: []string{"a", "b"}, + } + actualResult := NewOptionValidator([]string{"a", "b"}) + assert.Assert(t, reflect.DeepEqual(actualResult, expectedResult)) + }) +} + +func TestOptionValidator_Evaluate(t *testing.T) { + type test struct { + name string + value interface{} + result bool + } + + testTable := []test{ + {name: "empty string", value: "", result: false}, + {name: "value not included", value: "c", result: false}, + {name: "nil value", value: nil, result: false}, + } + + for _, test := range testTable { + t.Run(test.name, func(t *testing.T) { + + optionValidator := NewOptionValidator([]string{"a", "b"}) + expectedResult := test.result + actualResult, _ := optionValidator.Evaluate(test.value) + assert.Assert(t, reflect.DeepEqual(actualResult, expectedResult)) + }) + } +} + +func TestNewResourceStringValidator(t *testing.T) { + + t.Run("Test New Resource String Validator constructor", func(t *testing.T) { + + validRegexp := regexp.MustCompile(`^[a-z0-9]([-a-z0-9]*[a-z0-9])*(\.[a-z0-9]([-a-z0-9]*[a-z0-9])*)*$`) + expectedResult := &stringValidator{validRegexp} + actualResult := NewResourceStringValidator() + assert.Assert(t, reflect.DeepEqual(actualResult, expectedResult)) + }) +} + +func TestNewResourceStringValidator_Evaluate(t *testing.T) { + type test struct { + name string + value interface{} + result bool + } + + testTable := []test{ + {name: "empty string", value: "", result: false}, + {name: "valid value", value: "provided-value", result: true}, + {name: "string with spaces", value: "provided value", result: false}, + {name: "string with numbers", value: "site123", result: true}, + {name: "number", value: 123, result: false}, + {name: "nil value", value: nil, result: false}, + {name: "string with underscore", value: "abc_def", result: false}, + } + + for _, test := range testTable { + t.Run(test.name, func(t *testing.T) { + + stringValidator := NewResourceStringValidator() + expectedResult := test.result + actualResult, _ := stringValidator.Evaluate(test.value) + assert.Assert(t, reflect.DeepEqual(actualResult, expectedResult)) + }) + } +} + +func TestNewSelectorStringValidator(t *testing.T) { + + t.Run("Test New Selector String Validator constructor", func(t *testing.T) { + + validRegexp := regexp.MustCompile("^[A-Za-z0-9=:./-]+$") + expectedResult := &stringValidator{validRegexp} + actualResult := NewSelectorStringValidator() + assert.Assert(t, reflect.DeepEqual(actualResult, expectedResult)) + }) +} + +func TestNewSelectorStringValidator_Evaluate(t *testing.T) { + type test struct { + name string + value interface{} + result bool + } + + testTable := []test{ + {name: "empty string", value: "", result: false}, + {name: "string with spaces", value: "provided value", result: false}, + {name: "string with numbers", value: "site123", result: true}, + {name: "number", value: 123, result: false}, + {name: "nil value", value: nil, result: false}, + {name: "string with underscore", value: "abc_def", result: false}, + {name: "string with equal", value: "abc=def", result: true}, + {name: "string with slash", value: "abc/def", result: true}, + {name: "string with dash", value: "abc-def", result: true}, + {name: "string with dot", value: "provided.value", result: true}, + } + + for _, test := range testTable { + t.Run(test.name, func(t *testing.T) { + stringValidator := NewSelectorStringValidator() + expectedResult := test.result + actualResult, _ := stringValidator.Evaluate(test.value) + assert.Assert(t, reflect.DeepEqual(actualResult, expectedResult)) + }) + } +} + +func TestNewFilePathStringValidator(t *testing.T) { + + t.Run("Test New File Path String Validator constructor", func(t *testing.T) { + + validRegexp := regexp.MustCompile("^[A-Za-z0-9./~-]+$") + expectedResult := &stringValidator{validRegexp} + actualResult := NewFilePathStringValidator() + assert.Assert(t, reflect.DeepEqual(actualResult, expectedResult)) + }) +} + +func TestNewFilePathStringValidator_Evaluate(t *testing.T) { + type test struct { + name string + value interface{} + result bool + } + + testTable := []test{ + {name: "empty string", value: "", result: false}, + {name: "string with spaces", value: "provided value", result: false}, + {name: "string with numbers", value: "site123", result: true}, + {name: "number", value: 123, result: false}, + {name: "nil value", value: nil, result: false}, + {name: "string with underscore", value: "abc_def", result: false}, + {name: "string with equal", value: "abc=def", result: false}, + {name: "string with slash", value: "abc/def", result: true}, + {name: "string with dash", value: "abc-def", result: true}, + {name: "string with dot", value: "provided.value", result: true}, + {name: "valid path", value: "~/tmp/test.yaml", result: true}, + } + + for _, test := range testTable { + t.Run(test.name, func(t *testing.T) { + stringValidator := NewFilePathStringValidator() + expectedResult := test.result + actualResult, _ := stringValidator.Evaluate(test.value) + assert.Assert(t, reflect.DeepEqual(actualResult, expectedResult)) + }) + } +} + +func TestNewWorkloadStringValidator(t *testing.T) { + + t.Run("Test New Workload String Validator constructor", func(t *testing.T) { + + validRegexp := regexp.MustCompile("^[A-Za-z0-9._-]+$") + expectedResult := &WorkloadValidator{validRegexp, []string{"a", "b"}} + actualResult := NewWorkloadStringValidator([]string{"a", "b"}) + assert.Assert(t, reflect.DeepEqual(actualResult, expectedResult)) + }) +} +func TestWorkloadStringValidator_Evaluate(t *testing.T) { + type test struct { + name string + value interface{} + result bool + } + + testTable := []test{ + {name: "empty string", value: "", result: false}, + {name: "valid value", value: "a/name", result: true}, + {name: "string without /", value: "aname", result: false}, + {name: "string with numbers", value: "b/name123", result: true}, + {name: "number", value: 123, result: false}, + {name: "nil value", value: nil, result: false}, + {name: "string without name", value: "a/", result: false}, + {name: "string without type", value: "/name", result: false}, + {name: "string non matching type", value: "c/name", result: false}, + {name: "bad value", value: "a/name@#", result: false}, + {name: "string with dashes", value: "a/workload-with-dashes", result: true}, + } + + for _, test := range testTable { + t.Run(test.name, func(t *testing.T) { + + stringValidator := NewWorkloadStringValidator([]string{"a", "b"}) + expectedResult := test.result + _, _, actualResult, _ := stringValidator.Evaluate(test.value) + assert.Assert(t, reflect.DeepEqual(actualResult, expectedResult)) + }) + } +} diff --git a/internal/utils/version.go b/internal/utils/version.go new file mode 100644 index 0000000..d4a8186 --- /dev/null +++ b/internal/utils/version.go @@ -0,0 +1,115 @@ +package utils + +import ( + "regexp" + "strconv" + "strings" +) + +type Version struct { + Major int + Minor int + Patch int + Qualifier string +} + +func ParseVersion(version string) Version { + var result Version + re := regexp.MustCompile(`^v?(\d+)[\.\+\-](\d+)?(\.(\d+))?\W?(.+)?`) + parts := re.FindStringSubmatch(version) + if len(parts) > 1 { + result.Major, _ = strconv.Atoi(parts[1]) + } + if len(parts) > 2 { + result.Minor, _ = strconv.Atoi(parts[2]) + } + if len(parts) > 4 { + result.Patch, _ = strconv.Atoi(parts[4]) + } + if len(parts) > 5 { + result.Qualifier = parts[5] + } + return result +} + +func (a *Version) MoreRecentThan(b Version) bool { + if a.Major > b.Major { + return true + } else if a.Major < b.Major { + return false + } + // a.Major == b.Major, so look at Minor + if a.Minor > b.Minor { + return true + } else if a.Minor < b.Minor { + return false + } + //a.Minor == b.Minor, so look at Patch + return a.Patch > b.Patch +} + +func (a *Version) LessRecentThan(b Version) bool { + if a.Major < b.Major { + return true + } else if a.Major > b.Major { + return false + } + // a.Major == b.Major, so look at Minor + if a.Minor < b.Minor { + return true + } else if a.Minor > b.Minor { + return false + } + //a.Minor == b.Minor, so look at Patch + return a.Patch < b.Patch +} + +func (a *Version) Equivalent(b Version) bool { + return a.Major == b.Major && a.Minor == b.Minor && a.Patch == b.Patch +} + +func (v *Version) IsUndefined() bool { + return v.Major == 0 && v.Minor == 0 && v.Patch == 0 && v.Qualifier == "" +} + +func EquivalentVersion(a string, b string) bool { + va := ParseVersion(a) + vb := ParseVersion(b) + return va.Equivalent(vb) +} + +func LessRecentThanVersion(a string, b string) bool { + va := ParseVersion(a) + vb := ParseVersion(b) + return va.LessRecentThan(vb) +} + +func MoreRecentThanVersion(a string, b string) bool { + va := ParseVersion(a) + vb := ParseVersion(b) + return va.MoreRecentThan(vb) +} + +func IsValidFor(actual string, minimum string) bool { + if actual == "" { //assume pre 0.5 + return false + } + va := ParseVersion(actual) + vb := ParseVersion(minimum) + return va.IsUndefined() || !va.LessRecentThan(vb) +} + +func GetVersionTag(imageDescriptor string) string { + versionTag := "" + imageDescriptorSlices := strings.Split(imageDescriptor, " ") + + if len(imageDescriptorSlices) > 0 { + imageSlices := strings.Split(imageDescriptorSlices[0], ":") + + if len(imageSlices) > 1 { + versionTag = imageSlices[1] + } + } + + return versionTag +} diff --git a/internal/utils/version_test.go b/internal/utils/version_test.go new file mode 100644 index 0000000..e3f0258 --- /dev/null +++ b/internal/utils/version_test.go @@ -0,0 +1,192 @@ +package utils + +import ( + "reflect" + "testing" +) + +func TestParseVersion(t *testing.T) { + var tests = []struct { + input string + expected Version + }{ + {"1.2.3", Version{1, 2, 3, ""}}, + {"v1.2.3", Version{1, 2, 3, ""}}, + {"v1.2.3-foo", Version{1, 2, 3, "foo"}}, + {"0.22.9993@bar-xyz", Version{0, 22, 9993, "bar-xyz"}}, + {"0e8beee", Version{}}, + {"1231667", Version{}}, + {"3littlepigs", Version{}}, + {"x0.22.9993@bar-xyz", Version{}}, + {"10.22+whatever", Version{10, 22, 0, "whatever"}}, + {"10+whatever", Version{10, 0, 0, "whatever"}}, + {"10+", Version{10, 0, 0, ""}}, + {"10.", Version{10, 0, 0, ""}}, + {"whatever-10-nonsense", Version{}}, + } + for _, test := range tests { + if actual := ParseVersion(test.input); !reflect.DeepEqual(actual, test.expected) { + t.Errorf("Expected %q for %s, got %q", test.expected, test.input, actual) + } + } +} + +func TestIsUndefined(t *testing.T) { + var tests = []struct { + input string + expected bool + }{ + {"1.2.3", false}, + {"v1.2.3", false}, + {"0.22.9993@bar-xyz", false}, + {"x0.22.9993@bar-xyz", true}, + {"10.22+whatever", false}, + {"10+whatever", false}, + {"whatever-10-nonsense", true}, + } + for _, test := range tests { + v := ParseVersion(test.input) + if actual := v.IsUndefined(); actual != test.expected { + t.Errorf("Expected IsUndefined() for %s to be %v, got %v", test.input, test.expected, actual) + } + } +} + +func TestEquivalent(t *testing.T) { + var tests = []struct { + a string + b string + expected bool + }{ + {"1.2.3", "1.2.3", true}, + {"1.2.3", "v1.2.3", true}, + {"v1.2.3", "v1.2.3", true}, + {"v1.2.3", "1.2.3", true}, + {"v1.2.3", "x1.2.3", false}, + {"1.2.3", "0.2.3", false}, + {"1.2.3", "1.2.4", false}, + {"1.2.3", "1.3.2", false}, + {"0.22.9993@bar-xyz", "0.22.9993", true}, + {"0.22.9993@bar-xyz", "0.22.9993+xyz", true}, + {"0.22.9993@bar-xyz", "0.22.999@bar-xyz", false}, + {"10.22+whatever", "10.22", true}, + {"10.22+whatever", "10.22_ignoreme", true}, + {"10.22+whatever", "10.32_ignoreme", false}, + {"10+whatever", "10+", true}, + {"10+whatever", "10-rrr", true}, + {"cat", "dog", true}, + } + for _, test := range tests { + if actual := EquivalentVersion(test.a, test.b); actual != test.expected { + if test.expected { + t.Errorf("Expected %s to be equivalent to %s", test.a, test.b) + } else { + t.Errorf("Expected %s to not be equivalent to %s", test.a, test.b) + } + } + } +} + +func TestMoreRecentThan(t *testing.T) { + var tests = []struct { + a string + b string + expected bool + }{ + {"1.2.3", "1.2.3", false}, + {"1.2.3", "1.2.2", true}, + {"1.2.3", "0.2.3", true}, + {"1.2.3", "0.3.4", true}, + {"1.2.3", "v1.2.3", false}, + {"1.2.3", "1.1.3", true}, + {"1.2.3", "1.1.9", true}, + {"v1.2.3", "1.2.3", false}, + {"v1.2.3", "x1.2.3", true}, + {"v1.2.3", "frog", true}, + {"frog", "v1.2.3", false}, + {"1.2.3", "1.2.4", false}, + {"1.2.3", "1.3.3", false}, + {"1.2.3", "2.1.2", false}, + } + for _, test := range tests { + if actual := MoreRecentThanVersion(test.a, test.b); actual != test.expected { + if test.expected { + t.Errorf("Expected %s to be MoreRecentThan %s", test.a, test.b) + } else { + t.Errorf("Expected %s to not be MoreRecentThan %s", test.a, test.b) + } + } + } +} + +func TestLessRecentThan(t *testing.T) { + var tests = []struct { + a string + b string + expected bool + }{ + {"1.2.3", "1.2.3", false}, + {"1.2.3", "1.2.2", false}, + {"1.2.3", "0.2.3", false}, + {"1.2.3", "0.3.4", false}, + {"1.2.3", "v1.2.3", false}, + {"1.2.3", "1.1.3", false}, + {"1.2.3", "1.1.9", false}, + {"v1.2.3", "1.2.3", false}, + {"v1.2.3", "frog", false}, + {"frog", "v1.2.3", true}, + {"1.2.3", "1.2.4", true}, + {"1.2.3", "1.3.3", true}, + {"1.2.3", "2.1.2", true}, + } + for _, test := range tests { + if actual := LessRecentThanVersion(test.a, test.b); actual != test.expected { + if test.expected { + t.Errorf("Expected %s to be LessRecentThan %s", test.a, test.b) + } else { + t.Errorf("Expected %s to not be LessRecentThan %s", test.a, test.b) + } + } + } +} + +func TestIsValidFor(t *testing.T) { + var tests = []struct { + actual string + minimum string + expected bool + }{ + {"", "0.7.0", false}, + {"0.5.3", "0.7.0", false}, + {"34145a5-modified", "0.7.0", true}, + {"0e8beee", "0.7.0", true}, + {"0.7.0", "0.7.0", true}, + {"0.7.1", "0.7.0", true}, + {"0.8.6", "0.7.5", true}, + {"1.0.0", "0.7.0", true}, + } + for _, test := range tests { + if actual := IsValidFor(test.actual, test.minimum); actual != test.expected { + t.Errorf("Expected IsValidFor(%s, %s) to be %v, got %v", test.actual, test.minimum, test.expected, actual) + } + } +} + +func TestGetVersionFromImageTag(t *testing.T) { + var tests = []struct { + imageTag string + expected string + }{ + {"quay.io/skupper/config-sync:1.4.4 (sha256:3b7e81fc45bd)", "1.4.4"}, + {"quay.io/skupper/config-sync:1.4.4", "1.4.4"}, + {"quay.io/skupper/config-sync (sha256:3b7e81fc45bd)", ""}, + {"", ""}, + {"quay.io/skupper/config-sync:1.4.4-prerelease", "1.4.4-prerelease"}, + {"1.5.1", ""}, + } + for _, test := range tests { + if actual := GetVersionTag(test.imageTag); actual != test.expected { + t.Errorf("Expected GetVersionTag(%s) to be %s, got %s", test.imageTag, test.expected, actual) + } + } +} diff --git a/main.go b/main.go index 5b84b11..e24d244 100644 --- a/main.go +++ b/main.go @@ -21,6 +21,7 @@ import ( rt "github.com/datasance/router/internal/router" sdk "github.com/datasance/iofog-go-sdk/v3/pkg/microservices" + qdr "github.com/datasance/router/internal/qdr" ) var ( @@ -29,7 +30,18 @@ var ( func init() { router = new(rt.Router) - router.Config = new(rt.Config) + router.Config = &rt.Config{ + SslProfiles: make(map[string]rt.IncomingSslProfile), + ConvertedProfiles: make(map[string]qdr.SslProfile), + Listeners: make(map[string]qdr.Listener), + Connectors: make(map[string]qdr.Connector), + Addresses: make(map[string]qdr.Address), + LogConfig: make(map[string]qdr.LogConfig), + Bridges: qdr.BridgeConfig{ + TcpListeners: make(map[string]qdr.TcpEndpoint), + TcpConnectors: make(map[string]qdr.TcpEndpoint), + }, + } } func main() { @@ -52,11 +64,24 @@ func main() { case <-exitChannel: os.Exit(0) case <-confChannel: - newConfig := new(rt.Config) + newConfig := &rt.Config{ + SslProfiles: make(map[string]rt.IncomingSslProfile), + ConvertedProfiles: make(map[string]qdr.SslProfile), + Listeners: make(map[string]qdr.Listener), + Connectors: make(map[string]qdr.Connector), + Addresses: make(map[string]qdr.Address), + LogConfig: make(map[string]qdr.LogConfig), + Bridges: qdr.BridgeConfig{ + TcpListeners: make(map[string]qdr.TcpEndpoint), + TcpConnectors: make(map[string]qdr.TcpEndpoint), + }, + } if err := updateConfig(ioFogClient, newConfig); err != nil { log.Fatal(err) } else { - router.UpdateRouter(newConfig) + if err := router.UpdateRouter(newConfig); err != nil { + log.Printf("Error updating router: %v", err) + } } } }