From d6544957172df0023d5c03fdce35dc157e0c7f1b Mon Sep 17 00:00:00 2001 From: Nicolas Bigler Date: Thu, 14 Dec 2023 16:22:25 +0100 Subject: [PATCH] Remove non-working integration tests Signed-off-by: Nicolas Bigler --- .gitignore | 3 + go.mod | 57 ++--- go.sum | 167 ++++++++------ pkg/cloudscale/metrics_test.go | 72 ------ .../objecstorage_integration_test.go | 158 ------------- pkg/cloudscale/objectstorage.go | 47 ++-- pkg/cmd/cloudscale.go | 31 ++- pkg/cmd/exoscale.go | 52 ++--- pkg/controlAPI/controlapi.go | 22 ++ pkg/exofixtures/types.go | 2 +- pkg/exoscale/dbaas.go | 43 ++-- pkg/exoscale/dbaas_integration_test.go | 212 ------------------ pkg/exoscale/dbaas_test.go | 4 +- pkg/exoscale/objectstorage.go | 42 ++-- .../objectstorage_integration_test.go | 152 ------------- pkg/exoscale/objectstorage_test.go | 198 ---------------- pkg/kubernetes/client.go | 28 ++- pkg/odoo/odoo.go | 2 +- pkg/prom/prom.go | 55 ----- pkg/test/suite.go | 9 - 20 files changed, 284 insertions(+), 1072 deletions(-) delete mode 100644 pkg/cloudscale/metrics_test.go delete mode 100644 pkg/cloudscale/objecstorage_integration_test.go create mode 100644 pkg/controlAPI/controlapi.go delete mode 100644 pkg/exoscale/dbaas_integration_test.go delete mode 100644 pkg/exoscale/objectstorage_integration_test.go delete mode 100644 pkg/exoscale/objectstorage_test.go delete mode 100644 pkg/prom/prom.go diff --git a/.gitignore b/.gitignore index 551222c..462ade6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ +# VSCode +.vscode/ + # Goreleaser /dist/ /.github/release-notes.md diff --git a/go.mod b/go.mod index b08c96c..5c0f3fc 100644 --- a/go.mod +++ b/go.mod @@ -4,29 +4,30 @@ go 1.19 require ( github.com/appuio/appuio-cloud-reporting v0.10.0 + github.com/appuio/control-api v0.30.0 github.com/cloudscale-ch/cloudscale-go-sdk/v2 v2.1.0 github.com/exoscale/egoscale v0.90.1 - github.com/go-logr/logr v1.2.4 - github.com/go-logr/zapr v1.2.4 - github.com/prometheus/client_golang v1.14.0 - github.com/prometheus/client_model v0.3.0 - github.com/prometheus/common v0.40.0 - github.com/stretchr/testify v1.8.2 + github.com/go-logr/logr v1.3.0 + github.com/go-logr/zapr v1.3.0 + github.com/prometheus/client_golang v1.17.0 + github.com/prometheus/common v0.44.0 + github.com/stretchr/testify v1.8.4 github.com/urfave/cli/v2 v2.24.4 github.com/vshn/provider-cloudscale v0.5.0 github.com/vshn/provider-exoscale v0.8.1 - go.uber.org/zap v1.24.0 - golang.org/x/oauth2 v0.4.0 + go.uber.org/zap v1.26.0 + golang.org/x/oauth2 v0.8.0 gopkg.in/dnaeon/go-vcr.v3 v3.1.2 - k8s.io/api v0.26.1 - k8s.io/apimachinery v0.26.1 - k8s.io/client-go v0.26.1 - sigs.k8s.io/controller-runtime v0.14.1 + k8s.io/api v0.26.2 + k8s.io/apimachinery v0.26.2 + k8s.io/client-go v0.26.2 + sigs.k8s.io/controller-runtime v0.14.6 ) require ( github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect github.com/beorn7/perks v1.0.1 // indirect + github.com/blang/semver/v4 v4.0.0 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect github.com/crossplane/crossplane-runtime v0.18.0 // indirect @@ -40,11 +41,11 @@ require ( github.com/go-openapi/swag v0.22.3 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect - github.com/golang/protobuf v1.5.2 // indirect + github.com/golang/protobuf v1.5.3 // indirect github.com/google/gnostic v0.6.9 // indirect github.com/google/go-cmp v0.5.9 // indirect github.com/google/gofuzz v1.2.0 // indirect - github.com/google/uuid v1.3.0 // indirect + github.com/google/uuid v1.4.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-hclog v1.2.0 // indirect github.com/hashicorp/go-retryablehttp v0.7.2 // indirect @@ -60,10 +61,8 @@ require ( github.com/jmoiron/sqlx v1.3.5 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect - github.com/kr/pretty v0.3.0 // indirect github.com/lopezator/migrator v0.3.1 // indirect github.com/mailru/easyjson v0.7.7 // indirect - github.com/mattn/go-isatty v0.0.17 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect @@ -72,31 +71,33 @@ require ( github.com/onsi/gomega v1.24.2 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/prometheus/procfs v0.9.0 // indirect + github.com/prometheus/client_model v0.4.1-0.20230718164431-9a2bf3000d16 // indirect + github.com/prometheus/procfs v0.11.1 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/spf13/afero v1.9.3 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/stretchr/objx v0.5.0 // indirect github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect - go.uber.org/atomic v1.10.0 // indirect - go.uber.org/multierr v1.9.0 // indirect - golang.org/x/crypto v0.6.0 // indirect - golang.org/x/net v0.6.0 // indirect - golang.org/x/sys v0.5.0 // indirect - golang.org/x/term v0.5.0 // indirect - golang.org/x/text v0.7.0 // indirect + go.uber.org/multierr v1.11.0 // indirect + golang.org/x/crypto v0.14.0 // indirect + golang.org/x/net v0.17.0 // indirect + golang.org/x/sys v0.13.0 // indirect + golang.org/x/term v0.13.0 // indirect + golang.org/x/text v0.13.0 // indirect golang.org/x/time v0.3.0 // indirect gomodules.xyz/jsonpatch/v2 v2.2.0 // indirect google.golang.org/appengine v1.6.7 // indirect - google.golang.org/protobuf v1.28.1 // indirect + google.golang.org/protobuf v1.31.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect k8s.io/apiextensions-apiserver v0.26.1 // indirect - k8s.io/component-base v0.26.1 // indirect - k8s.io/klog/v2 v2.80.1 // indirect + k8s.io/apiserver v0.26.2 // indirect + k8s.io/component-base v0.26.2 // indirect + k8s.io/klog/v2 v2.110.1 // indirect k8s.io/kube-openapi v0.0.0-20230118215034-64b6bb138190 // indirect - k8s.io/utils v0.0.0-20230115233650-391b47cb4029 // indirect + k8s.io/utils v0.0.0-20231127182322-b307cd553661 // indirect + sigs.k8s.io/apiserver-runtime v1.1.2-0.20231017233931-4d54d00b524a // indirect sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect sigs.k8s.io/yaml v1.3.0 // indirect diff --git a/go.sum b/go.sum index 1881869..10d7950 100644 --- a/go.sum +++ b/go.sum @@ -38,22 +38,29 @@ cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3f dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= 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/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc= +github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= +github.com/Masterminds/semver/v3 v3.2.0 h1:3MEsd0SM6jqZojhjLWWeBY+Kcjy9i6MQAeY7YgDP83g= +github.com/NYTimes/gziphandler v1.1.1 h1:ZUDjpQae29j0ryrS0u/B8HZfJBtBQHjqw2rQ2cqUQ3I= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/RaveNoX/go-jsoncommentstrip v1.0.0/go.mod h1:78ihd09MekBnJnxpICcwzCMzGrKSKYe4AqU6PDYYpjk= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +github.com/antlr/antlr4/runtime/Go/antlr v1.4.10 h1:yL7+Jz0jTC6yykIK/Wh74gnTJnrGr5AyrNMXuA0gves= github.com/apapsch/go-jsonmerge/v2 v2.0.0 h1:axGnT1gRIfimI7gJifB699GoE/oq+F2MU7Dml6nw9rQ= github.com/apapsch/go-jsonmerge/v2 v2.0.0/go.mod h1:lvDnEdqiQrp0O42VQGgmlKpxL1AP2+08jFMw88y4klk= github.com/appuio/appuio-cloud-reporting v0.10.0 h1:4n9XVlCr5oHfnrg8F8KHueAO+/+Qh+Hb1GIzyTgb8WE= github.com/appuio/appuio-cloud-reporting v0.10.0/go.mod h1:3UppRODpaAHvfJwMEnkYENNXkVxWCptBMbk1Jw3qHww= -github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= -github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= +github.com/appuio/control-api v0.30.0 h1:s0/tJzAuIRuCfxZ3Fn3Z8qgMutxbBS4lMt5xZpiiEE0= +github.com/appuio/control-api v0.30.0/go.mod h1:8B4OnXJj1DrH90HDTMwNnQTtWrnjnm4v19ON8bvJIAk= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= +github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= github.com/bmatcuk/doublestar v1.1.1/go.mod h1:UD6OnuiIn0yFxxA2le/rnRU1G4RaI4UvFv1sNto9p6w= github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= +github.com/cenkalti/backoff v2.1.1+incompatible h1:tKJnvO2kl0zmb/jA5UKAt4VoEVw1qxKWjE/Bpp46npY= github.com/cenkalti/backoff v2.1.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= +github.com/cenkalti/backoff/v4 v4.2.0 h1:HN5dHm3WBOgndBH6E8V0q2jIYIR3s9yglV8k/+MN3u4= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= @@ -70,8 +77,11 @@ github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnht github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I= github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= +github.com/coreos/go-semver v0.3.0 h1:wkHLiw0WNATZnSG7epLsujiMCgPAc9xhjJ4tgnAxmfM= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f h1:JOrtw2xFKzlg+cbHpyrpLDmnN1HqhBfnX7WDiW7eG2c= github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= @@ -94,13 +104,14 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.m github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= 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 h1:4onqiflcdA9EOZ4RxV643DvftH5pOlLGNtQ5lPWQu84= +github.com/evanphx/json-patch v5.6.0+incompatible h1:jBYDEEiFBPxA0v50tFdvOzQQTCvpL6mnFh5mB2/l16U= github.com/evanphx/json-patch/v5 v5.6.0 h1:b91NhWfaz02IuVxO9faSllyAtNXHMPkC5J8sJCLunww= github.com/evanphx/json-patch/v5 v5.6.0/go.mod h1:G79N1coSVB93tBe7j6PhzjmR3/2VvlbKOFpnXhI9Bw4= github.com/exoscale/egoscale v0.90.1 h1:G/Uyz3Yjdvo3H2oOFS5DhnzEZARLh77IhN58xHHFOpI= github.com/exoscale/egoscale v0.90.1/go.mod h1:NDhQbdGNKwnLVC2YGTB6ds9WIPw+V5ckvEEV8ho7pFE= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= +github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk= github.com/flowstack/go-jsonschema v0.1.1/go.mod h1:yL7fNggx1o8rm9RlgXv7hTBWxdBM0rVwpMwimd3F3N0= github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= @@ -110,11 +121,11 @@ github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2 github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= -github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= -github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/zapr v1.2.4 h1:QHVo+6stLbfJmYGkQ7uGHUCu5hnAFAj6mDe6Ea0SeOo= -github.com/go-logr/zapr v1.2.4/go.mod h1:FyHWQIzQORZ0QVE1BtVHv3cKtNLuXsbNLtpuhNapBOA= +github.com/go-logr/logr v1.3.0 h1:2y3SDp0ZXuc6/cjLSZ+Q3ir+QB9T/iG5yYRXqsagWSY= +github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ= +github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg= github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE= github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE= @@ -156,10 +167,12 @@ github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QD 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.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= 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/cel-go v0.13.0 h1:z+8OBOcmh7IeKyqwT/6IlnMvy621fYUqnTVPEdegGlU= github.com/google/gnostic v0.6.9 h1:ZK/5VhkoX835RikCHpSUJV9a+S3e1zLh59YnyWeBW+0= github.com/google/gnostic v0.6.9/go.mod h1:Nm8234We1lq6iB9OmlgNv3nH91XLLVZHCDayfA3xq+E= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= @@ -192,12 +205,15 @@ github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLe github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/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/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= -github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4= +github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/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/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho= +github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.15.0 h1:1JYBfzqrWPcCclBwxFCPAou9n+q86mfnu7NAeHfte7A= github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= @@ -211,6 +227,7 @@ github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1: github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk= github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo= github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8= @@ -278,8 +295,7 @@ github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= 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/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= -github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= -github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= @@ -288,8 +304,8 @@ github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= -github.com/lib/pq v1.10.2 h1:AqzbZs4ZoCBp+GtejcpCpcxM3zlSMx29dXbUSeVtJb8= github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/lib/pq v1.10.6 h1:jbk+ZieJ0D7EVGJYpL9QTz7/YW6UHbmdnZWYyK5cdBs= github.com/lopezator/migrator v0.3.1 h1:ZFPT6aC7+nGWkqhleynABZ6ftycSf6hmHHLOaryq1Og= github.com/lopezator/migrator v0.3.1/go.mod h1:X+lHDMZ9Ci3/KdbypJcQYFFwipVrJsX4fRCQ4QLauYk= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= @@ -304,9 +320,8 @@ github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hd github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= -github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-sqlite3 v1.14.6 h1:dNPt6NO46WmLVt2DLNpwczCmdV5boIZ6g/tlDrlRUbg= github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= +github.com/mattn/go-sqlite3 v1.14.8 h1:gDp86IdQsN/xWjIEmr9MF6o9mpksUgh0fu+9ByFxzIU= github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -327,19 +342,18 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= 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/prometheus/client_golang v1.14.0 h1:nJdhIvne2eSX/XRAFV9PcvFFRbrjbcTUj0VP62TMhnw= -github.com/prometheus/client_golang v1.14.0/go.mod h1:8vpkKitgIVNcqrRBWh1C4TIUQgYNtG/XQE4E/Zae36Y= +github.com/prometheus/client_golang v1.17.0 h1:rl2sfwZMtSthVU752MqfjQozy7blglC+1SOtjMAMh+Q= +github.com/prometheus/client_golang v1.17.0/go.mod h1:VeL+gMmOAxkS2IqfCq0ZmHSL+LjWfWDUmp1mBz9JgUY= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.3.0 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4= -github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w= -github.com/prometheus/common v0.40.0 h1:Afz7EVRqGg2Mqqf4JuF9vdvp1pi220m55Pi9T2JnO4Q= -github.com/prometheus/common v0.40.0/go.mod h1:L65ZJPSmfn/UBWLQIHV7dBrKFidB/wPlF1y5TlSt9OE= -github.com/prometheus/procfs v0.9.0 h1:wzCHvIvM5SxWqYvwgVL7yJY8Lz3PKn49KQtpgMYJfhI= -github.com/prometheus/procfs v0.9.0/go.mod h1:+pB4zwohETzFnmlpe6yd2lSc+0/46IYZRB/chUwxUZY= +github.com/prometheus/client_model v0.4.1-0.20230718164431-9a2bf3000d16 h1:v7DLqVdK4VrYkVD5diGdl4sxJurKJEMnODWRJlxV9oM= +github.com/prometheus/client_model v0.4.1-0.20230718164431-9a2bf3000d16/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU= +github.com/prometheus/common v0.44.0 h1:+5BrQJwiBB9xsMygAB3TNvpQKOwlkc25LbISbrdOOfY= +github.com/prometheus/common v0.44.0/go.mod h1:ofAIvZbQ1e/nugmZGz4/qCb9Ap1VoSTIO7x0VV9VvuY= +github.com/prometheus/procfs v0.11.1 h1:xRC8Iq1yyca5ypa9n1EZnWZkt7dwcoRPQwX/5gwaUuI= +github.com/prometheus/procfs v0.11.1/go.mod h1:eesXgaPo1q7lBpVMoMy0ZOFTth9hBn4W/y0/p/ScXhY= 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/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k= -github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc= @@ -354,9 +368,11 @@ github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6Mwd github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.9.3 h1:41FoI0fD7OR7mGcKE/aOiLkGreyf8ifIOQmJANWogMk= github.com/spf13/afero v1.9.3/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y= +github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= 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/spkg/bom v0.0.0-20160624110644-59b7046e48ad/go.mod h1:qLr4V1qq6nMqFKkMo8ZTx3f+BZEkzsRUY10Xsm2mwU0= +github.com/stoewer/go-strcase v1.2.0 h1:Z2iHWqGXH00XYgqDmNgQbIBxf3wrNq0F3feEy0ainaU= 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= @@ -372,8 +388,8 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= -github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/urfave/cli/v2 v2.24.4 h1:0gyJJEBYtCV87zI/x2nZCPyDxD51K6xM8SkwjHFCNEU= github.com/urfave/cli/v2 v2.24.4/go.mod h1:GHupkWPMM0M/sj1a2b4wUrWBPzazNrIjouW6fmdJLxc= github.com/vshn/provider-cloudscale v0.5.0 h1:C5Cv5MZXLaC4qOQ0B6WTBGiyndQMiDMbhTelF7cJuzw= @@ -389,37 +405,45 @@ github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de 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.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= +go.etcd.io/etcd v0.0.0-20200513171258-e048e166ab9c h1:/RwRVN9EdXAVtdHxP7Ndn/tfmM9/goiwU0QTnLBgS4w= +go.etcd.io/etcd/api/v3 v3.5.6 h1:Cy2qx3npLcYqTKqGJzMypnMv2tiRyifZJ17BlWIWA7A= +go.etcd.io/etcd/client/pkg/v3 v3.5.6 h1:TXQWYceBKqLp4sa87rcPs11SXxUA/mHwH975v+BDvLU= +go.etcd.io/etcd/client/v3 v3.5.6 h1:coLs69PWCXE9G4FKquzNaSHrRyMCAXwF+IX1tAPVO8E= 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.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.37.0 h1:+uFejS4DCfNH6d3xODVIGsdhzgzhh45p9gpbHQMbdZI= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.37.0 h1:yt2NKzK7Vyo6h0+X8BA4FpreZQTlVEIarnsBP/H5mzs= +go.opentelemetry.io/otel v1.11.2 h1:YBZcQlsVekzFsFbjygXMOXSs6pialIZxcjfO/mBDmR0= +go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.11.2 h1:htgM8vZIF8oPSCxa341e3IZ4yr/sKxgu8KZYllByiVY= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.11.2 h1:fqR1kli93643au1RKo0Uma3d2aPQKT+WBKfTSBaKbOc= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.11.2 h1:ERwKPn9Aer7Gxsc0+ZlutlH1bEEAUXAUhqm3Y45ABbk= +go.opentelemetry.io/otel/metric v0.34.0 h1:MCPoQxcg/26EuuJwpYN1mZTeCYAUGx8ABxfW07YkjP8= +go.opentelemetry.io/otel/sdk v1.11.2 h1:GF4JoaEx7iihdMFu30sOyRx52HDHOkl9xQ8SMqNXUiU= +go.opentelemetry.io/otel/trace v1.11.2 h1:Xf7hWSF2Glv0DE3MH7fBHvtpSBsjcBUe5MYAmZM/+y0= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= +go.opentelemetry.io/proto/otlp v0.19.0 h1:IVN6GR+mhC4s5yfcTbmzHYODqvWAp3ZedA2SJPI1Nnw= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= -go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= -go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ= -go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= -go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= -go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= -go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI= -go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= -go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60= -go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg= +go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo= +go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= @@ -433,8 +457,9 @@ golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.6.0 h1:qfktjS5LUO+fFKeJXZ+ikTRijMmljikvG68fpMMruSc= golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= +golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= +golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= 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= @@ -468,7 +493,6 @@ 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.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= 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= @@ -505,8 +529,9 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.6.0 h1:L4ZwwTvKW9gr0ZMS1yrHD9GZhIuVjOBBnaKH+SPQK0Q= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= +golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= 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-20190517181255-950ef44c6e07/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -517,8 +542,8 @@ golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ 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.4.0 h1:NF0gk8LVPg1Ml7SSbGyySuoxdsXitj7TvgvuRxIMc/M= -golang.org/x/oauth2 v0.4.0/go.mod h1:RznEsdpjGAINPTOF0UH/t+xJ75L18YO3Ho6Pyn+uRec= +golang.org/x/oauth2 v0.8.0 h1:6dkIjl3j3LtZ/O3sTgZTMsLKSftL/B8Zgq4huOIIUu8= +golang.org/x/oauth2 v0.8.0/go.mod h1:yr7u4HXZRm1R1kBWqr/xKNqewf0plRYoB7sla+BCIXE= 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= @@ -529,8 +554,8 @@ golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJ 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-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= 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-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -578,15 +603,16 @@ golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/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-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= +golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= 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.5.0 h1:n2a8QNdAb0sZNpU9R1ALUXBbY+w51fCQDN+7EdxNBsY= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek= +golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 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= @@ -596,8 +622,9 @@ 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.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= 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= @@ -657,7 +684,6 @@ golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= -golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -732,6 +758,7 @@ google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6D google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20220107163113-42d7afdf6368/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20230104163317-caabf589fcbf h1:/JqRexUvugu6JURQ0O7RfV1EnvgrOxUV4tSjuAv0Sr0= 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= @@ -751,6 +778,7 @@ google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA5 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.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= +google.golang.org/grpc v1.51.0 h1:E1eGv1FTqoLIdnBCZufiSHgKjlqG6fKFf6pPWtMTh8U= 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= @@ -764,8 +792,8 @@ google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlba 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.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= -google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= +google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= 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= @@ -777,6 +805,7 @@ gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8= 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.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= @@ -794,27 +823,33 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh 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= -k8s.io/api v0.26.1 h1:f+SWYiPd/GsiWwVRz+NbFyCgvv75Pk9NK6dlkZgpCRQ= -k8s.io/api v0.26.1/go.mod h1:xd/GBNgR0f707+ATNyPmQ1oyKSgndzXij81FzWGsejg= +k8s.io/api v0.26.2 h1:dM3cinp3PGB6asOySalOZxEG4CZ0IAdJsrYZXE/ovGQ= +k8s.io/api v0.26.2/go.mod h1:1kjMQsFE+QHPfskEcVNgL3+Hp88B80uj0QtSOlj8itU= k8s.io/apiextensions-apiserver v0.26.1 h1:cB8h1SRk6e/+i3NOrQgSFij1B2S0Y0wDoNl66bn8RMI= k8s.io/apiextensions-apiserver v0.26.1/go.mod h1:AptjOSXDGuE0JICx/Em15PaoO7buLwTs0dGleIHixSM= -k8s.io/apimachinery v0.26.1 h1:8EZ/eGJL+hY/MYCNwhmDzVqq2lPl3N3Bo8rvweJwXUQ= -k8s.io/apimachinery v0.26.1/go.mod h1:tnPmbONNJ7ByJNz9+n9kMjNP8ON+1qoAIIC70lztu74= -k8s.io/client-go v0.26.1 h1:87CXzYJnAMGaa/IDDfRdhTzxk/wzGZ+/HUQpqgVSZXU= -k8s.io/client-go v0.26.1/go.mod h1:IWNSglg+rQ3OcvDkhY6+QLeasV4OYHDjdqeWkDQZwGE= -k8s.io/component-base v0.26.1 h1:4ahudpeQXHZL5kko+iDHqLj/FSGAEUnSVO0EBbgDd+4= -k8s.io/component-base v0.26.1/go.mod h1:VHrLR0b58oC035w6YQiBSbtsf0ThuSwXP+p5dD/kAWU= -k8s.io/klog/v2 v2.80.1 h1:atnLQ121W371wYYFawwYx1aEY2eUfs4l3J72wtgAwV4= -k8s.io/klog/v2 v2.80.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= +k8s.io/apimachinery v0.26.2 h1:da1u3D5wfR5u2RpLhE/ZtZS2P7QvDgLZTi9wrNZl/tQ= +k8s.io/apimachinery v0.26.2/go.mod h1:ats7nN1LExKHvJ9TmwootT00Yz05MuYqPXEXaVeOy5I= +k8s.io/apiserver v0.26.2 h1:Pk8lmX4G14hYqJd1poHGC08G03nIHVqdJMR0SD3IH3o= +k8s.io/apiserver v0.26.2/go.mod h1:GHcozwXgXsPuOJ28EnQ/jXEM9QeG6HT22YxSNmpYNh8= +k8s.io/client-go v0.26.2 h1:s1WkVujHX3kTp4Zn4yGNFK+dlDXy1bAAkIl+cFAiuYI= +k8s.io/client-go v0.26.2/go.mod h1:u5EjOuSyBa09yqqyY7m3abZeovO/7D/WehVVlZ2qcqU= +k8s.io/component-base v0.26.2 h1:IfWgCGUDzrD6wLLgXEstJKYZKAFS2kO+rBRi0p3LqcI= +k8s.io/component-base v0.26.2/go.mod h1:DxbuIe9M3IZPRxPIzhch2m1eT7uFrSBJUBuVCQEBivs= +k8s.io/klog/v2 v2.110.1 h1:U/Af64HJf7FcwMcXyKm2RPM22WZzyR7OSpYj5tg3cL0= +k8s.io/klog/v2 v2.110.1/go.mod h1:YGtd1984u+GgbuZ7e08/yBuAfKLSO0+uR1Fhi6ExXjo= +k8s.io/kms v0.26.2 h1:GM1gg3tFK3OUU/QQFi93yGjG3lJT8s8l3Wkn2+VxBLM= k8s.io/kube-openapi v0.0.0-20230118215034-64b6bb138190 h1:5MAqxJfshQZ9NdSNGAn7CJ9vuBxAiTaqn3B4pfqD+PE= k8s.io/kube-openapi v0.0.0-20230118215034-64b6bb138190/go.mod h1:/BYxry62FuDzmI+i9B+X2pqfySRmSOW2ARmj5Zbqhj0= -k8s.io/utils v0.0.0-20230115233650-391b47cb4029 h1:L8zDtT4jrxj+TaQYD0k8KNlr556WaVQylDXswKmX+dE= -k8s.io/utils v0.0.0-20230115233650-391b47cb4029/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +k8s.io/utils v0.0.0-20231127182322-b307cd553661 h1:FepOBzJ0GXm8t0su67ln2wAZjbQ6RxQGZDnzuLcrUTI= +k8s.io/utils v0.0.0-20231127182322-b307cd553661/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/controller-runtime v0.14.1 h1:vThDes9pzg0Y+UbCPY3Wj34CGIYPgdmspPm2GIpxpzM= -sigs.k8s.io/controller-runtime v0.14.1/go.mod h1:GaRkrY8a7UZF0kqFFbUKG7n9ICiTY5T55P1RiE3UZlU= +sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.35 h1:+xBL5uTc+BkPBwmMi3vYfUJjq+N3K+H6PXeETwf5cPI= +sigs.k8s.io/apiserver-runtime v1.1.2-0.20231017233931-4d54d00b524a h1:gNAaOi/JlTDAzweUgybSazp3DQh30TRrAmb1srdEdIg= +sigs.k8s.io/apiserver-runtime v1.1.2-0.20231017233931-4d54d00b524a/go.mod h1:4b9SbLg7Sf5dlYK6gD3lIn3tHNTpeKUUjLhOLqiN8FE= +sigs.k8s.io/controller-runtime v0.14.6 h1:oxstGVvXGNnMvY7TAESYk+lzr6S3V5VFxQ6d92KcwQA= +sigs.k8s.io/controller-runtime v0.14.6/go.mod h1:WqIdsAY6JBsjfc/CqO0CORmNtoCtE4S6qbPc9s68h+0= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= sigs.k8s.io/structured-merge-diff/v4 v4.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE= diff --git a/pkg/cloudscale/metrics_test.go b/pkg/cloudscale/metrics_test.go deleted file mode 100644 index ddb55ca..0000000 --- a/pkg/cloudscale/metrics_test.go +++ /dev/null @@ -1,72 +0,0 @@ -//go:build integration - -package cloudscale - -import ( - "fmt" - "testing" - "time" - - "github.com/cloudscale-ch/cloudscale-go-sdk/v2" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -// assertEqualfUint64 implements the functionality of assert.Equalf for uint64, because assert.Equalf cannot print uint64 correctly. -// See https://github.com/stretchr/testify/issues/400 -func assertEqualfUint64(t *testing.T, expected uint64, actual uint64, msg string, args ...interface{}) bool { - if expected != actual { - return assert.Fail(t, fmt.Sprintf("Not equal: \n"+ - "expected: %d\n"+ - "actual : %d", expected, actual)) - } - return true -} - -func TestAccumulateBucketMetricsForObjectsUser(t *testing.T) { - zone := "cloudscale" - organization := "inity" - namespace := "testnamespace" - - location, err := time.LoadLocation("Europe/Zurich") - assert.NoError(t, err) - - now := time.Now().In(location) - date := time.Date(now.Year(), now.Month(), now.Day()-1, 0, 0, 0, 0, now.Location()) - - // build input data structure - bucketMetricsInterval := []cloudscale.BucketMetricsInterval{ - { - Start: date, - End: date, - Usage: cloudscale.BucketMetricsIntervalUsage{ - Requests: 5, - StorageBytes: 1000000, - SentBytes: 2000000, - }, - }, - } - bucketMetricsData := cloudscale.BucketMetricsData{ - TimeSeries: bucketMetricsInterval, - } - - accumulated := make(map[AccumulateKey]uint64) - assert.NoError(t, accumulateBucketMetricsForObjectsUser(accumulated, bucketMetricsData, namespace)) - - require.Len(t, accumulated, 3, "incorrect amount of values 'accumulated'") - - key := AccumulateKey{ - Zone: zone, - Namespace: namespace, - Start: date, - } - - key.ProductId = "appcat_object-storage-requests" - assertEqualfUint64(t, uint64(5), accumulated[key], "incorrect value in %s", key) - - key.ProductId = "appcat_object-storage-storage" - assertEqualfUint64(t, uint64(1000000), accumulated[key], "incorrect value in %s", key) - - key.ProductId = "appcat_object-storage-traffic-out" - assertEqualfUint64(t, uint64(2000000), accumulated[key], "incorrect value in %s", key) -} diff --git a/pkg/cloudscale/objecstorage_integration_test.go b/pkg/cloudscale/objecstorage_integration_test.go deleted file mode 100644 index 0ebf09c..0000000 --- a/pkg/cloudscale/objecstorage_integration_test.go +++ /dev/null @@ -1,158 +0,0 @@ -//go:build integration - -package cloudscale - -import ( - "os" - "testing" - "time" - - "github.com/cloudscale-ch/cloudscale-go-sdk/v2" - "github.com/stretchr/testify/suite" - "github.com/vshn/billing-collector-cloudservices/pkg/prom" - "github.com/vshn/billing-collector-cloudservices/pkg/test" - cloudscalev1 "github.com/vshn/provider-cloudscale/apis/cloudscale/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "sigs.k8s.io/controller-runtime/pkg/client" -) - -const objectStorageBind = ":9123" - -type ObjectStorageTestSuite struct { - test.Suite - billingDate time.Time -} - -func (ts *ObjectStorageTestSuite) SetupSuite() { - cloudscaleCRDsPath := os.Getenv("CLOUDSCALE_CRDS_PATH") - ts.Require().NotZero(cloudscaleCRDsPath, "missing env variable CLOUDSCALE_CRDS_PATH") - - ts.SetupEnv([]string{cloudscaleCRDsPath}, objectStorageBind) - - ts.RegisterScheme(cloudscalev1.SchemeBuilder.AddToScheme) -} - -// TestMetrics sets up a couple of buckets and associated namespaces with organizations set. -// The cloudscale client is set up with an HTTP replay recorder (go-vcr) which looks into testdata/ for recorded -// HTTP responses. -// For simplicity reasons, the recorder only uses URL and method for matching recorded responses. The upside -// of this is it doesn't matter when we execute the tests since the date used to fetch metrics doesn't matter for matching. -// Downside of course is it doesn't do any validation related to the date matching but that's not the main thing to test here. -func (ts *ObjectStorageTestSuite) TestMetrics() { - assert := ts.Assert() - ctx := ts.Context - - o, cancel := ts.setupObjectStorage() - defer cancel() - - assertMetrics := []test.PromMetric{ - { - Category: "cloudscale:example-project", - Product: "appcat_object-storage-requests:cloudscale:example-company:example-project", - Value: 100.001, - }, - { - Category: "cloudscale:example-project", - Product: "appcat_object-storage-storage:cloudscale:example-company:example-project", - Value: 1000.000004096, - }, - { - Category: "cloudscale:example-project", - Product: "appcat_object-storage-traffic-out:cloudscale:example-company:example-project", - Value: 50, - }, - { - Category: "cloudscale:next-big-thing", - Product: "appcat_object-storage-requests:cloudscale:big-corporation:next-big-thing", - Value: 0.001, - }, - { - Category: "cloudscale:next-big-thing", - Product: "appcat_object-storage-storage:cloudscale:big-corporation:next-big-thing", - Value: 0, - }, - { - Category: "cloudscale:next-big-thing", - Product: "appcat_object-storage-traffic-out:cloudscale:big-corporation:next-big-thing", - Value: 0, - }, - } - nameNsMap := map[string]string{ - "example-project-a": "example-project", - "example-project-b": "example-project", - "next-big-thing-a": "next-big-thing", - } - nsTenantMap := map[string]string{ - "example-project": "example-company", - "next-big-thing": "big-corporation", - } - ts.ensureBuckets(nameNsMap) - - createdNs := make(map[string]bool) - for _, ns := range nameNsMap { - if _, ok := createdNs[ns]; !ok { - ts.EnsureNS(ns, map[string]string{organizationLabel: nsTenantMap[ns]}) - createdNs[ns] = true - } - } - - testDate := time.Date(2023, 1, 11, 0, 0, 0, 0, time.Local) - metrics, err := o.Accumulate(ctx, testDate) - assert.NoError(err) - - // This test doesn't divide the values, it tests with 1 hour remaining for the day - assert.NoError(Export(metrics, 23)) - assert.NoError(test.AssertPromMetrics(assert, assertMetrics, objectStorageBind), "cannot assert prom metrics") - prom.ResetAppCatMetric() - - // This test simulates exporting the metrics from 06:00 for the rest of the day - // For that we'll have to divide the values by 18 to match up. - for i := range assertMetrics { - assertMetrics[i].Value = assertMetrics[i].Value / float64(18) - } - assert.NoError(Export(metrics, 6)) - assert.NoError(test.AssertPromMetrics(assert, assertMetrics, objectStorageBind), "cannot assert prom metrics") - prom.ResetAppCatMetric() -} - -func (ts *ObjectStorageTestSuite) ensureBuckets(nameNsMap map[string]string) { - resources := make([]client.Object, 0) - for name, ns := range nameNsMap { - resources = append(resources, &cloudscalev1.Bucket{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Labels: map[string]string{namespaceLabel: ns}, - }, - Spec: cloudscalev1.BucketSpec{ - ForProvider: cloudscalev1.BucketParameters{BucketName: name}, - }, - }) - } - ts.EnsureResources(resources...) -} - -func (ts *ObjectStorageTestSuite) setupObjectStorage() (*ObjectStorage, func()) { - assert := ts.Assert() - httpClient, cancel, err := test.RequestRecorder(ts.T(), "testdata/cloudscale/"+ts.T().Name()) - assert.NoError(err) - - c := cloudscale.NewClient(httpClient) - // required to be set when recording new response. - if apiToken := os.Getenv("CLOUDSCALE_API_TOKEN"); apiToken != "" { - c.AuthToken = apiToken - ts.T().Log("API token set") - } else { - ts.T().Log("no API token provided") - } - - o, err := NewObjectStorage(c, ts.Client, "") - assert.NoError(err) - - return o, cancel -} - -// In order for 'go test' to run this suite, we need to create -// a normal test function and pass our suite to suite.Run -func TestObjectStorageTestSuite(t *testing.T) { - suite.Run(t, new(ObjectStorageTestSuite)) -} diff --git a/pkg/cloudscale/objectstorage.go b/pkg/cloudscale/objectstorage.go index 66f1b18..214c8d8 100644 --- a/pkg/cloudscale/objectstorage.go +++ b/pkg/cloudscale/objectstorage.go @@ -4,16 +4,17 @@ import ( "context" "errors" "fmt" - apiv1 "github.com/prometheus/client_golang/api/prometheus/v1" + "time" + + "github.com/vshn/billing-collector-cloudservices/pkg/controlAPI" "github.com/vshn/billing-collector-cloudservices/pkg/kubernetes" "github.com/vshn/billing-collector-cloudservices/pkg/log" "github.com/vshn/billing-collector-cloudservices/pkg/odoo" - "github.com/vshn/billing-collector-cloudservices/pkg/prom" cloudscalev1 "github.com/vshn/provider-cloudscale/apis/cloudscale/v1" - "time" "github.com/cloudscale-ch/cloudscale-go-sdk/v2" "sigs.k8s.io/controller-runtime/pkg/client" + k8s "sigs.k8s.io/controller-runtime/pkg/client" ) type BucketDetail struct { @@ -22,26 +23,26 @@ type BucketDetail struct { } type ObjectStorage struct { - client *cloudscale.Client - k8sClient client.Client - promClient apiv1.API - salesOrderId string - clusterId string - uomMapping map[string]string + client *cloudscale.Client + k8sClient k8s.Client + controlApiClient k8s.Client + salesOrder string + clusterId string + uomMapping map[string]string } const ( namespaceLabel = "crossplane.io/claim-namespace" ) -func NewObjectStorage(client *cloudscale.Client, k8sClient client.Client, promClient apiv1.API, salesOrderId, clusterId string, uomMapping map[string]string) (*ObjectStorage, error) { +func NewObjectStorage(client *cloudscale.Client, k8sClient k8s.Client, controlApiClient k8s.Client, salesOrder, clusterId string, uomMapping map[string]string) (*ObjectStorage, error) { return &ObjectStorage{ - client: client, - k8sClient: k8sClient, - promClient: promClient, - salesOrderId: salesOrderId, - clusterId: clusterId, - uomMapping: uomMapping, + client: client, + k8sClient: k8sClient, + controlApiClient: controlApiClient, + salesOrder: salesOrder, + clusterId: clusterId, + uomMapping: uomMapping, }, nil } @@ -56,9 +57,9 @@ func (o *ObjectStorage) GetMetrics(ctx context.Context, billingDate time.Time) ( return nil, err } - // Fetch organisations in case salesOrderId is missing + // Fetch organisations in case salesOrder is missing var nsTenants map[string]string - if o.salesOrderId == "" { + if o.salesOrder == "" { logger.V(1).Info("Sales order id is missing, fetching namespaces to get the associated org id") nsTenants, err = kubernetes.FetchNamespaceWithOrganizationMap(ctx, o.k8sClient) if err != nil { @@ -83,9 +84,9 @@ func (o *ObjectStorage) GetMetrics(ctx context.Context, billingDate time.Time) ( continue } appuioManaged := true - if o.salesOrderId == "" { + if o.salesOrder == "" { appuioManaged = false - o.salesOrderId, err = prom.GetSalesOrderId(ctx, o.promClient, nsTenants[bd.Namespace]) + o.salesOrder, err = controlAPI.GetSalesOrder(ctx, o.controlApiClient, nsTenants[bd.Namespace]) if err != nil { logger.Error(err, "unable to sync bucket", "namespace", bd.Namespace) continue @@ -136,7 +137,7 @@ func (o *ObjectStorage) createOdooRecord(bucketMetricsData cloudscale.BucketMetr InstanceID: instanceId, ItemDescription: "AppCat Cloudscale ObjectStorage", ItemGroupDescription: itemGroup, - SalesOrderID: o.salesOrderId, + SalesOrder: o.salesOrder, UnitID: o.uomMapping[units[productIdStorage]], ConsumedUnits: storageBytesValue, TimeRange: odoo.TimeRange{ @@ -149,7 +150,7 @@ func (o *ObjectStorage) createOdooRecord(bucketMetricsData cloudscale.BucketMetr InstanceID: instanceId, ItemDescription: "AppCat Cloudscale ObjectStorage", ItemGroupDescription: itemGroup, - SalesOrderID: o.salesOrderId, + SalesOrder: o.salesOrder, UnitID: o.uomMapping[units[productIdTrafficOut]], ConsumedUnits: trafficOutValue, TimeRange: odoo.TimeRange{ @@ -162,7 +163,7 @@ func (o *ObjectStorage) createOdooRecord(bucketMetricsData cloudscale.BucketMetr InstanceID: instanceId, ItemDescription: "AppCat Cloudscale ObjectStorage", ItemGroupDescription: itemGroup, - SalesOrderID: o.salesOrderId, + SalesOrder: o.salesOrder, UnitID: o.uomMapping[units[productIdQueryRequests]], ConsumedUnits: queryRequestsValue, TimeRange: odoo.TimeRange{ diff --git a/pkg/cmd/cloudscale.go b/pkg/cmd/cloudscale.go index a7b8f63..fc7ec33 100644 --- a/pkg/cmd/cloudscale.go +++ b/pkg/cmd/cloudscale.go @@ -2,14 +2,13 @@ package cmd import ( "fmt" - apiv1 "github.com/prometheus/client_golang/api/prometheus/v1" - "github.com/vshn/billing-collector-cloudservices/pkg/odoo" - "github.com/vshn/billing-collector-cloudservices/pkg/prom" "net/http" "os" "sync" "time" + "github.com/vshn/billing-collector-cloudservices/pkg/odoo" + "github.com/cloudscale-ch/cloudscale-go-sdk/v2" "github.com/urfave/cli/v2" cs "github.com/vshn/billing-collector-cloudservices/pkg/cloudscale" @@ -24,6 +23,8 @@ func CloudscaleCmds() *cli.Command { var ( apiToken string kubeconfig string + controlApiUrl string + controlApiToken string days int collectInterval int billingHour int @@ -31,8 +32,7 @@ func CloudscaleCmds() *cli.Command { odooOauthTokenURL string odooClientId string odooClientSecret string - salesOrderId string - prometheusURL string + salesOrder string clusterId string uom string ) @@ -44,6 +44,10 @@ func CloudscaleCmds() *cli.Command { EnvVars: []string{"CLOUDSCALE_API_TOKEN"}, Destination: &apiToken, Required: true, DefaultText: defaultTextForRequiredFlags}, &cli.StringFlag{Name: "kubeconfig", Usage: "Path to a kubeconfig file which will be used instead of url/token flags if set", EnvVars: []string{"KUBECONFIG"}, Destination: &kubeconfig, Required: false, DefaultText: defaultTextForOptionalFlags}, + &cli.StringFlag{Name: "control-api-url", Usage: "URL of the APPUiO Cloud Control API", + EnvVars: []string{"CONTROL_API_URL"}, Destination: &controlApiUrl, Required: false, DefaultText: defaultTextForOptionalFlags}, + &cli.StringFlag{Name: "control-api-token", Usage: "Token of the APPUiO Cloud Control API", + EnvVars: []string{"CONTROL_API_TOKEN"}, Destination: &controlApiToken, Required: false, DefaultText: defaultTextForOptionalFlags}, &cli.IntFlag{Name: "days", Usage: "Days of metrics to fetch since today, set to 0 to get current metrics", EnvVars: []string{"DAYS"}, Destination: &days, Value: 1, Required: false, DefaultText: defaultTextForOptionalFlags}, &cli.StringFlag{Name: "odoo-url", Usage: "URL of the Odoo Metered Billing API", @@ -55,11 +59,9 @@ func CloudscaleCmds() *cli.Command { &cli.StringFlag{Name: "odoo-oauth-client-secret", Usage: "Client secret of the oauth client to interact with Odoo metered billing API", EnvVars: []string{"ODOO_OAUTH_CLIENT_SECRET"}, Destination: &odooClientSecret, Required: true, DefaultText: defaultTextForRequiredFlags}, &cli.StringFlag{Name: "appuio-managed-sales-order", Usage: "Sales order id to save in the billing record for APPUiO Managed only", - EnvVars: []string{"APPUIO_MANAGED_SALES_ORDER"}, Destination: &salesOrderId, Required: false, DefaultText: defaultTextForOptionalFlags}, + EnvVars: []string{"APPUIO_MANAGED_SALES_ORDER"}, Destination: &salesOrder, Required: false, DefaultText: defaultTextForOptionalFlags}, &cli.StringFlag{Name: "cluster-id", Usage: "The cluster id to save in the billing record", EnvVars: []string{"CLUSTER_ID"}, Destination: &clusterId, Required: true, DefaultText: defaultTextForRequiredFlags}, - &cli.StringFlag{Name: "prom-url", Usage: "Prometheus connection URL in the form of http://host:port, required for APPUiO Cloud", - EnvVars: []string{"PROM_URL"}, Destination: &prometheusURL, Value: "http://localhost:9090"}, &cli.StringFlag{Name: "uom", Usage: "Unit of measure mapping between cloud services and Odoo16 in json format", EnvVars: []string{"UOM"}, Destination: &uom, Required: true, DefaultText: defaultTextForRequiredFlags}, &cli.IntFlag{Name: "collect-interval", Usage: "How often to collect the metrics from the Cloud Service in hours - 1-23", @@ -87,17 +89,14 @@ func CloudscaleCmds() *cli.Command { cloudscaleClient.AuthToken = apiToken logger.Info("Creating k8s client") - k8sClient, err := kubernetes.NewClient(kubeconfig) + k8sClient, err := kubernetes.NewClient(kubeconfig, "", "") if err != nil { return fmt.Errorf("k8s client: %w", err) } - var promClient apiv1.API - if salesOrderId == "" { - promClient, err = prom.NewPrometheusAPIClient(prometheusURL) - if err != nil { - return fmt.Errorf("prometheus client: %w", err) - } + k8sControlClient, err := kubernetes.NewClient("", controlApiUrl, controlApiToken) + if err != nil { + return fmt.Errorf("k8s control client: %w", err) } odooClient := odoo.NewOdooAPIClient(c.Context, odooURL, odooOauthTokenURL, odooClientId, odooClientSecret, logger) @@ -107,7 +106,7 @@ func CloudscaleCmds() *cli.Command { return fmt.Errorf("load loaction: %w", err) } - o, err := cs.NewObjectStorage(cloudscaleClient, k8sClient, promClient, salesOrderId, clusterId, mapping) + o, err := cs.NewObjectStorage(cloudscaleClient, k8sClient, k8sControlClient, salesOrder, clusterId, mapping) if err != nil { return fmt.Errorf("object storage: %w", err) } diff --git a/pkg/cmd/exoscale.go b/pkg/cmd/exoscale.go index 29b41f3..a6d99e5 100644 --- a/pkg/cmd/exoscale.go +++ b/pkg/cmd/exoscale.go @@ -2,13 +2,12 @@ package cmd import ( "fmt" - apiv1 "github.com/prometheus/client_golang/api/prometheus/v1" - "github.com/vshn/billing-collector-cloudservices/pkg/odoo" - "github.com/vshn/billing-collector-cloudservices/pkg/prom" "os" "sync" "time" + "github.com/vshn/billing-collector-cloudservices/pkg/odoo" + "github.com/urfave/cli/v2" "github.com/vshn/billing-collector-cloudservices/pkg/exoscale" "github.com/vshn/billing-collector-cloudservices/pkg/kubernetes" @@ -25,12 +24,13 @@ func ExoscaleCmds() *cli.Command { secret string accessKey string kubeconfig string + controlApiUrl string + controlApiToken string odooURL string odooOauthTokenURL string odooClientId string odooClientSecret string - salesOrderId string - prometheusURL string + salesOrder string clusterId string uom string collectInterval int @@ -46,6 +46,10 @@ func ExoscaleCmds() *cli.Command { EnvVars: []string{"EXOSCALE_API_KEY"}, Destination: &accessKey, Required: true, DefaultText: defaultTextForRequiredFlags}, &cli.StringFlag{Name: "kubeconfig", Usage: "Path to a kubeconfig file which will be used instead of url/token flags if set", EnvVars: []string{"KUBECONFIG"}, Destination: &kubeconfig, Required: false, DefaultText: defaultTextForOptionalFlags}, + &cli.StringFlag{Name: "control-api-url", Usage: "URL of the APPUiO Cloud Control API", + EnvVars: []string{"CONTROL_API_URL"}, Destination: &controlApiUrl, Required: false, DefaultText: defaultTextForOptionalFlags}, + &cli.StringFlag{Name: "control-api-token", Usage: "Token of the APPUiO Cloud Control API", + EnvVars: []string{"CONTROL_API_TOKEN"}, Destination: &controlApiToken, Required: false, DefaultText: defaultTextForOptionalFlags}, &cli.StringFlag{Name: "odoo-url", Usage: "URL of the Odoo Metered Billing API", EnvVars: []string{"ODOO_URL"}, Destination: &odooURL, Value: "http://localhost:8080"}, &cli.StringFlag{Name: "odoo-oauth-token-url", Usage: "Oauth Token URL to authenticate with Odoo metered billing API", @@ -55,15 +59,13 @@ func ExoscaleCmds() *cli.Command { &cli.StringFlag{Name: "odoo-oauth-client-secret", Usage: "Client secret of the oauth client to interact with Odoo metered billing API", EnvVars: []string{"ODOO_OAUTH_CLIENT_SECRET"}, Destination: &odooClientSecret, Required: true, DefaultText: defaultTextForRequiredFlags}, &cli.StringFlag{Name: "appuio-managed-sales-order", Usage: "Sales order for APPUiO Managed clusters", - EnvVars: []string{"APPUIO_MANAGED_SALES_ORDER"}, Destination: &salesOrderId, Required: false, DefaultText: defaultTextForOptionalFlags}, + EnvVars: []string{"APPUIO_MANAGED_SALES_ORDER"}, Destination: &salesOrder, Required: false, DefaultText: defaultTextForOptionalFlags}, &cli.IntFlag{Name: "collect-interval", Usage: "How often to collect the metrics from the Cloud Service in hours - 1-23", EnvVars: []string{"COLLECT_INTERVAL"}, Destination: &collectInterval, Required: true, DefaultText: defaultTextForRequiredFlags}, &cli.IntFlag{Name: "billing-hour", Usage: "At what time to start collect the metrics (ex 6 would start running from 6)", EnvVars: []string{"BILLING_HOUR"}, Destination: &billingHour, Required: false, DefaultText: defaultTextForOptionalFlags}, &cli.StringFlag{Name: "cluster-id", Usage: "The cluster id to save in the billing record", EnvVars: []string{"CLUSTER_ID"}, Destination: &clusterId, Required: true, DefaultText: defaultTextForRequiredFlags}, - &cli.StringFlag{Name: "prom-url", Usage: "Prometheus connection URL in the form of http://host:port, required for APPUiO Cloud", - EnvVars: []string{"PROM_URL"}, Destination: &prometheusURL, Value: "http://localhost:9090"}, &cli.StringFlag{Name: "uom", Usage: "Unit of measure mapping between cloud services and Odoo16 in json format", EnvVars: []string{"UOM"}, Destination: &uom, Required: true, DefaultText: defaultTextForRequiredFlags}, }, @@ -94,27 +96,24 @@ func ExoscaleCmds() *cli.Command { } logger.Info("Creating k8s client") - k8sClient, err := kubernetes.NewClient(kubeconfig) + k8sClient, err := kubernetes.NewClient(kubeconfig, "", "") if err != nil { return fmt.Errorf("k8s client: %w", err) } - odooClient := odoo.NewOdooAPIClient(c.Context, odooURL, odooOauthTokenURL, odooClientId, odooClientSecret, logger) - - var promClient apiv1.API - if salesOrderId == "" { - promClient, err = prom.NewPrometheusAPIClient(prometheusURL) - if err != nil { - return fmt.Errorf("prometheus client: %w", err) - } + k8sControlClient, err := kubernetes.NewClient("", controlApiUrl, controlApiToken) + if err != nil { + return fmt.Errorf("k8s control client: %w", err) } + odooClient := odoo.NewOdooAPIClient(c.Context, odooURL, odooOauthTokenURL, odooClientId, odooClientSecret, logger) + if collectInterval < 1 || collectInterval > 23 { // Set to run once a day after billingHour in case the collectInterval is out of boundaries collectInterval = 23 } - o, err := exoscale.NewObjectStorage(exoscaleClient, k8sClient, promClient, salesOrderId, clusterId, mapping) + o, err := exoscale.NewObjectStorage(exoscaleClient, k8sClient, k8sControlClient, salesOrder, clusterId, mapping) if err != nil { return fmt.Errorf("objectbucket service: %w", err) } @@ -177,27 +176,24 @@ func ExoscaleCmds() *cli.Command { } logger.Info("Creating k8s client") - k8sClient, err := kubernetes.NewClient(kubeconfig) + k8sClient, err := kubernetes.NewClient(kubeconfig, "", "") if err != nil { return fmt.Errorf("k8s client: %w", err) } - odooClient := odoo.NewOdooAPIClient(c.Context, odooURL, odooOauthTokenURL, odooClientId, odooClientSecret, logger) - - var promClient apiv1.API - if salesOrderId == "" { - promClient, err = prom.NewPrometheusAPIClient(prometheusURL) - if err != nil { - return fmt.Errorf("prometheus client: %w", err) - } + k8sControlClient, err := kubernetes.NewClient("", controlApiUrl, controlApiToken) + if err != nil { + return fmt.Errorf("k8s control client: %w", err) } + odooClient := odoo.NewOdooAPIClient(c.Context, odooURL, odooOauthTokenURL, odooClientId, odooClientSecret, logger) + if collectInterval < 1 || collectInterval > 24 { // Set to run once a day after billingHour in case the collectInterval is out of boundaries collectInterval = 1 } - d, err := exoscale.NewDBaaS(exoscaleClient, k8sClient, promClient, collectInterval, salesOrderId, clusterId, mapping) + d, err := exoscale.NewDBaaS(exoscaleClient, k8sClient, k8sControlClient, collectInterval, salesOrder, clusterId, mapping) if err != nil { return fmt.Errorf("dbaas service: %w", err) } diff --git a/pkg/controlAPI/controlapi.go b/pkg/controlAPI/controlapi.go new file mode 100644 index 0000000..14385ae --- /dev/null +++ b/pkg/controlAPI/controlapi.go @@ -0,0 +1,22 @@ +package controlAPI + +import ( + "context" + "fmt" + + orgv1 "github.com/appuio/control-api/apis/organization/v1" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +func GetSalesOrder(ctx context.Context, k8sClient client.Client, orgId string) (string, error) { + + org := &orgv1.Organization{} + err := k8sClient.Get(ctx, client.ObjectKey{Name: orgId}, org) + if err != nil { + return "", fmt.Errorf("cannot get Organization object '%s', err: %v", orgId, err) + } + if org.Status.SaleOrderName == "" { + return "", fmt.Errorf("Cannot get SalesOrder from organization object '%s', err: %v", orgId, err) + } + return org.Status.SaleOrderName, nil +} diff --git a/pkg/exofixtures/types.go b/pkg/exofixtures/types.go index e590530..5854e38 100644 --- a/pkg/exofixtures/types.go +++ b/pkg/exofixtures/types.go @@ -48,7 +48,7 @@ const ( defaultUnitDBaaS = "Instances" ) -// BillingTypes contains exoscale service types to ProductId billing Database types +// BillingTypes contains exoscale service types to ProductId billing Odoo types var BillingTypes = map[string]string{ "pg": queryDBaaSPostgres, "mysql": queryDBaaSMysql, diff --git a/pkg/exoscale/dbaas.go b/pkg/exoscale/dbaas.go index a6b1d27..f916b35 100644 --- a/pkg/exoscale/dbaas.go +++ b/pkg/exoscale/dbaas.go @@ -6,11 +6,10 @@ import ( "time" egoscale "github.com/exoscale/egoscale/v2" - apiv1 "github.com/prometheus/client_golang/api/prometheus/v1" + "github.com/vshn/billing-collector-cloudservices/pkg/controlAPI" "github.com/vshn/billing-collector-cloudservices/pkg/kubernetes" "github.com/vshn/billing-collector-cloudservices/pkg/log" "github.com/vshn/billing-collector-cloudservices/pkg/odoo" - "github.com/vshn/billing-collector-cloudservices/pkg/prom" "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime/schema" @@ -54,27 +53,27 @@ type Detail struct { Organization, DBName, Namespace, Plan, Zone, Kind string } -// DBaaS provides DBaaS Database info and required clients +// DBaaS provides DBaaS Odoo info and required clients type DBaaS struct { - exoscaleClient *egoscale.Client - k8sClient k8s.Client - promClient apiv1.API - salesOrderId string - clusterId string - collectInterval int - uomMapping map[string]string + exoscaleClient *egoscale.Client + k8sClient k8s.Client + controlApiClient k8s.Client + salesOrder string + clusterId string + collectInterval int + uomMapping map[string]string } // NewDBaaS creates a Service with the initial setup -func NewDBaaS(exoscaleClient *egoscale.Client, k8sClient k8s.Client, promClient apiv1.API, collectInterval int, salesOrderId, clusterId string, uomMapping map[string]string) (*DBaaS, error) { +func NewDBaaS(exoscaleClient *egoscale.Client, k8sClient k8s.Client, controlApiClient k8s.Client, collectInterval int, salesOrder, clusterId string, uomMapping map[string]string) (*DBaaS, error) { return &DBaaS{ - exoscaleClient: exoscaleClient, - k8sClient: k8sClient, - promClient: promClient, - salesOrderId: salesOrderId, - clusterId: clusterId, - collectInterval: collectInterval, - uomMapping: uomMapping, + exoscaleClient: exoscaleClient, + k8sClient: k8sClient, + controlApiClient: controlApiClient, + salesOrder: salesOrder, + clusterId: clusterId, + collectInterval: collectInterval, + uomMapping: uomMapping, }, nil } @@ -199,10 +198,10 @@ func (ds *DBaaS) AggregateDBaaS(ctx context.Context, exoscaleDBaaS []*egoscale.D if exists && dbaasDetail.Kind == groupVersionKinds[*dbaasUsage.Type].Kind { logger.V(1).Info("Found exoscale dbaas usage", "instance", dbaasUsage.Name, "instance created", dbaasUsage.CreatedAt) - if ds.salesOrderId == "" { - ds.salesOrderId, err = prom.GetSalesOrderId(ctx, ds.promClient, dbaasDetail.Organization) + if ds.salesOrder == "" { + ds.salesOrder, err = controlAPI.GetSalesOrder(ctx, ds.controlApiClient, dbaasDetail.Organization) if err != nil { - logger.Error(err, "Unable to sync DBaaS, cannot get salesOrderId", "namespace", dbaasDetail.Namespace) + logger.Error(err, "Unable to sync DBaaS, cannot get salesOrder", "namespace", dbaasDetail.Namespace) continue } } @@ -213,7 +212,7 @@ func (ds *DBaaS) AggregateDBaaS(ctx context.Context, exoscaleDBaaS []*egoscale.D InstanceID: dbaasDetail.DBName, ItemDescription: "Exoscale DBaaS", ItemGroupDescription: "AppCat Exoscale DBaaS", - SalesOrderID: ds.salesOrderId, + SalesOrder: ds.salesOrder, UnitID: ds.uomMapping[odoo.InstanceHour], ConsumedUnits: 1, TimeRange: odoo.TimeRange{ diff --git a/pkg/exoscale/dbaas_integration_test.go b/pkg/exoscale/dbaas_integration_test.go deleted file mode 100644 index 2a30dcd..0000000 --- a/pkg/exoscale/dbaas_integration_test.go +++ /dev/null @@ -1,212 +0,0 @@ -//go:build integration - -package exoscale - -import ( - "os" - "strings" - "testing" - "time" - - "github.com/exoscale/egoscale/v2/oapi" - "github.com/stretchr/testify/suite" - "github.com/vshn/billing-collector-cloudservices/pkg/kubernetes" - "github.com/vshn/billing-collector-cloudservices/pkg/prom" - "github.com/vshn/billing-collector-cloudservices/pkg/test" - exoscalev1 "github.com/vshn/provider-exoscale/apis/exoscale/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - "k8s.io/apimachinery/pkg/runtime/schema" - "sigs.k8s.io/controller-runtime/pkg/client" -) - -const dbaasBind = ":9124" - -type DBaaSTestSuite struct { - test.Suite - billingDate time.Time -} - -func (ts *DBaaSTestSuite) SetupSuite() { - exoscaleCRDPaths := os.Getenv("EXOSCALE_CRDS_PATH") - ts.Require().NotZero(exoscaleCRDPaths, "missing env variable EXOSCALE_CRDS_PATH") - - ts.SetupEnv([]string{exoscaleCRDPaths}, dbaasBind) - - ts.RegisterScheme(exoscalev1.SchemeBuilder.AddToScheme) -} - -func (ts *DBaaSTestSuite) TestMetrics() { - assert := ts.Assert() - ctx := ts.Context - - ds, cancel := ts.setupDBaaS() - defer cancel() - - type testcase struct { - gvk schema.GroupVersionKind - ns string - plan string - dbType oapi.DbaasServiceTypeName - } - - nsTenantMap := map[string]string{ - "example-project": "example-company", - "next-big-thing": "big-corporation", - } - for ns, tenant := range nsTenantMap { - ts.EnsureNS(ns, map[string]string{kubernetes.OrganizationLabel: tenant}) - } - - tests := make(map[string]testcase) - for key, gvk := range groupVersionKinds { - plan := "hobbyist-2" - // kafka has no hobbyist plan - if key == "kafka" { - plan = "startup-2" - } - tests[key+"-example-project"] = testcase{ - gvk: gvk, - ns: "example-project", - plan: plan, - dbType: oapi.DbaasServiceTypeName(key), - } - } - - tests["pg-expensive-example-project"] = testcase{ - gvk: groupVersionKinds["pg"], - ns: "example-project", - plan: "premium-225", - dbType: "pg", - } - - tests["pg-next-big-thing"] = testcase{ - gvk: groupVersionKinds["pg"], - ns: "next-big-thing", - plan: "business-225", - dbType: "pg", - } - - type expectation struct { - value float64 - tc testcase - } - expectedQuantities := make(map[Key]expectation, 0) - - for name, tc := range tests { - key := NewKey(tc.ns, tc.plan, string(tc.dbType)) - if _, ok := expectedQuantities[key]; !ok { - expectedQuantities[key] = expectation{ - value: 0, - tc: tc, - } - } - expectedQuantities[key] = expectation{ - value: expectedQuantities[key].value + 1, - tc: tc, - } - - obj := &unstructured.Unstructured{} - obj.SetUnstructuredContent(map[string]interface{}{ - "apiVersion": tc.gvk.GroupVersion().String(), - // a bit ugly, but I wanted to avoid adding more than necessary code - "kind": strings.Replace(tc.gvk.Kind, "List", "", 1), - "metadata": map[string]interface{}{ - "name": name, - "labels": map[string]string{ - namespaceLabel: tc.ns, - }, - }, - "spec": map[string]interface{}{ - "forProvider": map[string]interface{}{ - "zone": "ch-gva-2", - }, - }, - }) - ts.EnsureResources(obj) - } - - assertMetrics := []test.PromMetric{ - { - Product: "appcat_opensearch:exoscale:example-company:example-project:hobbyist-2", - Category: "exoscale:example-project", - Value: 1, - }, - { - Product: "appcat_postgres:exoscale:example-company:example-project:premium-225", - Category: "exoscale:example-project", - Value: 1, - }, - { - Product: "appcat_postgres:exoscale:big-corporation:next-big-thing:business-225", - Category: "exoscale:next-big-thing", - Value: 1, - }, - { - Product: "appcat_mysql:exoscale:example-company:example-project:hobbyist-2", - Category: "exoscale:example-project", - Value: 1, - }, - { - Product: "appcat_opensearch:exoscale:example-company:example-project:hobbyist-2", - Category: "exoscale:example-project", - Value: 1, - }, - { - Product: "appcat_redis:exoscale:example-company:example-project:hobbyist-2", - Category: "exoscale:example-project", - Value: 1, - }, - { - Product: "appcat_kafka:exoscale:example-company:example-project:startup-2", - Category: "exoscale:example-project", - Value: 1, - }, - } - - metrics, err := ds.Accumulate(ctx) - assert.NoError(err, "cannot accumulate dbaas") - - assert.NoError(Export(metrics)) - assert.NoError(test.AssertPromMetrics(assert, assertMetrics, dbaasBind), "cannot assert prom metrics") - prom.ResetAppCatMetric() - -} - -type dbaasSource struct { - dbType string - tenant string - namespace string - plan string -} - -func (ts *DBaaSTestSuite) ensureBuckets(nameNsMap map[string]string) { - resources := make([]client.Object, 0) - for name, ns := range nameNsMap { - resources = append(resources, &exoscalev1.Bucket{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Labels: map[string]string{namespaceLabel: ns}, - }, - Spec: exoscalev1.BucketSpec{ - ForProvider: exoscalev1.BucketParameters{BucketName: name}, - }, - }) - } - ts.EnsureResources(resources...) -} - -func (ts *DBaaSTestSuite) setupDBaaS() (*DBaaS, func()) { - exoClient, cancel, err := newEgoscaleClient(ts.T()) - ts.Assert().NoError(err) - - ds, err := NewDBaaS(exoClient, ts.Client, "") - ts.Assert().NoError(err) - return ds, cancel -} - -// In order for 'go test' to run this suite, we need to create -// a normal test function and pass our suite to suite.Run -func TestDBaaSTestSuite(t *testing.T) { - suite.Run(t, new(DBaaSTestSuite)) -} diff --git a/pkg/exoscale/dbaas_test.go b/pkg/exoscale/dbaas_test.go index 299af7e..42949d4 100644 --- a/pkg/exoscale/dbaas_test.go +++ b/pkg/exoscale/dbaas_test.go @@ -23,7 +23,7 @@ func TestDBaaS_aggregatedDBaaS(t *testing.T) { InstanceID: "postgres-abc", ItemDescription: "Exoscale DBaaS", ItemGroupDescription: "AppCat Exoscale DBaaS", - SalesOrderID: "1234", + SalesOrder: "1234", UnitID: "", ConsumedUnits: 1, TimeRange: odoo.TimeRange{ @@ -35,7 +35,7 @@ func TestDBaaS_aggregatedDBaaS(t *testing.T) { ProductID: "appcat-exoscale-dbaas-appcat_postgres-business-128", InstanceID: "postgres-def", ItemDescription: "Exoscale DBaaS", ItemGroupDescription: "AppCat Exoscale DBaaS", - SalesOrderID: "1234", + SalesOrder: "1234", UnitID: "", ConsumedUnits: 1, TimeRange: odoo.TimeRange{ diff --git a/pkg/exoscale/objectstorage.go b/pkg/exoscale/objectstorage.go index 31d03bb..cf97099 100644 --- a/pkg/exoscale/objectstorage.go +++ b/pkg/exoscale/objectstorage.go @@ -3,16 +3,16 @@ package exoscale import ( "context" "fmt" + "time" + egoscale "github.com/exoscale/egoscale/v2" "github.com/exoscale/egoscale/v2/oapi" - apiv1 "github.com/prometheus/client_golang/api/prometheus/v1" + "github.com/vshn/billing-collector-cloudservices/pkg/controlAPI" "github.com/vshn/billing-collector-cloudservices/pkg/exofixtures" "github.com/vshn/billing-collector-cloudservices/pkg/kubernetes" "github.com/vshn/billing-collector-cloudservices/pkg/log" "github.com/vshn/billing-collector-cloudservices/pkg/odoo" - "github.com/vshn/billing-collector-cloudservices/pkg/prom" exoscalev1 "github.com/vshn/provider-exoscale/apis/exoscale/v1" - "time" k8s "sigs.k8s.io/controller-runtime/pkg/client" ) @@ -21,12 +21,12 @@ const productIdStorage = "appcat-exoscale-object-storage" // ObjectStorage gathers bucket data from exoscale provider and cluster and saves to the database type ObjectStorage struct { - k8sClient k8s.Client - exoscaleClient *egoscale.Client - promClient apiv1.API - salesOrderId string - clusterId string - uomMapping map[string]string + k8sClient k8s.Client + exoscaleClient *egoscale.Client + controlApiClient k8s.Client + salesOrder string + clusterId string + uomMapping map[string]string } // BucketDetail a k8s bucket object with relevant data @@ -35,14 +35,14 @@ type BucketDetail struct { } // NewObjectStorage creates an ObjectStorage with the initial setup -func NewObjectStorage(exoscaleClient *egoscale.Client, k8sClient k8s.Client, promClient apiv1.API, salesOrderId, clusterId string, uomMapping map[string]string) (*ObjectStorage, error) { +func NewObjectStorage(exoscaleClient *egoscale.Client, k8sClient k8s.Client, controlApiClient k8s.Client, salesOrder, clusterId string, uomMapping map[string]string) (*ObjectStorage, error) { return &ObjectStorage{ - k8sClient: k8sClient, - exoscaleClient: exoscaleClient, - promClient: promClient, - salesOrderId: salesOrderId, - clusterId: clusterId, - uomMapping: uomMapping, + k8sClient: k8sClient, + exoscaleClient: exoscaleClient, + controlApiClient: controlApiClient, + salesOrder: salesOrder, + clusterId: clusterId, + uomMapping: uomMapping, }, nil } @@ -70,7 +70,7 @@ func (o *ObjectStorage) getBucketUsage(ctx context.Context, bucketDetails []Buck return nil, err } - odooMetrics, err := o.getOdooMeteredBillingRecords(ctx, o.promClient, *resp.JSON200.SosBucketsUsage, bucketDetails) + odooMetrics, err := o.getOdooMeteredBillingRecords(ctx, *resp.JSON200.SosBucketsUsage, bucketDetails) if err != nil { return nil, err } @@ -82,7 +82,7 @@ func (o *ObjectStorage) getBucketUsage(ctx context.Context, bucketDetails []Buck return odooMetrics, nil } -func (o *ObjectStorage) getOdooMeteredBillingRecords(ctx context.Context, promClient apiv1.API, sosBucketsUsage []oapi.SosBucketUsage, bucketDetails []BucketDetail) ([]odoo.OdooMeteredBillingRecord, error) { +func (o *ObjectStorage) getOdooMeteredBillingRecords(ctx context.Context, sosBucketsUsage []oapi.SosBucketUsage, bucketDetails []BucketDetail) ([]odoo.OdooMeteredBillingRecord, error) { logger := log.Logger(ctx) logger.Info("Aggregating buckets by namespace") @@ -112,9 +112,9 @@ func (o *ObjectStorage) getOdooMeteredBillingRecords(ctx context.Context, promCl itemGroup := fmt.Sprintf("APPUiO Managed - Zone: %s / Namespace: %s", o.clusterId, bucketDetail.Namespace) instanceId := fmt.Sprintf("%s/%s", bucketDetail.Zone, bucketDetail.BucketName) - if o.salesOrderId == "" { + if o.salesOrder == "" { itemGroup = fmt.Sprintf("APPUiO Cloud - Zone: %s / Namespace: %s", o.clusterId, bucketDetail.Namespace) - o.salesOrderId, err = prom.GetSalesOrderId(ctx, promClient, bucketDetail.Organization) + o.salesOrder, err = controlAPI.GetSalesOrder(ctx, o.controlApiClient, bucketDetail.Organization) if err != nil { logger.Error(err, "unable to sync bucket", "namespace", bucketDetail.Namespace) continue @@ -126,7 +126,7 @@ func (o *ObjectStorage) getOdooMeteredBillingRecords(ctx context.Context, promCl InstanceID: instanceId, ItemDescription: "AppCat Exoscale ObjectStorage", ItemGroupDescription: itemGroup, - SalesOrderID: o.salesOrderId, + SalesOrder: o.salesOrder, UnitID: o.uomMapping[odoo.GBDay], ConsumedUnits: value, TimeRange: odoo.TimeRange{ diff --git a/pkg/exoscale/objectstorage_integration_test.go b/pkg/exoscale/objectstorage_integration_test.go deleted file mode 100644 index 2a104b6..0000000 --- a/pkg/exoscale/objectstorage_integration_test.go +++ /dev/null @@ -1,152 +0,0 @@ -//go:build integration - -package exoscale - -import ( - "fmt" - "os" - "testing" - "time" - - egoscale "github.com/exoscale/egoscale/v2" - "github.com/stretchr/testify/suite" - "github.com/vshn/billing-collector-cloudservices/pkg/exofixtures" - "github.com/vshn/billing-collector-cloudservices/pkg/kubernetes" - "github.com/vshn/billing-collector-cloudservices/pkg/prom" - "github.com/vshn/billing-collector-cloudservices/pkg/test" - exoscalev1 "github.com/vshn/provider-exoscale/apis/exoscale/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "sigs.k8s.io/controller-runtime/pkg/client" -) - -const objectStorageBind = ":9125" - -type ObjectStorageTestSuite struct { - test.Suite - billingDate time.Time -} - -func (ts *ObjectStorageTestSuite) SetupSuite() { - exoscaleCRDPaths := os.Getenv("EXOSCALE_CRDS_PATH") - ts.Require().NotZero(exoscaleCRDPaths, "missing env variable EXOSCALE_CRDS_PATH") - - ts.SetupEnv([]string{exoscaleCRDPaths}, objectStorageBind) - - ts.RegisterScheme(exoscalev1.SchemeBuilder.AddToScheme) -} - -type objectStorageSource struct { - namespace string - tenant string - objectType exofixtures.ObjectType - billingDate time.Time -} - -func (ts *ObjectStorageTestSuite) TestMetrics() { - assert := ts.Assert() - ctx := ts.Context - - o, cancel := ts.setupObjectStorage() - defer cancel() - - nameNsMap := map[string]string{ - "example-project-a": "example-project", - "example-project-b": "example-project", - "next-big-thing-a": "next-big-thing", - } - nsTenantMap := map[string]string{ - "example-project": "example-company", - "next-big-thing": "big-corporation", - } - ts.ensureBuckets(nameNsMap) - - for ns, tenant := range nsTenantMap { - ts.EnsureNS(ns, map[string]string{kubernetes.OrganizationLabel: tenant}) - } - - assertMetrics := []test.PromMetric{ - { - Product: "appcat_object-storage-storage:exoscale:example-company:example-project", - Category: "exoscale:example-project", - Value: 932.253897190094, - }, - { - Product: "appcat_object-storage-storage:exoscale:big-corporation:next-big-thing", - Category: "exoscale:next-big-thing", - Value: 0, - }, - } - - // This test doesn't divide the values, it tests with 1 hour remaining for the day - metrics, err := o.Accumulate(ctx, 23) - assert.NoError(err, "cannot accumulate exoscale object storage") - assert.NoError(Export(metrics)) - assert.NoError(test.AssertPromMetrics(assert, assertMetrics, objectStorageBind), "cannot assert prom metrics") - prom.ResetAppCatMetric() - - // This test simulates exporting the metrics from 06:00 for the rest of the day - // For that we'll have to divide the values by 18 to match up. - for i := range assertMetrics { - assertMetrics[i].Value = assertMetrics[i].Value / float64(18) - } - metrics, err = o.Accumulate(ctx, 6) - assert.NoError(err, "cannot accumulate exoscale object storage") - assert.NoError(Export(metrics)) - assert.NoError(test.AssertPromMetrics(assert, assertMetrics, objectStorageBind), "cannot assert prom metrics") - prom.ResetAppCatMetric() -} - -func (ts *ObjectStorageTestSuite) ensureBuckets(nameNsMap map[string]string) { - resources := make([]client.Object, 0) - for name, ns := range nameNsMap { - resources = append(resources, &exoscalev1.Bucket{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Labels: map[string]string{namespaceLabel: ns}, - }, - Spec: exoscalev1.BucketSpec{ - ForProvider: exoscalev1.BucketParameters{BucketName: name}, - }, - }) - } - ts.EnsureResources(resources...) -} - -func (ts *ObjectStorageTestSuite) setupObjectStorage() (*ObjectStorage, func()) { - exoClient, cancel, err := newEgoscaleClient(ts.T()) - ts.Assert().NoError(err) - - o, err := NewObjectStorage(exoClient, ts.Client, "") - ts.Assert().NoError(err) - return o, cancel -} - -func newEgoscaleClient(t *testing.T) (*egoscale.Client, func(), error) { - httpClient, cancel, err := test.RequestRecorder(t, "testdata/exoscale/"+t.Name()) - if err != nil { - return nil, nil, fmt.Errorf("request recorder: %w", err) - } - - apiKey := os.Getenv("EXOSCALE_API_KEY") - secret := os.Getenv("EXOSCALE_API_SECRET") - if apiKey != "" && secret != "" { - t.Log("api key & secret set") - } else { - // override empty values since otherwise egoscale complains - apiKey = "NOTVALID" - secret = "NOTVALIDSECRET" - t.Log("api key or secret not set") - } - - exoClient, err := NewClientWithOptions(apiKey, secret, egoscale.ClientOptWithHTTPClient(httpClient)) - if err != nil { - return nil, nil, fmt.Errorf("new client: %w", err) - } - return exoClient, cancel, nil -} - -// In order for 'go test' to run this suite, we need to create -// a normal test function and pass our suite to suite.Run -func TestObjectStorageTestSuite(t *testing.T) { - suite.Run(t, new(ObjectStorageTestSuite)) -} diff --git a/pkg/exoscale/objectstorage_test.go b/pkg/exoscale/objectstorage_test.go deleted file mode 100644 index a1ea313..0000000 --- a/pkg/exoscale/objectstorage_test.go +++ /dev/null @@ -1,198 +0,0 @@ -//go:build integration - -package exoscale - -import ( - "testing" - "time" - - "github.com/exoscale/egoscale/v2/oapi" - "github.com/stretchr/testify/assert" - "github.com/vshn/billing-collector-cloudservices/pkg/exofixtures" - "github.com/vshn/billing-collector-cloudservices/pkg/kubernetes" - exoscalev1 "github.com/vshn/provider-exoscale/apis/exoscale/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -func TestObjectStorage_GetAggregated(t *testing.T) { - defaultKey := NewKey("default") - alphaKey := NewKey("alpha") - omegaKey := NewKey("omega") - - tests := map[string]struct { - givenSosBucketsUsage []oapi.SosBucketUsage - givenBucketDetails []BucketDetail - expectedAggregated map[Key]Aggregated - }{ - "GivenSosBucketsUsageAndBuckets_WhenMatch_ThenExpectAggregatedObjects": { - givenSosBucketsUsage: []oapi.SosBucketUsage{ - createSosBucketUsage("bucket-test-1", 1), - createSosBucketUsage("bucket-test-2", 4), - createSosBucketUsage("bucket-test-3", 9), - createSosBucketUsage("bucket-test-4", 0), - createSosBucketUsage("bucket-test-5", 5), - }, - givenBucketDetails: []BucketDetail{ - createBucketDetail("bucket-test-1", "default", "orgA"), - createBucketDetail("bucket-test-2", "alpha", "orgB"), - createBucketDetail("bucket-test-3", "alpha", "orgB"), - createBucketDetail("bucket-test-4", "omega", "orgC"), - createBucketDetail("no-metrics-bucket", "beta", "orgD"), - }, - expectedAggregated: map[Key]Aggregated{ - defaultKey: createAggregated(defaultKey, "orgA", "default", 1), - alphaKey: createAggregated(alphaKey, "orgB", "alpha", 13), - omegaKey: createAggregated(omegaKey, "orgC", "omega", 0), - }, - }, - "GivenSosBucketsUsageAndBuckets_WhenMatch_ThenExpectNoAggregatedObjects": { - givenSosBucketsUsage: []oapi.SosBucketUsage{ - createSosBucketUsage("bucket-test-1", 1), - createSosBucketUsage("bucket-test-2", 4), - }, - givenBucketDetails: []BucketDetail{ - createBucketDetail("bucket-test-3", "default", "orgA"), - createBucketDetail("bucket-test-4", "alpha", "orgB"), - createBucketDetail("bucket-test-5", "alpha", "orgB"), - }, - expectedAggregated: map[Key]Aggregated{}, - }, - "GivenSosBucketsUsageAndBuckets_WhenSosBucketsUsageEmpty_ThenExpectNoAggregatedObjects": { - givenSosBucketsUsage: []oapi.SosBucketUsage{ - createSosBucketUsage("bucket-test-1", 1), - createSosBucketUsage("bucket-test-2", 4), - }, - givenBucketDetails: []BucketDetail{}, - expectedAggregated: map[Key]Aggregated{}, - }, - "GivenSosBucketsUsageAndBuckets_WhenNoBuckets_ThenExpectNoAggregatedObjects": { - givenSosBucketsUsage: []oapi.SosBucketUsage{}, - givenBucketDetails: []BucketDetail{ - createBucketDetail("bucket-test-3", "default", "orgA"), - createBucketDetail("bucket-test-4", "alpha", "orgB"), - }, - expectedAggregated: map[Key]Aggregated{}, - }, - } - for name, tc := range tests { - t.Run(name, func(t *testing.T) { - // Given - ctx := getTestContext(t) - - // When - aggregated := getOdooMeteredBillingRecords(ctx, tc.givenSosBucketsUsage, tc.givenBucketDetails, 1) - - // Then - assert.Equal(t, tc.expectedAggregated, aggregated) - }) - } -} - -func TestObjectStorage_addOrgAndNamespaceToBucket(t *testing.T) { - tests := map[string]struct { - givenBucketList exoscalev1.BucketList - givenNamespaces map[string]string - expectedBucketDetails []BucketDetail - }{ - "GivenBucketListFromExoscale_WhenOrgAndNamespaces_ThenExpectBucketDetailObjects": { - givenBucketList: exoscalev1.BucketList{ - Items: []exoscalev1.Bucket{ - createBucket("bucket-1", "alpha", "orgA"), - createBucket("bucket-2", "beta", "orgB"), - createBucket("bucket-3", "alpha", "orgA"), - createBucket("bucket-4", "omega", "orgB"), - createBucket("bucket-5", "theta", "orgC"), - }, - }, - givenNamespaces: map[string]string{ - "alpha": "orgA", - "beta": "orgB", - "omega": "orgB", - "theta": "orgC", - }, - expectedBucketDetails: []BucketDetail{ - createBucketDetail("bucket-1", "alpha", "orgA"), - createBucketDetail("bucket-2", "beta", "orgB"), - createBucketDetail("bucket-3", "alpha", "orgA"), - createBucketDetail("bucket-4", "omega", "orgB"), - createBucketDetail("bucket-5", "theta", "orgC"), - }, - }, - "GivenBucketListFromExoscale_WhenNoOrgOrNamespaces_ThenExpectNoBucketDetailObjects": { - givenBucketList: exoscalev1.BucketList{ - Items: []exoscalev1.Bucket{ - createBucket("bucket-1", "", "orgA"), - createBucket("bucket-2", "beta", ""), - createBucket("bucket-3", "", ""), - }, - }, - givenNamespaces: map[string]string{}, - expectedBucketDetails: []BucketDetail{}, - }, - } - for name, tc := range tests { - t.Run(name, func(t *testing.T) { - // Given - ctx := getTestContext(t) - - // When - bucketDetails := addOrgAndNamespaceToBucket(ctx, tc.givenBucketList, tc.givenNamespaces) - - // Then - assert.ElementsMatch(t, tc.expectedBucketDetails, bucketDetails) - }) - } -} - -func createBucket(name, namespace, organization string) exoscalev1.Bucket { - labels := make(map[string]string) - if namespace != "" { - labels[namespaceLabel] = namespace - } - if organization != "" { - labels[kubernetes.OrganizationLabel] = organization - } - return exoscalev1.Bucket{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: namespace, - Labels: labels, - }, - Spec: exoscalev1.BucketSpec{ - ForProvider: exoscalev1.BucketParameters{ - BucketName: name, - }, - }, - } -} - -func createAggregated(key Key, organization, namespace string, size float64) Aggregated { - return Aggregated{ - Key: key, - Value: size / 1024 / 1024 / 1024, - Source: exofixtures.SOSSourceString{ - Namespace: namespace, - Organization: organization, - }, - } -} - -func createBucketDetail(bucketName, namespace, organization string) BucketDetail { - return BucketDetail{ - Organization: organization, - BucketName: bucketName, - Namespace: namespace, - } -} - -func createSosBucketUsage(bucketName string, size int) oapi.SosBucketUsage { - date := time.Now() - actualSize := int64(size) - zone := oapi.ZoneName("ch-gva-2") - return oapi.SosBucketUsage{ - CreatedAt: &date, - Name: &bucketName, - Size: &actualSize, - ZoneName: &zone, - } -} diff --git a/pkg/kubernetes/client.go b/pkg/kubernetes/client.go index 1b418c5..1366f21 100644 --- a/pkg/kubernetes/client.go +++ b/pkg/kubernetes/client.go @@ -3,6 +3,8 @@ package kubernetes import ( "context" "fmt" + + orgv1 "github.com/appuio/control-api/apis/organization/v1" "github.com/vshn/billing-collector-cloudservices/pkg/log" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime/schema" @@ -25,7 +27,7 @@ const ( // NewClient creates a k8s client from the server url and token url // If kubeconfig (path to it) is supplied, that takes precedence. Its use is mainly for local development // since local clusters usually don't have a valid certificate. -func NewClient(kubeconfig string) (client.Client, error) { +func NewClient(kubeconfig, url, token string) (client.Client, error) { scheme := runtime.NewScheme() if err := corev1.AddToScheme(scheme); err != nil { return nil, fmt.Errorf("core scheme: %w", err) @@ -36,17 +38,23 @@ func NewClient(kubeconfig string) (client.Client, error) { if err := cloudscaleapis.AddToScheme(scheme); err != nil { return nil, fmt.Errorf("cloudscale scheme: %w", err) } + if err := orgv1.AddToScheme(scheme); err != nil { + return nil, fmt.Errorf("control api org scheme: %w", err) + } var c client.Client var err error - if kubeconfig != "" { - config, err := restConfig(kubeconfig) - if err != nil { - return nil, fmt.Errorf("cannot initialize k8s client: %w", err) - } + config, err := restConfig(kubeconfig, url, token) + if err != nil { + return nil, fmt.Errorf("cannot initialize k8s client: %w", err) + } + if kubeconfig != "" || (url != "" && token != "") { c, err = client.New(config, client.Options{ Scheme: scheme, }) + if err != nil { + return nil, fmt.Errorf("cannot create new k8s client: %w", err) + } } else { c, err = client.New(ctrl.GetConfigOrDie(), client.Options{ Scheme: scheme, @@ -60,8 +68,12 @@ func NewClient(kubeconfig string) (client.Client, error) { } -func restConfig(kubeconfig string) (*rest.Config, error) { - return clientcmd.BuildConfigFromFlags("", kubeconfig) +func restConfig(kubeconfig string, url string, token string) (*rest.Config, error) { + // kubeconfig takes precedence if set. + if kubeconfig != "" { + return clientcmd.BuildConfigFromFlags("", kubeconfig) + } + return &rest.Config{Host: url, BearerToken: token}, nil } func FetchNamespaceWithOrganizationMap(ctx context.Context, k8sClient client.Client) (map[string]string, error) { diff --git a/pkg/odoo/odoo.go b/pkg/odoo/odoo.go index fcdd625..902b5e0 100644 --- a/pkg/odoo/odoo.go +++ b/pkg/odoo/odoo.go @@ -36,7 +36,7 @@ type OdooMeteredBillingRecord struct { InstanceID string `json:"instance_id"` ItemDescription string `json:"item_description,omitempty"` ItemGroupDescription string `json:"item_group_description,omitempty"` - SalesOrderID string `json:"sales_order_id"` + SalesOrder string `json:"sales_order"` UnitID string `json:"unit_id"` ConsumedUnits float64 `json:"consumed_units"` TimeRange TimeRange `json:"timerange"` diff --git a/pkg/prom/prom.go b/pkg/prom/prom.go deleted file mode 100644 index 4653291..0000000 --- a/pkg/prom/prom.go +++ /dev/null @@ -1,55 +0,0 @@ -package prom - -import ( - "context" - "fmt" - "time" - - "github.com/appuio/appuio-cloud-reporting/pkg/thanos" - "github.com/prometheus/client_golang/api" - apiv1 "github.com/prometheus/client_golang/api/prometheus/v1" - "github.com/prometheus/common/model" -) - -const ( - salesOrderPromMetrics = "appuio_control_organization_info" - salesOrderIDLabel = "sales_order" -) - -func NewPrometheusAPIClient(promURL string) (apiv1.API, error) { - rt := api.DefaultRoundTripper - rt = &thanos.PartialResponseRoundTripper{ - RoundTripper: rt, - } - - client, err := api.NewClient(api.Config{ - Address: promURL, - RoundTripper: rt, - }) - - return apiv1.NewAPI(client), err -} - -// GetSalesOrderId retrieves from prometheus the sales order id associated to orgId -func GetSalesOrderId(ctx context.Context, client apiv1.API, orgId string) (string, error) { - query := salesOrderPromMetrics + fmt.Sprintf("{name=\"%s\"}", orgId) - - res, _, err := client.Query(ctx, query, time.Now()) - if err != nil { - return "", fmt.Errorf("cannot query '%s' for organisation %s, err: %v", salesOrderPromMetrics, orgId, err) - } - samples := res.(model.Vector) - if samples.Len() > 1 { - return "", fmt.Errorf("prometheus metric '%s' has multiple results for organisation %s ", salesOrderPromMetrics, orgId) - } - - return getMetricLabel(samples[0].Metric) -} - -func getMetricLabel(m model.Metric) (string, error) { - value, ok := m[model.LabelName(salesOrderIDLabel)] - if !ok { - return "", fmt.Errorf("no '%s' label in metrics '%s'", salesOrderIDLabel, salesOrderPromMetrics) - } - return string(value), nil -} diff --git a/pkg/test/suite.go b/pkg/test/suite.go index e791e6c..7fae2fc 100644 --- a/pkg/test/suite.go +++ b/pkg/test/suite.go @@ -13,7 +13,6 @@ import ( "github.com/go-logr/logr" "github.com/stretchr/testify/suite" "github.com/vshn/billing-collector-cloudservices/pkg/log" - "github.com/vshn/billing-collector-cloudservices/pkg/prom" "gopkg.in/dnaeon/go-vcr.v3/cassette" "gopkg.in/dnaeon/go-vcr.v3/recorder" corev1 "k8s.io/api/core/v1" @@ -82,14 +81,6 @@ func (ts *Suite) SetupEnv(crdPaths []string, bindString string) { ts.Env = testEnv ts.Config = config ts.Client = k8sClient - - go func() { - assert.NoError(ts.exportMetrics(bindString), "error exportig the metrics") - }() -} - -func (ts *Suite) exportMetrics(bindString string) error { - return prom.ServeMetrics(ts.Context, bindString) } // RegisterScheme passes the current scheme to the given SchemeBuilder func.