diff --git a/examples/signalfxgatewayprometheusremotewritereceiver/Dockerfile.fake-metrics-generator b/examples/signalfxgatewayprometheusremotewritereceiver/Dockerfile.fake-metrics-generator new file mode 100644 index 0000000000..40983256e3 --- /dev/null +++ b/examples/signalfxgatewayprometheusremotewritereceiver/Dockerfile.fake-metrics-generator @@ -0,0 +1,9 @@ +FROM golang:1.19-alpine AS builder +WORKDIR /app +COPY fake-metrics-generator.go go.mod go.sum ./ +RUN CGO_ENABLED=0 GOOS=linux go build -o fake-metrics-generator + +FROM alpine:3.14 +WORKDIR /app +COPY --from=builder /app/fake-metrics-generator . +CMD ["./fake-metrics-generator"] \ No newline at end of file diff --git a/examples/signalfxgatewayprometheusremotewritereceiver/Makefile b/examples/signalfxgatewayprometheusremotewritereceiver/Makefile new file mode 100644 index 0000000000..c1496226e5 --- /dev/null +++ b/examples/signalfxgatewayprometheusremotewritereceiver/Makefile @@ -0,0 +1 @@ +include ../../Makefile.Common \ No newline at end of file diff --git a/examples/signalfxgatewayprometheusremotewritereceiver/README.md b/examples/signalfxgatewayprometheusremotewritereceiver/README.md new file mode 100644 index 0000000000..994528328c --- /dev/null +++ b/examples/signalfxgatewayprometheusremotewritereceiver/README.md @@ -0,0 +1,24 @@ +# SignalFx Gateway Prometheus Remote write Receiver Example + +This example provides a `docker-compose` environment that continually sends some fake prometheus remote writes to an otel receiver replacement for the deprecated SignalFx Gateway for Prometheus Remote Writes. +To run this, ensure you have `docker-compose` installed. + +## Configuration +You can change the exporters to your liking by modifying `otel-collector-config.yaml`. + +Ensure the following environment variables are properly set, should you wish to send data to splunk observability cloud: +1. `SPLUNK_ACCESS_TOKEN` +2. `SPLUNK_REALM` + +Alternatively, you can remove the `signalfx` array item from the `exporters` configuration map in `otel-collector-config.yaml` + +Feel free to modify the sample client to your liking, or even disable it and write your own! + +## Running +Once you've verified your environment, you can run the example by + +```bash +$> docker-compose up +``` + +If everything is configured properly, logs with sample writes should start appearing in stdout shortly. diff --git a/examples/signalfxgatewayprometheusremotewritereceiver/docker-compose.yml b/examples/signalfxgatewayprometheusremotewritereceiver/docker-compose.yml new file mode 100644 index 0000000000..490af720c1 --- /dev/null +++ b/examples/signalfxgatewayprometheusremotewritereceiver/docker-compose.yml @@ -0,0 +1,23 @@ +version: "3.1" +services: + otelcollector: + image: otelcol:latest + container_name: otelcollector + environment: + - SPLUNK_ACCESS_TOKEN=${SPLUNK_ACCESS_TOKEN} + - SPLUNK_REALM=${SPLUNK_REALM} + command: ["--config=/etc/otel-collector-config.yaml", "--set=service.telemetry.logs.level=debug"] + volumes: + - ./otel-collector-config.yaml:/etc/otel-collector-config.yaml + ports: + - "19291:19291" + - "8888:8888" + + fake_metrics_generator: + build: + context: . + dockerfile: Dockerfile.fake-metrics-generator + depends_on: + - otelcollector + environment: + - TARGET_URL=http://otelcollector:19291/metrics diff --git a/examples/signalfxgatewayprometheusremotewritereceiver/fake-metrics-generator.go b/examples/signalfxgatewayprometheusremotewritereceiver/fake-metrics-generator.go new file mode 100644 index 0000000000..1a39a5eea7 --- /dev/null +++ b/examples/signalfxgatewayprometheusremotewritereceiver/fake-metrics-generator.go @@ -0,0 +1,79 @@ +// Copyright OpenTelemetry Authors +// Copyright Splunk, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package main + +import ( + "context" + "net/url" + "os" + "time" + + "github.com/gogo/protobuf/proto" + "github.com/golang/snappy" + "github.com/prometheus/common/config" + "github.com/prometheus/prometheus/prompb" + "github.com/prometheus/prometheus/storage/remote" +) + +func main() { + + URL := &config.URL{ + URL: &url.URL{ + Scheme: "http", + Host: os.Getenv("endpoint"), + Path: os.Getenv("path"), + }, + } + cfg := &remote.ClientConfig{ + URL: URL, + HTTPClientConfig: config.HTTPClientConfig{}, + } + client, err := remote.NewWriteClient("mock_prw_client", cfg) + if err != nil { + panic(err) + } + + metrics := []prompb.TimeSeries{ + { + Labels: []prompb.Label{ + {Name: "__name__", Value: "fake_metric_total"}, + {Name: "instance", Value: cfg.URL.Host}, + }, + Samples: []prompb.Sample{ + {Value: 42, Timestamp: time.Now().UnixMilli()}, + }, + }, + } + + req := &prompb.WriteRequest{ + Timeseries: metrics, + } + compressed := encodeWriteRequest(req) + + for { + client.Store(context.Background(), compressed) + time.Sleep(10 * time.Second) + } +} + +func encodeWriteRequest(request *prompb.WriteRequest) []byte { + data, err := proto.Marshal(request) + if err != nil { + panic(err) + } + + return snappy.Encode(nil, data) +} diff --git a/examples/signalfxgatewayprometheusremotewritereceiver/go.mod b/examples/signalfxgatewayprometheusremotewritereceiver/go.mod new file mode 100644 index 0000000000..1ccefbddae --- /dev/null +++ b/examples/signalfxgatewayprometheusremotewritereceiver/go.mod @@ -0,0 +1,53 @@ +module fake-metrics-generator + +go 1.19 + +require ( + github.com/gogo/protobuf v1.3.2 + github.com/golang/snappy v0.0.4 + github.com/prometheus/common v0.42.0 + github.com/prometheus/prometheus v0.43.1 +) + +require ( + github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 // indirect + github.com/aws/aws-sdk-go v1.44.217 // indirect + github.com/beorn7/perks v1.0.1 // indirect + github.com/cespare/xxhash/v2 v2.2.0 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/dennwc/varint v1.0.0 // indirect + github.com/felixge/httpsnoop v1.0.3 // indirect + github.com/go-kit/log v0.2.1 // indirect + github.com/go-logfmt/logfmt v0.6.0 // indirect + github.com/go-logr/logr v1.2.3 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/golang/protobuf v1.5.3 // indirect + github.com/grafana/regexp v0.0.0-20221122212121-6b5c0a4cb7fd // indirect + github.com/jmespath/go-jmespath v0.4.0 // indirect + github.com/jpillora/backoff v1.0.0 // indirect + github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect + github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/prometheus/client_golang v1.14.0 // indirect + github.com/prometheus/client_model v0.3.0 // indirect + github.com/prometheus/common/sigv4 v0.1.0 // indirect + github.com/prometheus/procfs v0.9.0 // indirect + github.com/stretchr/testify v1.8.2 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.40.0 // indirect + go.opentelemetry.io/otel v1.14.0 // indirect + go.opentelemetry.io/otel/metric v0.37.0 // indirect + go.opentelemetry.io/otel/trace v1.14.0 // indirect + go.uber.org/atomic v1.10.0 // indirect + go.uber.org/goleak v1.2.1 // indirect + golang.org/x/exp v0.0.0-20230307190834-24139beb5833 // indirect + golang.org/x/net v0.8.0 // indirect + golang.org/x/oauth2 v0.6.0 // indirect + golang.org/x/sys v0.6.0 // indirect + golang.org/x/text v0.8.0 // indirect + golang.org/x/time v0.3.0 // indirect + google.golang.org/appengine v1.6.7 // indirect + google.golang.org/protobuf v1.29.0 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/examples/signalfxgatewayprometheusremotewritereceiver/go.sum b/examples/signalfxgatewayprometheusremotewritereceiver/go.sum new file mode 100644 index 0000000000..fc12b4fd51 --- /dev/null +++ b/examples/signalfxgatewayprometheusremotewritereceiver/go.sum @@ -0,0 +1,652 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= +cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= +cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= +cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= +cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= +cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= +cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= +cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= +cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= +cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= +cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= +cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= +cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= +cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= +cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= +cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= +cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +github.com/Azure/azure-sdk-for-go v65.0.0+incompatible h1:HzKLt3kIwMm4KeJYTdx9EbjRYTySD/t8i1Ee/W5EGXw= +github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs= +github.com/Azure/go-autorest/autorest v0.11.28 h1:ndAExarwr5Y+GaHE6VCaY1kyS/HwwGGyuimVhWsHOEM= +github.com/Azure/go-autorest/autorest/adal v0.9.22 h1:/GblQdIudfEM3AWWZ0mrYJQSd7JS4S/Mbzh6F0ov0Xc= +github.com/Azure/go-autorest/autorest/date v0.3.0 h1:7gUk1U5M/CQbp9WoqinNzJar+8KY+LPI6wiWrP/myHw= +github.com/Azure/go-autorest/autorest/to v0.4.0 h1:oXVqrxakqqV1UZdSazDOPOLvOIz+XA683u8EctwboHk= +github.com/Azure/go-autorest/autorest/validation v0.3.1 h1:AgyqjAd94fwNAoTjl/WQXg4VvFeRFpO+UhNyRXqF1ac= +github.com/Azure/go-autorest/logger v0.2.1 h1:IG7i4p/mDa2Ce4TRyAO8IHnVhAVF3RFU+ZtXWSmf4Tg= +github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo= +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/Microsoft/go-winio v0.6.0 h1:slsWYD/zyx7lCXoZVlvQrj0hPTM1HI4+v1sIda2yDvg= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= +github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 h1:s6gZFSlWYmbqAuRjVTiNNhvNRfY2Wxp9nhfyel4rklc= +github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE= +github.com/armon/go-metrics v0.4.1 h1:hR91U9KYmb6bLBYLQjyM+3j+rcd/UhE+G78SFnF8gJA= +github.com/aws/aws-sdk-go v1.38.35/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro= +github.com/aws/aws-sdk-go v1.44.217 h1:FcWC56MRl+k756aH3qeMQTylSdeJ58WN0iFz3fkyRz0= +github.com/aws/aws-sdk-go v1.44.217/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= +github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cncf/xds/go v0.0.0-20230112175826-46e39c7b9b43 h1:XP+uhjN0yBCN/tPkr8Z0BNDc5rZam9RG6UWyf2FrSQ0= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dennwc/varint v1.0.0 h1:kGNFFSSw8ToIy3obO/kKr8U9GZYUAxQEVuix4zfDWzE= +github.com/dennwc/varint v1.0.0/go.mod h1:hnItb35rvZvJrbTALZtY/iQfDs48JKRG1RPpgziApxA= +github.com/digitalocean/godo v1.97.0 h1:p9w1yCcWMZcxFSLPToNGXA96WfUVLXqoHti6GzVomL4= +github.com/docker/distribution v2.8.1+incompatible h1:Q50tZOPR6T/hjNsyc9g8/syEs6bk8XXApsHjKukMl68= +github.com/docker/docker v23.0.1+incompatible h1:vjgvJZxprTTE1A37nm+CLNAdwu6xZekyoiVlUZEINcY= +github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= +github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= +github.com/edsrzf/mmap-go v1.1.0 h1:6EUwBLQ/Mcr1EYLE4Tn1VdW1A4ckqCQWZBw8Hr0kjpQ= +github.com/emicklei/go-restful/v3 v3.10.1 h1:rc42Y5YTp7Am7CS630D7JmhRjq4UlEUuEKfrDac4bSQ= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/go-control-plane v0.11.0 h1:jtLewhRR2vMRNnq2ZZUoCjUlgut+Y0+sDDWPOfwOi1o= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/envoyproxy/protoc-gen-validate v0.9.1 h1:PS7VIOgmSVhWUEeZwTe7z7zouA22Cr590PzXKbZHOVY= +github.com/fatih/color v1.14.1 h1:qfhVLaG5s+nCROl1zJsZRxFeYrHLqWroPOQ8BWiNb4w= +github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk= +github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= +github.com/go-kit/log v0.2.1 h1:MRVx0/zhvdseW+Gza6N9rVzU/IVzaeE1SFI4raAhmBU= +github.com/go-kit/log v0.2.1/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= +github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4= +github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0= +github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE= +github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE= +github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/g= +github.com/go-resty/resty/v2 v2.7.0 h1:me+K9p3uhSmXtrBZ4k9jcEAfJmuC8IivWHwaLZwPrFY= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/go-zookeeper/zk v1.0.3 h1:7M2kwOsc//9VeeFiPtf+uSJlVpU66x9Ba5+8XK7/TDg= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= +github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/gnostic v0.6.9 h1:ZK/5VhkoX835RikCHpSUJV9a+S3e1zLh59YnyWeBW+0= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +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/gophercloud/gophercloud v1.2.0 h1:1oXyj4g54KBg/kFtCdMM6jtxSzeIyg8wv4z1HoGPp1E= +github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= +github.com/grafana/regexp v0.0.0-20221122212121-6b5c0a4cb7fd h1:PpuIBO5P3e9hpqBD0O/HjhShYuM6XE0i/lbE6J94kww= +github.com/grafana/regexp v0.0.0-20221122212121-6b5c0a4cb7fd/go.mod h1:M5qHK+eWfAv8VR/265dIuEpL3fNfeC21tXXp9itM24A= +github.com/hashicorp/consul/api v1.20.0 h1:9IHTjNVSZ7MIwjlW3N3a7iGiykCMDpxZu8jsxFJh0yc= +github.com/hashicorp/cronexpr v1.1.1 h1:NJZDd87hGXjoZBdvyCF9mX4DCq5Wy7+A/w+A7q0wn6c= +github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= +github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= +github.com/hashicorp/go-hclog v1.4.0 h1:ctuWFGrhFha8BnnzxqeRGidlEcQkDyL5u8J8t5eA11I= +github.com/hashicorp/go-immutable-radix v1.3.1 h1:DKHmCUm2hRBK510BaiZlwvpD40f8bJFeZnpfm2KLowc= +github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= +github.com/hashicorp/go-retryablehttp v0.7.2 h1:AcYqCvkpalPnPF2pn0KamgwamS42TqUDDYFRKq/RAd0= +github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.6.0 h1:uL2shRDx7RTrOrTCUZEGP/wJUFiUI8QT6E7z5o8jga4= +github.com/hashicorp/nomad/api v0.0.0-20230308192510-48e7d70fcd4b h1:EkuSTU8c/63q4LMayj8ilgg/4I5PXDFVcnqKfs9qcwI= +github.com/hashicorp/serf v0.10.1 h1:Z1H2J60yRKvfDYAOZLd2MU0ND4AH/WDz7xYHDWQsIPY= +github.com/hetznercloud/hcloud-go v1.41.0 h1:KJGFRRc68QiVu4PrEP5BmCQVveCP2CM26UGQUKGpIUs= +github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk= +github.com/ionos-cloud/sdk-go/v6 v6.1.4 h1:BJHhFA8Q1SZC7VOXqKKr2BV2ysQ2/4hlk1e4hZte7GY= +github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= +github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= +github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= +github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA= +github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/kolo/xmlrpc v0.0.0-20220921171641-a4b6fa1dd06b h1:udzkj9S/zlT5X367kqJis0QP7YMxobob6zhzq6Yre00= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/linode/linodego v1.14.1 h1:uGxQyy0BidoEpLGdvfi4cPgEW+0YUFsEGrLEhcTfjNc= +github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +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/miekg/dns v1.1.51 h1:0+Xg7vObnhrz/4ZCZcZh7zPXlmU0aveS2HDBd0m0qSo= +github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f h1:KUppIJq7/+SVif2QVs3tOP0zanoHgBEVAwHxUSIzRqU= +github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4= +github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= +github.com/opencontainers/image-spec v1.0.2 h1:9yCKha/T5XdGtO0q9Q9a6T5NUCsTn/DrBg0D7ufOcFM= +github.com/ovh/go-ovh v1.3.0 h1:mvZaddk4E4kLcXhzb+cxBsMPYp2pHqiQpWYkInsuZPQ= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= +github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= +github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= +github.com/prometheus/client_golang v1.14.0 h1:nJdhIvne2eSX/XRAFV9PcvFFRbrjbcTUj0VP62TMhnw= +github.com/prometheus/client_golang v1.14.0/go.mod h1:8vpkKitgIVNcqrRBWh1C4TIUQgYNtG/XQE4E/Zae36Y= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.3.0 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4= +github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w= +github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= +github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= +github.com/prometheus/common v0.29.0/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= +github.com/prometheus/common v0.42.0 h1:EKsfXEYo4JpWMHH5cg+KOUWeuJSov1Id8zGR8eeI1YM= +github.com/prometheus/common v0.42.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc= +github.com/prometheus/common/sigv4 v0.1.0 h1:qoVebwtwwEhS85Czm2dSROY5fTo2PAPEVdDeppTwGX4= +github.com/prometheus/common/sigv4 v0.1.0/go.mod h1:2Jkxxk9yYvCkE5G1sQT7GuEXm57JrvHu9k5YwTjsNtI= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= +github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= +github.com/prometheus/procfs v0.9.0 h1:wzCHvIvM5SxWqYvwgVL7yJY8Lz3PKn49KQtpgMYJfhI= +github.com/prometheus/procfs v0.9.0/go.mod h1:+pB4zwohETzFnmlpe6yd2lSc+0/46IYZRB/chUwxUZY= +github.com/prometheus/prometheus v0.43.1 h1:Z/Z0S0CoPUVtUnHGokFksWMssSw2Y1Ir9NnWS1pPWU0= +github.com/prometheus/prometheus v0.43.1/go.mod h1:2BA14LgBeqlPuzObSEbh+Y+JwLH2GcqDlJKbF2sA6FM= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/scaleway/scaleway-sdk-go v1.0.0-beta.14 h1:yFl3jyaSVLNYXlnNYM5z2pagEk1dYQhfr1p20T1NyKY= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= +github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/vultr/govultr/v2 v2.17.2 h1:gej/rwr91Puc/tgh+j33p/BLR16UrIPnSr+AIwYWZQs= +github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +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.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +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.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.40.0 h1:lE9EJyw3/JhrjWH/hEy9FptnalDQgj7vpbgC2KCCCxE= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.40.0/go.mod h1:pcQ3MM3SWvrA71U4GDqv9UFDJ3HQsW7y5ZO3tDTlUdI= +go.opentelemetry.io/otel v1.14.0 h1:/79Huy8wbf5DnIPhemGB+zEPVwnN6fuQybr/SRXa6hM= +go.opentelemetry.io/otel v1.14.0/go.mod h1:o4buv+dJzx8rohcUeRmWUZhqupFvzWis188WlggnNeU= +go.opentelemetry.io/otel/metric v0.37.0 h1:pHDQuLQOZwYD+Km0eb657A25NaRzy0a+eLyKfDXedEs= +go.opentelemetry.io/otel/metric v0.37.0/go.mod h1:DmdaHfGt54iV6UKxsV9slj2bBRJcKC1B1uvDLIioc1s= +go.opentelemetry.io/otel/trace v1.14.0 h1:wp2Mmvj41tDsyAJXiWDWpfNsOiIyd38fy85pyKcFq/M= +go.opentelemetry.io/otel/trace v1.14.0/go.mod h1:8avnQLK+CG77yNLUae4ea2JDQ6iT+gozhnZjy/rw9G8= +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.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A= +go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= +golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/exp v0.0.0-20230307190834-24139beb5833 h1:SChBja7BCQewoTAU7IgvucQKMIXrEpFxNMs0spT3/5s= +golang.org/x/exp v0.0.0-20230307190834-24139beb5833/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= +golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/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.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.9.0 h1:KENHtAZL2y3NLMYZeHY9DW8HW8V+kQyJsY/V9JlKvCs= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= +golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ= +golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.6.0 h1:Lh8GPgSKBfWSwFvtuWOfeI3aAAnbXTSutYxJiOJFgIw= +golang.org/x/oauth2 v0.6.0/go.mod h1:ycmewcwgD4Rpr3eZJLSB4Kyyljb3qDh40vJ8STE5HKw= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-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.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.6.0 h1:clScbb1cHjoCkyRbWwBEUZ5H/tIFu5TAXIqaZD0Gcjw= +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= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68= +golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= +golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.7.0 h1:W4OVu8VVOaIO0yzWMNdepAulS7YfoS3Zabrm8DOXXU4= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= +google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= +google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= +google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= +google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4 h1:DdoeryqhaXp1LtT/emMP1BRJPHHKFi5akj/nbx/zNTA= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= +google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= +google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.53.0 h1:LAv2ds7cmFV/XTS3XG1NneeENYrXGmorPxsBbptIjNc= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.29.0 h1:44S3JjaKmLEE4YIkjzexaP+NzZsudE3Zin5Njn/pYX0= +google.golang.org/protobuf v1.29.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= +gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +k8s.io/api v0.26.2 h1:dM3cinp3PGB6asOySalOZxEG4CZ0IAdJsrYZXE/ovGQ= +k8s.io/apimachinery v0.26.2 h1:da1u3D5wfR5u2RpLhE/ZtZS2P7QvDgLZTi9wrNZl/tQ= +k8s.io/client-go v0.26.2 h1:s1WkVujHX3kTp4Zn4yGNFK+dlDXy1bAAkIl+cFAiuYI= +k8s.io/klog v1.0.0 h1:Pt+yjF5aB1xDSVbau4VsWe+dQNzA0qv1LlXdC2dF6Q8= +k8s.io/klog/v2 v2.90.1 h1:m4bYOKall2MmOiRaR1J+We67Do7vm9KiQVlT96lnHUw= +k8s.io/kube-openapi v0.0.0-20230303024457-afdc3dddf62d h1:VcFq5n7wCJB2FQMCIHfC+f+jNcGgNMar1uKd6rVlifU= +k8s.io/utils v0.0.0-20230308161112-d77c459e9343 h1:m7tbIjXGcGIAtpmQr7/NAi7RsWoW3E7Zcm4jI1HicTc= +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/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= +sigs.k8s.io/structured-merge-diff/v4 v4.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE= +sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= diff --git a/examples/signalfxgatewayprometheusremotewritereceiver/otel-collector-config.yaml b/examples/signalfxgatewayprometheusremotewritereceiver/otel-collector-config.yaml new file mode 100644 index 0000000000..88541a9093 --- /dev/null +++ b/examples/signalfxgatewayprometheusremotewritereceiver/otel-collector-config.yaml @@ -0,0 +1,28 @@ +receivers: + signalfxgatewayprometheusremotewrite: + endpoint: "0.0.0.0:19291" + path: "/metrics" + buffer_size: 100 +extensions: + health_check: + endpoint: 0.0.0.0:8888 +processors: + batch: +exporters: + signalfx: + # to configure, see https://github:com/open-telemetry/opentelemetry-collector-contrib/tree/main/exporter/signalfxexporter + access_token: "${SPLUNK_ACCESS_TOKEN}" + realm: "${SPLUNK_REALM}" + logging: + verbosity: Detailed +service: + #telemetry: + # metrics: + # address: ":19291" + pipelines: + metrics: + receivers: ["signalfxgatewayprometheusremotewrite"] + processors: ["batch"] + exporters: + - "logging" + - "signalfx" diff --git a/internal/components/components.go b/internal/components/components.go index 113b49a2ee..fe67903b34 100644 --- a/internal/components/components.go +++ b/internal/components/components.go @@ -98,6 +98,7 @@ import ( "github.com/signalfx/splunk-otel-collector/internal/receiver/databricksreceiver" "github.com/signalfx/splunk-otel-collector/internal/receiver/discoveryreceiver" "github.com/signalfx/splunk-otel-collector/internal/receiver/lightprometheusreceiver" + "github.com/signalfx/splunk-otel-collector/internal/receiver/signalfxgatewayprometheusremotewritereceiver" "github.com/signalfx/splunk-otel-collector/pkg/extension/smartagentextension" "github.com/signalfx/splunk-otel-collector/pkg/processor/timestampprocessor" "github.com/signalfx/splunk-otel-collector/pkg/receiver/smartagentreceiver" @@ -152,6 +153,7 @@ func Get() (otelcol.Factories, error) { redisreceiver.NewFactory(), sapmreceiver.NewFactory(), signalfxreceiver.NewFactory(), + signalfxgatewayprometheusremotewritereceiver.NewFactory(), simpleprometheusreceiver.NewFactory(), smartagentreceiver.NewFactory(), splunkhecreceiver.NewFactory(), diff --git a/internal/components/components_test.go b/internal/components/components_test.go index 64b5414a0a..e3315589f5 100644 --- a/internal/components/components_test.go +++ b/internal/components/components_test.go @@ -69,6 +69,7 @@ func TestDefaultComponents(t *testing.T) { "redis", "sapm", "signalfx", + "signalfxgatewayprometheusremotewrite", "smartagent", "splunk_hec", "sqlquery", diff --git a/internal/receiver/prometheusremotewritereceiver/README.md b/internal/receiver/prometheusremotewritereceiver/README.md deleted file mode 100644 index fe26aad139..0000000000 --- a/internal/receiver/prometheusremotewritereceiver/README.md +++ /dev/null @@ -1,33 +0,0 @@ -# Prometheus remote write receiver - -This prometheus remote write receiver aims to -1. Deprecate the Prometheus Gateway from signalfx -2. Support prometheus remote writes as an ingestion mechanism to the open-telemetry collector -3. Support flaky clients to the best possible degree - -## Limitations -As of this writing, no official specification exists for remote write endpoints, nor a 1-1 mapping between prometheus remote write metrics and OpenTelemetry metrics. - -As such, this receiver implements a best-effort mapping between such metrics. If you find your use case or access patterns do not jive well with this receiver, please [cut an issue](https://github.com/signalfx/splunk-otel-collector/issues/new) to our repo with the specific data incongruity that you're experiencing, and we will do our best to provide for you within maintainable reason. - -## Receiver Configuration -This receiver is configured via standard OpenTelemetry mechanisms. See [`config.go`](./config.go) for specific details. - -* `path` is the path in which the receiver should respond to prometheus remote write requests. - * Defaults to `/metrics` -* `buffer_size` is the degree to which metric translations may be buffered without blocking further write requests. - * Defaults to `100` -* `cache_size` is the number of most recent metadata requests which should be stored. Turn this to zero if you wish to disable caching between requests, but do ensure your metrics write requests are independently and consistently parseable without any metadata if so. - * Defaults to `10000` - -This receiver uses `opentelemetry-collector`'s [`confighttp`](https://github.com/open-telemetry/opentelemetry-collector/blob/main/config/confighttp/confighttp.go#L206) options if you would like to set up tls or similar. (See linked documentation for the most up-to-date details). -However, we make the following changes to their default options: -* `endpoint` is the default interface + port to listen on - * Defaults to `localhost:19291` - -## Remote write client configuration -If you're using the [native remote write configuration](https://prometheus.io/docs/prometheus/latest/configuration/configuration/#remote_write), it's advisable that you enable `send=true` under `metadata_config`. -If possible, wait on sending multiple requests until you're reasonably assured that metadata has propagated to the receiver. - -## Nuances in translation -We do not [remove suffixes](https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/6658646e7705b74f13031c777fcd8dd1cd64c850/receiver/prometheusreceiver/internal/metricfamily.go#L316) as is done in the otel-contrib `prometheusreceiver` diff --git a/internal/receiver/prometheusremotewritereceiver/mock_reporter.go b/internal/receiver/prometheusremotewritereceiver/mock_reporter.go deleted file mode 100644 index 2fa210ae83..0000000000 --- a/internal/receiver/prometheusremotewritereceiver/mock_reporter.go +++ /dev/null @@ -1,85 +0,0 @@ -// Copyright 2020, OpenTelemetry Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package prometheusremotewritereceiver - -import ( - "context" - "errors" - "fmt" - "sync" - "sync/atomic" - "time" -) - -// mockReporter provides a reporter that provides some useful functionalities for -// tests (e.g.: wait for certain number of messages). -type mockReporter struct { - Errors []error - wgMetricsProcessed sync.WaitGroup - TotalCalls uint32 - MessagesProcessed uint32 -} - -var _ reporter = (*mockReporter)(nil) - -func (m *mockReporter) AddExpected(newCalls int) int { - m.wgMetricsProcessed.Add(newCalls) - atomic.AddUint32(&m.MessagesProcessed, uint32(newCalls)) - atomic.AddUint32(&m.TotalCalls, uint32(newCalls)) - return int(m.TotalCalls) -} - -// newMockReporter returns a new instance of a mockReporter. -func newMockReporter(expectedOnMetricsProcessedCalls int) *mockReporter { - m := mockReporter{} - m.wgMetricsProcessed.Add(expectedOnMetricsProcessedCalls) - return &m -} - -func (m *mockReporter) StartMetricsOp(ctx context.Context) context.Context { - return ctx -} - -func (m *mockReporter) OnError(_ context.Context, _ string, err error) { - m.Errors = append(m.Errors, err) -} - -func (m *mockReporter) OnMetricsProcessed(_ context.Context, numReceivedMessages int, _ error) { - atomic.AddUint32(&m.MessagesProcessed, uint32(numReceivedMessages)) - m.wgMetricsProcessed.Done() -} - -func (m *mockReporter) OnDebugf(template string, args ...interface{}) { - fmt.Println(fmt.Sprintf(template, args...)) -} - -// WaitAllOnMetricsProcessedCalls blocks until the number of expected calls -// specified at creation of the otelReporter is completed. -func (m *mockReporter) WaitAllOnMetricsProcessedCalls(timeout time.Duration) error { - ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(timeout)) - defer cancel() - - go func() { - m.wgMetricsProcessed.Wait() - cancel() - }() - - select { - case <-time.After(timeout): - return errors.New("took too long to return") - case <-ctx.Done(): - return nil - } -} diff --git a/internal/receiver/prometheusremotewritereceiver/receiver_test.go b/internal/receiver/prometheusremotewritereceiver/receiver_test.go deleted file mode 100644 index 3653f03978..0000000000 --- a/internal/receiver/prometheusremotewritereceiver/receiver_test.go +++ /dev/null @@ -1,81 +0,0 @@ -// Copyright Splunk, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package prometheusremotewritereceiver - -import ( - "context" - "fmt" - "testing" - "time" - - "github.com/prometheus/prometheus/prompb" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "go.opentelemetry.io/collector/component/componenttest" - "go.opentelemetry.io/collector/consumer/consumertest" - "go.opentelemetry.io/collector/receiver/receivertest" -) - -func TestHappy(t *testing.T) { - timeout := time.Minute - ctx, cancel := context.WithTimeout(context.Background(), timeout) - defer cancel() - - cfg := createDefaultConfig().(*Config) - freePort, err := GetFreePort() - require.NoError(t, err) - expectedEndpoint := fmt.Sprintf("localhost:%d", freePort) - - cfg.Endpoint = expectedEndpoint - cfg.ListenPath = "/metrics" - - nopHost := componenttest.NewNopHost() - mockSettings := receivertest.NewNopCreateSettings() - mockConsumer := consumertest.NewNop() - mockreporter := newMockReporter(0) - receiver, err := New(mockSettings, cfg, mockConsumer) - remoteWriteReceiver := receiver.(*prometheusRemoteWriteReceiver) - remoteWriteReceiver.reporter = mockreporter - - assert.NoError(t, err) - require.NotNil(t, remoteWriteReceiver) - require.NoError(t, remoteWriteReceiver.Start(ctx, nopHost)) - require.NotEmpty(t, remoteWriteReceiver.server) - require.NotEmpty(t, remoteWriteReceiver.cancel) - require.NotEmpty(t, remoteWriteReceiver.config) - require.Equal(t, remoteWriteReceiver.config.Endpoint, fmt.Sprintf("localhost:%d", freePort)) - require.NotEmpty(t, remoteWriteReceiver.settings) - require.NotNil(t, remoteWriteReceiver.reporter) - require.Equal(t, expectedEndpoint, remoteWriteReceiver.server.Addr) - - // Calling start again should remain graceful - - // Ensure we can instantiate - client, err := NewMockPrwClient( - cfg.Endpoint, - "metrics", - ) - require.NoError(t, err) - require.NotNil(t, client) - mockreporter.AddExpected(1) - require.NoError(t, client.SendWriteRequest(&prompb.WriteRequest{ - Timeseries: []prompb.TimeSeries{}, - Metadata: []prompb.MetricMetadata{}, - })) - require.NoError(t, mockreporter.WaitAllOnMetricsProcessedCalls(10*time.Second)) - require.NoError(t, remoteWriteReceiver.Shutdown(ctx)) - // Shutting down should remain graceful as well - require.NoError(t, remoteWriteReceiver.Shutdown(ctx)) -} diff --git a/internal/receiver/prometheusremotewritereceiver/server_test.go b/internal/receiver/prometheusremotewritereceiver/server_test.go deleted file mode 100644 index e0057fe24e..0000000000 --- a/internal/receiver/prometheusremotewritereceiver/server_test.go +++ /dev/null @@ -1,75 +0,0 @@ -// Copyright Splunk, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package prometheusremotewritereceiver - -import ( - "context" - "fmt" - "sync" - "testing" - "time" - - "github.com/prometheus/prometheus/prompb" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "go.opentelemetry.io/collector/config/confighttp" - "go.opentelemetry.io/collector/pdata/pmetric" -) - -func TestWriteEmpty(t *testing.T) { - mc := make(chan<- pmetric.Metrics) - timeout := 5 * time.Second - reporter := newMockReporter(1) - freePort, err := GetFreePort() - require.NoError(t, err) - expectedEndpoint := fmt.Sprintf("localhost:%d", freePort) - cfg := &ServerConfig{ - Path: "/metrics", - Reporter: reporter, - Mc: mc, - HTTPServerSettings: confighttp.HTTPServerSettings{ - Endpoint: expectedEndpoint, - }, - } - require.Equal(t, expectedEndpoint, cfg.Endpoint) - - ctx, cancel := context.WithTimeout(context.Background(), timeout) - defer cancel() - receiver, err := newPrometheusRemoteWriteServer(ctx, cfg) - assert.NoError(t, err) - require.NotNil(t, receiver) - - wg := sync.WaitGroup{} - wg.Add(1) - go func() { - require.NoError(t, receiver.ListenAndServe()) - wg.Done() - }() - - client, err := NewMockPrwClient( - cfg.Endpoint, - "metrics", - ) - require.NoError(t, err) - require.NotNil(t, client) - time.Sleep(100 * time.Millisecond) - require.NoError(t, client.SendWriteRequest(&prompb.WriteRequest{ - Timeseries: []prompb.TimeSeries{}, - Metadata: []prompb.MetricMetadata{}, - })) - - require.NoError(t, receiver.Shutdown(ctx)) - require.Eventually(t, func() bool { wg.Wait(); return true }, time.Second*10, time.Second) -} diff --git a/internal/receiver/signalfxgatewayprometheusremotewritereceiver/README.md b/internal/receiver/signalfxgatewayprometheusremotewritereceiver/README.md new file mode 100644 index 0000000000..d8b64cdabc --- /dev/null +++ b/internal/receiver/signalfxgatewayprometheusremotewritereceiver/README.md @@ -0,0 +1,32 @@ +# SignalFx Gateway Prometheus remote write receiver + +## Limitations and Nuances in translation +This receiver specifically obsoletes the near-exact behavior of the [SignalFx prometheus remote write gateway](https://github.com/signalfx/gateway/blob/main/protocol/prometheus/prometheuslistener.go). +The behavior of the prometheus remote write gateway predates the formalization of the PRW v1 specification, and thus differs in the following ways. + +- We do not [remove suffixes](https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/6658646e7705b74f13031c777fcd8dd1cd64c850/receiver/prometheusreceiver/internal/metricfamily.go#L316) as is done in the otel-contrib `prometheusreceiver` +- It will transform histograms [into counters](https://github.com/signalfx/gateway/blob/main/protocol/prometheus/prometheuslistener.go#L98). +- It will transform quantiles (summaries) into gauges. +- If the representation of a float could be expressed as an integer without loss, we will set it as an integer +- If the representation of a sample is NAN, we will report an additional counter with the metric name [`"prometheus.total_NAN_samples"`](https://github.com/signalfx/gateway/blob/main/protocol/prometheus/prometheuslistener.go#LL190C24-L190C53) +- If the representation of a sample is missing a metric name, we will report an additional counter with the metric name [`"prometheus.total_bad_datapoints"`](https://github.com/signalfx/gateway/blob/main/protocol/prometheus/prometheuslistener.go#LL191C24-L191C24) +- Any errors in parsing the request will report an additional counter [`"prometheus.invalid_requests"`](https://github.com/signalfx/gateway/blob/main/protocol/prometheus/prometheuslistener.go#LL189C80-L189C91) +- Metadata from the `prompb.WriteRequest` is **ignored** + +The following behavior from sfx gateway is not supported +- `"request_time.ns"` is no longer reported. `obsreport` handles similar functionality. +- `"drain_size"` is no longer reported. `obsreport` handles similar functionality. + +## Receiver Configuration +This receiver is configured via standard OpenTelemetry mechanisms. See [`config.go`](./config.go) for specific details. + +* `path` is the path in which the receiver should respond to prometheus remote write requests. + * Defaults to `/metrics` +* `buffer_size` is the degree to which metric translations may be buffered without blocking further write requests. + * Defaults to `100` + +This receiver uses `opentelemetry-collector`'s [`confighttp`](https://github.com/open-telemetry/opentelemetry-collector/blob/main/config/confighttp/confighttp.go#L206) options if you would like to set up tls or similar. (See linked documentation for the most up-to-date details). +However, we make the following changes to their default options: +* `endpoint` is the default interface + port to listen on + * Defaults to `localhost:19291` + diff --git a/internal/receiver/prometheusremotewritereceiver/client_test.go b/internal/receiver/signalfxgatewayprometheusremotewritereceiver/client_test.go similarity index 88% rename from internal/receiver/prometheusremotewritereceiver/client_test.go rename to internal/receiver/signalfxgatewayprometheusremotewritereceiver/client_test.go index 9724e392c3..5535ec0c77 100644 --- a/internal/receiver/prometheusremotewritereceiver/client_test.go +++ b/internal/receiver/signalfxgatewayprometheusremotewritereceiver/client_test.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package prometheusremotewritereceiver +package signalfxgatewayprometheusremotewritereceiver import ( "context" @@ -35,7 +35,7 @@ type MockPrwClient struct { Timeout time.Duration } -func NewMockPrwClient(addr string, path string) (MockPrwClient, error) { +func NewMockPrwClient(addr string, path string, timeout time.Duration) (MockPrwClient, error) { URL := &config.URL{ URL: &url.URL{ Scheme: "http", @@ -43,7 +43,6 @@ func NewMockPrwClient(addr string, path string) (MockPrwClient, error) { Path: path, }, } - timeout := time.Second * 10 cfg := &remote.ClientConfig{ URL: URL, Timeout: model.Duration(timeout), @@ -67,18 +66,20 @@ func (prwc *MockPrwClient) SendWriteRequest(wr *prompb.WriteRequest) error { ctx, cancel := context.WithTimeout(context.Background(), prwc.Timeout) defer cancel() - retry := 3 + retry := 10 for retry > 0 { err = prwc.Client.Store(ctx, compressed) if nil == err { - break + return nil } if errors.Is(err, syscall.ECONNREFUSED) { retry-- time.Sleep(2 * time.Second) + } else { + return err } } - return err + return errors.New("failed to send prometheus remote write requests to server") } func GetFreePort() (int, error) { diff --git a/internal/receiver/prometheusremotewritereceiver/config.go b/internal/receiver/signalfxgatewayprometheusremotewritereceiver/config.go similarity index 86% rename from internal/receiver/prometheusremotewritereceiver/config.go rename to internal/receiver/signalfxgatewayprometheusremotewritereceiver/config.go index e18529e167..4f353acd20 100644 --- a/internal/receiver/prometheusremotewritereceiver/config.go +++ b/internal/receiver/signalfxgatewayprometheusremotewritereceiver/config.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package prometheusremotewritereceiver +package signalfxgatewayprometheusremotewritereceiver import ( "errors" @@ -25,7 +25,7 @@ import ( var _ component.Config = (*Config)(nil) const ( - typeString = "prometheusremotewrite" + typeString = "signalfxgatewayprometheusremotewrite" ) type Config struct { @@ -35,8 +35,6 @@ type Config struct { confighttp.HTTPServerSettings `mapstructure:",squash"` // BufferSize is the degree to which metric translations may be buffered without blocking further write requests. BufferSize int `mapstructure:"buffer_size"` - // CacheCapacity determines LRU capacity for how many different metrics may concurrently have persisted metadata. - CacheCapacity int `mapstructure:"cache_size"` } func (c *Config) Validate() error { diff --git a/internal/receiver/prometheusremotewritereceiver/config_test.go b/internal/receiver/signalfxgatewayprometheusremotewritereceiver/config_test.go similarity index 95% rename from internal/receiver/prometheusremotewritereceiver/config_test.go rename to internal/receiver/signalfxgatewayprometheusremotewritereceiver/config_test.go index e6819bb796..abf1bacf01 100644 --- a/internal/receiver/prometheusremotewritereceiver/config_test.go +++ b/internal/receiver/signalfxgatewayprometheusremotewritereceiver/config_test.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package prometheusremotewritereceiver +package signalfxgatewayprometheusremotewritereceiver import ( "testing" @@ -31,7 +31,6 @@ func TestValidateConfigAndDefaults(t *testing.T) { assert.NoError(t, cfg.Validate()) assert.Equal(t, "localhost:19291", cfg.Endpoint) assert.Equal(t, "/metrics", cfg.ListenPath) - assert.Equal(t, 10000, cfg.CacheCapacity) assert.Equal(t, 100, cfg.BufferSize) } diff --git a/internal/receiver/prometheusremotewritereceiver/doc.go b/internal/receiver/signalfxgatewayprometheusremotewritereceiver/doc.go similarity index 92% rename from internal/receiver/prometheusremotewritereceiver/doc.go rename to internal/receiver/signalfxgatewayprometheusremotewritereceiver/doc.go index f1ae67c26f..284d50bb5f 100644 --- a/internal/receiver/prometheusremotewritereceiver/doc.go +++ b/internal/receiver/signalfxgatewayprometheusremotewritereceiver/doc.go @@ -16,4 +16,4 @@ //go:generate mdatagen metadata.yaml -package prometheusremotewritereceiver +package signalfxgatewayprometheusremotewritereceiver diff --git a/internal/receiver/prometheusremotewritereceiver/factory.go b/internal/receiver/signalfxgatewayprometheusremotewritereceiver/factory.go similarity index 92% rename from internal/receiver/prometheusremotewritereceiver/factory.go rename to internal/receiver/signalfxgatewayprometheusremotewritereceiver/factory.go index 7319761261..132adcdd0b 100644 --- a/internal/receiver/prometheusremotewritereceiver/factory.go +++ b/internal/receiver/signalfxgatewayprometheusremotewritereceiver/factory.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package prometheusremotewritereceiver +package signalfxgatewayprometheusremotewritereceiver import ( "context" @@ -46,8 +46,7 @@ func createDefaultConfig() component.Config { HTTPServerSettings: confighttp.HTTPServerSettings{ Endpoint: "localhost:19291", // While not IANA registered, convention is 19291 as a common PRW port }, - ListenPath: "/metrics", - CacheCapacity: 10000, - BufferSize: 100, + ListenPath: "/metrics", + BufferSize: 100, } } diff --git a/internal/receiver/prometheusremotewritereceiver/factory_test.go b/internal/receiver/signalfxgatewayprometheusremotewritereceiver/factory_test.go similarity index 97% rename from internal/receiver/prometheusremotewritereceiver/factory_test.go rename to internal/receiver/signalfxgatewayprometheusremotewritereceiver/factory_test.go index 20d3260823..27db804281 100644 --- a/internal/receiver/prometheusremotewritereceiver/factory_test.go +++ b/internal/receiver/signalfxgatewayprometheusremotewritereceiver/factory_test.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package prometheusremotewritereceiver +package signalfxgatewayprometheusremotewritereceiver import ( "context" @@ -49,9 +49,7 @@ func TestFactory(t *testing.T) { assert.NoError(t, err) require.NotNil(t, receiver) require.NoError(t, receiver.Start(ctx, nopHost)) - require.NoError(t, receiver.Shutdown(ctx)) - } func TestNewFactory(t *testing.T) { diff --git a/internal/receiver/prometheusremotewritereceiver/internal/metadata/generated_status.go b/internal/receiver/signalfxgatewayprometheusremotewritereceiver/internal/metadata/generated_status.go similarity index 74% rename from internal/receiver/prometheusremotewritereceiver/internal/metadata/generated_status.go rename to internal/receiver/signalfxgatewayprometheusremotewritereceiver/internal/metadata/generated_status.go index e32c0262a0..b06d34b630 100644 --- a/internal/receiver/prometheusremotewritereceiver/internal/metadata/generated_status.go +++ b/internal/receiver/signalfxgatewayprometheusremotewritereceiver/internal/metadata/generated_status.go @@ -7,6 +7,6 @@ import ( ) const ( - Type = "prometheusremotewritereceiver" + Type = "signalfxgatewayprometheusremotewritereceiver" Stability = component.StabilityLevelDevelopment ) diff --git a/internal/receiver/signalfxgatewayprometheusremotewritereceiver/internal/prometheus_spec_utils.go b/internal/receiver/signalfxgatewayprometheusremotewritereceiver/internal/prometheus_spec_utils.go new file mode 100644 index 0000000000..d0c488f99c --- /dev/null +++ b/internal/receiver/signalfxgatewayprometheusremotewritereceiver/internal/prometheus_spec_utils.go @@ -0,0 +1,72 @@ +// Copyright Splunk, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package internal + +import ( + "errors" + "strings" + + "github.com/prometheus/prometheus/prompb" +) + +// ExtractMetricNameLabel Finds label corresponding to metric name +func ExtractMetricNameLabel(labels []prompb.Label) (string, error) { + metricName, ok := getLabelValue(labels, "__name__") + if !ok { + return "", errors.New("did not find a label with `__name__` as per prometheus spec") + } + return metricName, nil +} + +// DetermineMetricTypeByConvention This is a 'best effort' heuristic applying guidance from the latest PRW receiver and OpenMetrics specifications +// See: https://prometheus.io/docs/concepts/remote_write_spec/#prometheus-remote-write-specification +// Also see: https://raw.githubusercontent.com/OpenObservability/OpenMetrics/main/specification/OpenMetrics.md +// As this is a heuristic process, the order of operations is significant. +func DetermineMetricTypeByConvention(metricName string, labels []prompb.Label) prompb.MetricMetadata_MetricType { + + _, hasLe := getLabelValue(labels, "le") + _, hasQuantile := getLabelValue(labels, "quantile") + _, hasMetricName := getLabelValue(labels, metricName) + + switch { + case hasLe && (strings.HasSuffix(metricName, "_gsum") || strings.HasSuffix(metricName, "_gcount")): + return prompb.MetricMetadata_GAUGEHISTOGRAM + case hasLe: + return prompb.MetricMetadata_HISTOGRAM + case hasQuantile: + return prompb.MetricMetadata_SUMMARY + case hasMetricName: + return prompb.MetricMetadata_STATESET + case strings.HasSuffix(metricName, "_total") || strings.HasSuffix(metricName, "_count") || strings.HasSuffix(metricName, "_counter") || strings.HasSuffix(metricName, "_created"): + return prompb.MetricMetadata_COUNTER + case strings.HasSuffix(metricName, "_bucket"): + // While bucket may exist for a gauge histogram or Summary, we've checked such above + return prompb.MetricMetadata_HISTOGRAM + case strings.HasSuffix(metricName, "_info"): + return prompb.MetricMetadata_INFO + } + return prompb.MetricMetadata_GAUGE +} + +// getLabelValue will return the first label matching the provided name (if present), and an "ok" flag denoting whether +// the name was actually found within the provided labels +func getLabelValue(labels []prompb.Label, name string) (string, bool) { + for _, label := range labels { + if label.Name == name { + return label.Value, true + } + } + return "", false +} diff --git a/internal/receiver/signalfxgatewayprometheusremotewritereceiver/internal/prometheus_spec_utils_test.go b/internal/receiver/signalfxgatewayprometheusremotewritereceiver/internal/prometheus_spec_utils_test.go new file mode 100644 index 0000000000..8c6b4bcc03 --- /dev/null +++ b/internal/receiver/signalfxgatewayprometheusremotewritereceiver/internal/prometheus_spec_utils_test.go @@ -0,0 +1,87 @@ +// Copyright Splunk, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package internal + +import ( + "testing" + + "github.com/prometheus/prometheus/prompb" + "github.com/stretchr/testify/require" +) + +func TestGetMetricTypeByLabels(t *testing.T) { + testCases := []struct { + labels []prompb.Label + metricType prompb.MetricMetadata_MetricType + }{ + { + labels: []prompb.Label{ + {Name: "__name__", Value: "http_requests_count"}, + }, + metricType: prompb.MetricMetadata_COUNTER, + }, + { + labels: []prompb.Label{ + {Name: "__name__", Value: "http_requests_total"}, + {Name: "method", Value: "GET"}, + {Name: "status", Value: "200"}, + }, + metricType: prompb.MetricMetadata_COUNTER, + }, + { + labels: []prompb.Label{ + {Name: "__name__", Value: "go_goroutines"}, + }, + metricType: prompb.MetricMetadata_GAUGE, + }, + { + labels: []prompb.Label{ + {Name: "__name__", Value: "api_request_duration_seconds_bucket"}, + {Name: "le", Value: "0.1"}, + }, + metricType: prompb.MetricMetadata_HISTOGRAM, + }, + { + labels: []prompb.Label{ + {Name: "__name__", Value: "rpc_duration_seconds_total"}, + {Name: "le", Value: "0.1"}, + }, + metricType: prompb.MetricMetadata_HISTOGRAM, + }, + { + labels: []prompb.Label{ + {Name: "__name__", Value: "rpc_duration_seconds_total"}, + {Name: "quantile", Value: "0.5"}, + }, + metricType: prompb.MetricMetadata_SUMMARY, + }, + { + labels: []prompb.Label{ + {Name: "__name__", Value: "rpc_duration_total"}, + {Name: "quantile", Value: "0.5"}, + }, + metricType: prompb.MetricMetadata_SUMMARY, + }, + } + + for _, tc := range testCases { + metricName, err := ExtractMetricNameLabel(tc.labels) + require.NoError(t, err) + metricType := DetermineMetricTypeByConvention(metricName, tc.labels) + if metricType != tc.metricType { + t.Errorf("DetermineMetricTypeByConvention(%v) = %v; want %v", tc.labels, metricType, tc.metricType) + } + } +} diff --git a/internal/receiver/prometheusremotewritereceiver/internal/testdata/otel-collector-config.yaml b/internal/receiver/signalfxgatewayprometheusremotewritereceiver/internal/testdata/otel-collector-config.yaml similarity index 100% rename from internal/receiver/prometheusremotewritereceiver/internal/testdata/otel-collector-config.yaml rename to internal/receiver/signalfxgatewayprometheusremotewritereceiver/internal/testdata/otel-collector-config.yaml diff --git a/internal/receiver/prometheusremotewritereceiver/metadata.yaml b/internal/receiver/signalfxgatewayprometheusremotewritereceiver/metadata.yaml similarity index 66% rename from internal/receiver/prometheusremotewritereceiver/metadata.yaml rename to internal/receiver/signalfxgatewayprometheusremotewritereceiver/metadata.yaml index 4a5956a79a..403d8651be 100644 --- a/internal/receiver/prometheusremotewritereceiver/metadata.yaml +++ b/internal/receiver/signalfxgatewayprometheusremotewritereceiver/metadata.yaml @@ -1,4 +1,4 @@ -type: "prometheusremotewritereceiver" +type: "signalfxgatewayprometheusremotewritereceiver" status: class: "receiver" stability: "development" diff --git a/internal/receiver/signalfxgatewayprometheusremotewritereceiver/mock_reporter_test.go b/internal/receiver/signalfxgatewayprometheusremotewritereceiver/mock_reporter_test.go new file mode 100644 index 0000000000..277b57ae14 --- /dev/null +++ b/internal/receiver/signalfxgatewayprometheusremotewritereceiver/mock_reporter_test.go @@ -0,0 +1,124 @@ +// Copyright 2020, OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package signalfxgatewayprometheusremotewritereceiver + +import ( + "context" + "fmt" + "sync" + "sync/atomic" + "time" +) + +// mockReporter provides a reporter that provides some useful functionalities for +// tests (e.g.: wait for certain number of messages). +type mockReporter struct { + OpsSuccess *sync.WaitGroup + OpsStarted *sync.WaitGroup + OpsFailed *sync.WaitGroup + Errors chan error + ErrorLocation chan string + TotalSuccessMetrics atomic.Int32 + TotalErrorMetrics atomic.Int32 +} + +var _ reporter = (*mockReporter)(nil) + +func (m *mockReporter) AddExpectedError(newCalls int) { + m.OpsFailed.Add(newCalls) +} + +func (m *mockReporter) AddExpectedSuccess(newCalls int) { + m.OpsSuccess.Add(newCalls) +} + +func (m *mockReporter) AddExpectedStart(newCalls int) { + m.OpsStarted.Add(newCalls) +} + +// newMockReporter returns a new instance of a mockReporter. +func newMockReporter() *mockReporter { + m := mockReporter{ + OpsSuccess: &sync.WaitGroup{}, + OpsFailed: &sync.WaitGroup{}, + OpsStarted: &sync.WaitGroup{}, + TotalErrorMetrics: atomic.Int32{}, + TotalSuccessMetrics: atomic.Int32{}, + } + return &m +} + +func (m *mockReporter) StartMetricsOp(ctx context.Context) context.Context { + m.OpsStarted.Done() + return ctx +} + +func (m *mockReporter) OnError(_ context.Context, errorLocation string, err error) { + m.TotalErrorMetrics.Add(1) + m.Errors <- err + m.ErrorLocation <- errorLocation + m.OpsFailed.Done() +} + +func (m *mockReporter) OnMetricsProcessed(_ context.Context, numReceivedMessages int, _ error) { + m.TotalSuccessMetrics.Add(int32(numReceivedMessages)) + m.OpsSuccess.Done() +} + +func (m *mockReporter) OnDebugf(template string, args ...interface{}) { + fmt.Println(fmt.Sprintf(template, args...)) +} + +// WaitAllOnMetricsProcessedCalls blocks until the number of expected calls +// specified at creation of the otelReporter is completed. +func (m *mockReporter) WaitAllOnMetricsProcessedCalls(timeout time.Duration) error { + ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(timeout)) + defer cancel() + allDone := &sync.WaitGroup{} + allDone.Add(3) + + done := make(chan string) + + go func() { + m.OpsFailed.Wait() + allDone.Done() + done <- "done with failed" + }() + go func() { + m.OpsSuccess.Wait() + allDone.Done() + done <- "done with success" + }() + go func() { + m.OpsStarted.Wait() + allDone.Done() + done <- "done with started" + }() + go func() { + allDone.Wait() + cancel() + }() + var completed []string + for { + select { + case completedOps := <-done: + completed = append(completed, completedOps) + case <-time.After(timeout): + return fmt.Errorf("took too long to return. Ones that did: %s", completed) + case <-ctx.Done(): + return nil + } + } +} diff --git a/internal/receiver/signalfxgatewayprometheusremotewritereceiver/prometheus_remote_write_requests_test.go b/internal/receiver/signalfxgatewayprometheusremotewritereceiver/prometheus_remote_write_requests_test.go new file mode 100644 index 0000000000..6d53a13a65 --- /dev/null +++ b/internal/receiver/signalfxgatewayprometheusremotewritereceiver/prometheus_remote_write_requests_test.go @@ -0,0 +1,399 @@ +// Copyright Splunk, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package signalfxgatewayprometheusremotewritereceiver + +import ( + "testing" + "time" + + "github.com/prometheus/prometheus/prompb" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.opentelemetry.io/collector/pdata/pcommon" + "go.opentelemetry.io/collector/pdata/pmetric" +) + +var ( + Jan20 = time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC) +) + +func SampleCounterTs() []prompb.TimeSeries { + return []prompb.TimeSeries{ + { + Labels: []prompb.Label{ + {Name: "__name__", Value: "http_requests_total"}, + {Name: "method", Value: "GET"}, + {Name: "status", Value: "200"}, + }, + Samples: []prompb.Sample{ + {Value: 1024, Timestamp: Jan20.UnixMilli()}, + }, + }, + } +} +func SampleCounterWq() *prompb.WriteRequest { + return &prompb.WriteRequest{Timeseries: SampleCounterTs()} +} + +func SampleGaugeTs() []prompb.TimeSeries { + return []prompb.TimeSeries{ + { + Labels: []prompb.Label{ + {Name: "__name__", Value: "i_am_a_gauge"}, + }, + Samples: []prompb.Sample{ + {Value: 42, Timestamp: Jan20.UnixMilli()}, + }, + }, + } +} + +func SampleGaugeWq() *prompb.WriteRequest { return &prompb.WriteRequest{Timeseries: SampleGaugeTs()} } + +func SampleHistogramTs() []prompb.TimeSeries { + return []prompb.TimeSeries{ + { + Labels: []prompb.Label{ + {Name: "__name__", Value: "api_request_duration_seconds_bucket"}, + {Name: "le", Value: "0.1"}, + }, + Samples: []prompb.Sample{ + {Value: 500, Timestamp: Jan20.UnixMilli()}, + }, + }, + { + Labels: []prompb.Label{ + {Name: "__name__", Value: "api_request_duration_seconds_bucket"}, + {Name: "le", Value: "0.2"}, + }, + Samples: []prompb.Sample{ + {Value: 1500, Timestamp: Jan20.UnixMilli()}, + }, + }, + { + Labels: []prompb.Label{ + {Name: "__name__", Value: "api_request_duration_seconds_count"}, + }, + Samples: []prompb.Sample{ + {Value: 2500, Timestamp: Jan20.UnixMilli()}, + }, + }, + { + Labels: []prompb.Label{ + {Name: "__name__", Value: "api_request_duration_seconds_sum"}, + }, + Samples: []prompb.Sample{ + {Value: 350, Timestamp: Jan20.UnixMilli()}, + }, + }, + } +} + +func SampleHistogramWq() *prompb.WriteRequest { + return &prompb.WriteRequest{ + Timeseries: SampleHistogramTs(), + } +} + +func SampleSummaryTs() []prompb.TimeSeries { + return []prompb.TimeSeries{ + { + Labels: []prompb.Label{ + {Name: "__name__", Value: "request_duration_seconds"}, + {Name: "quantile", Value: "0.5"}, + }, + Samples: []prompb.Sample{ + {Value: 0.25, Timestamp: Jan20.UnixMilli()}, + }, + }, + { + Labels: []prompb.Label{ + {Name: "__name__", Value: "request_duration_seconds"}, + {Name: "quantile", Value: "0.9"}, + }, + Samples: []prompb.Sample{ + {Value: 0.35, Timestamp: Jan20.UnixMilli()}, + }, + }, + { + Labels: []prompb.Label{ + {Name: "__name__", Value: "request_duration_seconds_sum"}, + }, + Samples: []prompb.Sample{ + {Value: 123.5, Timestamp: Jan20.UnixMilli()}, + }, + }, + { + Labels: []prompb.Label{ + {Name: "__name__", Value: "request_duration_seconds_count"}, + }, + Samples: []prompb.Sample{ + {Value: 1500, Timestamp: Jan20.UnixMilli()}, + }, + }, + } +} + +func SampleSummaryWq() *prompb.WriteRequest { + return &prompb.WriteRequest{ + Timeseries: SampleSummaryTs(), + } +} + +func ExpectedCounter() pmetric.Metrics { + result := pmetric.NewMetrics() + resourceMetrics := result.ResourceMetrics().AppendEmpty() + scopeMetrics := resourceMetrics.ScopeMetrics().AppendEmpty() + scopeMetrics.Scope().SetName("signalfxgatewayprometheusremotewrite") + scopeMetrics.Scope().SetVersion("0.1") + metric := scopeMetrics.Metrics().AppendEmpty() + metric.SetName("http_requests_total") + counter := metric.SetEmptySum() + counter.SetIsMonotonic(true) + counter.SetAggregationTemporality(pmetric.AggregationTemporalityCumulative) + dp := counter.DataPoints().AppendEmpty() + dp.SetTimestamp(pcommon.NewTimestampFromTime(Jan20)) + dp.SetStartTimestamp(pcommon.NewTimestampFromTime(Jan20)) + dp.SetIntValue(1024) + dp.Attributes().PutStr("method", "GET") + dp.Attributes().PutStr("status", "200") + + return result +} + +func ExpectedGauge() pmetric.Metrics { + result := pmetric.NewMetrics() + resourceMetrics := result.ResourceMetrics().AppendEmpty() + scopeMetrics := resourceMetrics.ScopeMetrics().AppendEmpty() + scopeMetrics.Scope().SetName("signalfxgatewayprometheusremotewrite") + scopeMetrics.Scope().SetVersion("0.1") + metric := scopeMetrics.Metrics().AppendEmpty() + metric.SetName("i_am_a_gauge") + counter := metric.SetEmptyGauge() + dp := counter.DataPoints().AppendEmpty() + dp.SetTimestamp(pcommon.NewTimestampFromTime(Jan20)) + dp.SetStartTimestamp(pcommon.NewTimestampFromTime(Jan20)) + dp.SetIntValue(42) + + return result +} + +func ExpectedSfxCompatibleHistogram() pmetric.Metrics { + result := pmetric.NewMetrics() + resourceMetrics := result.ResourceMetrics().AppendEmpty() + scopeMetrics := resourceMetrics.ScopeMetrics().AppendEmpty() + scopeMetrics.Scope().SetName("signalfxgatewayprometheusremotewrite") + scopeMetrics.Scope().SetVersion("0.1") + + // set bucket sizes + pairs := []struct { + bucket string + value int64 + timestamp int64 + }{ + { + bucket: "0.1", + value: 500, + timestamp: Jan20.UnixNano(), + }, + { + bucket: "0.2", + value: 1500, + timestamp: Jan20.UnixNano(), + }, + } + for _, values := range pairs { + metric := scopeMetrics.Metrics().AppendEmpty() + metric.SetName("api_request_duration_seconds_bucket") + counter := metric.SetEmptySum() + counter.SetIsMonotonic(true) + counter.SetAggregationTemporality(pmetric.AggregationTemporalityCumulative) + dp := counter.DataPoints().AppendEmpty() + dp.Attributes().PutStr("le", values.bucket) + dp.SetTimestamp(pcommon.Timestamp(values.timestamp)) + dp.SetStartTimestamp(pcommon.Timestamp(values.timestamp)) + dp.SetIntValue(values.value) + } + + metric := scopeMetrics.Metrics().AppendEmpty() + metric.SetName("api_request_duration_seconds_count") + counter := metric.SetEmptySum() + counter.SetIsMonotonic(true) + counter.SetAggregationTemporality(pmetric.AggregationTemporalityCumulative) + dp := counter.DataPoints().AppendEmpty() + dp.SetTimestamp(pcommon.Timestamp(Jan20.UnixNano())) + dp.SetStartTimestamp(pcommon.Timestamp(Jan20.UnixNano())) + dp.SetIntValue(2500) + + metric = scopeMetrics.Metrics().AppendEmpty() + metric.SetName("api_request_duration_seconds_sum") + gauge := metric.SetEmptyGauge() + dp = gauge.DataPoints().AppendEmpty() + + dp.SetTimestamp(pcommon.Timestamp(Jan20.UnixNano())) + dp.SetStartTimestamp(pcommon.Timestamp(Jan20.UnixNano())) + dp.SetIntValue(350) + + return result +} + +func ExpectedSfxCompatibleQuantile() pmetric.Metrics { + result := pmetric.NewMetrics() + resourceMetrics := result.ResourceMetrics().AppendEmpty() + scopeMetrics := resourceMetrics.ScopeMetrics().AppendEmpty() + scopeMetrics.Scope().SetName("signalfxgatewayprometheusremotewrite") + scopeMetrics.Scope().SetVersion("0.1") + + // set bucket sizes + pairs := []struct { + bucket string + value float64 + timestamp int64 + }{ + { + bucket: "0.5", + value: .25, + timestamp: Jan20.UnixNano(), + }, + { + bucket: "0.9", + value: .35, + timestamp: Jan20.UnixNano(), + }, + } + for _, values := range pairs { + metric := scopeMetrics.Metrics().AppendEmpty() + metric.SetName("request_duration_seconds") + gauge := metric.SetEmptyGauge() + dp := gauge.DataPoints().AppendEmpty() + dp.Attributes().PutStr("quantile", values.bucket) + dp.SetTimestamp(pcommon.Timestamp(values.timestamp)) + dp.SetStartTimestamp(pcommon.Timestamp(values.timestamp)) + dp.SetDoubleValue(values.value) + } + + metric := scopeMetrics.Metrics().AppendEmpty() + metric.SetName("request_duration_seconds_count") + sum := metric.SetEmptySum() + sum.SetIsMonotonic(true) + sum.SetAggregationTemporality(pmetric.AggregationTemporalityCumulative) + dp := sum.DataPoints().AppendEmpty() + dp.SetTimestamp(pcommon.Timestamp(Jan20.UnixNano())) + dp.SetStartTimestamp(pcommon.Timestamp(Jan20.UnixNano())) + dp.SetIntValue(1500) + + metric = scopeMetrics.Metrics().AppendEmpty() + metric.SetName("request_duration_seconds_sum") + gauge := metric.SetEmptyGauge() + dp = gauge.DataPoints().AppendEmpty() + + dp.SetTimestamp(pcommon.Timestamp(Jan20.UnixNano())) + dp.SetStartTimestamp(pcommon.Timestamp(Jan20.UnixNano())) + dp.SetDoubleValue(123.5) + + return result +} + +func GetWriteRequestsOfAllTypesWithoutMetadata() []*prompb.WriteRequest { + var sampleWriteRequestsNoMetadata = []*prompb.WriteRequest{ + // Counter + SampleCounterWq(), + // Gauge + SampleGaugeWq(), + // Histogram + SampleHistogramWq(), + // Summary + SampleSummaryWq(), + } + return sampleWriteRequestsNoMetadata +} + +func AddSfxCompatibilityMetrics(metrics pmetric.Metrics, expectedNans int64, expectedMissing int64, expectedInvalid int64) pmetric.Metrics { + if metrics == pmetric.NewMetrics() { + metrics.ResourceMetrics().AppendEmpty().ScopeMetrics().AppendEmpty() + } + scope := metrics.ResourceMetrics().At(0).ScopeMetrics().At(0) + addSfxCompatibilityMissingNameMetrics(scope, expectedMissing) + addSfxCompatibilityNanMetrics(scope, expectedNans) + addSfxCompatibilityInvalidRequestMetrics(scope, expectedInvalid) + return metrics +} + +// addSfxCompatibilityInvalidRequestMetrics adds the meta-metrics to a given scope, but won't set values +// See https://github.com/signalfx/gateway/blob/main/protocol/prometheus/prometheuslistener.go#L188 +func addSfxCompatibilityInvalidRequestMetrics(scopeMetrics pmetric.ScopeMetrics, value int64) pmetric.Metric { + metric := scopeMetrics.Metrics().AppendEmpty() + metric.SetName("prometheus.invalid_requests") + counter := metric.SetEmptySum() + counter.SetIsMonotonic(true) + counter.SetAggregationTemporality(pmetric.AggregationTemporalityCumulative) + dp := counter.DataPoints().AppendEmpty() + dp.SetIntValue(value) + dp.SetStartTimestamp(pcommon.NewTimestampFromTime(Jan20)) + dp.SetTimestamp(pcommon.NewTimestampFromTime(Jan20)) + return metric +} + +// addSfxCompatibilityMissingNameMetrics adds the meta-metrics to a given scope, but won't set values +// See https://github.com/signalfx/gateway/blob/main/protocol/prometheus/prometheuslistener.go#L188 +func addSfxCompatibilityMissingNameMetrics(scopeMetrics pmetric.ScopeMetrics, value int64) pmetric.Metric { + metric := scopeMetrics.Metrics().AppendEmpty() + metric.SetName("prometheus.total_bad_datapoints") + counter := metric.SetEmptySum() + counter.SetIsMonotonic(true) + counter.SetAggregationTemporality(pmetric.AggregationTemporalityCumulative) + dp := counter.DataPoints().AppendEmpty() + dp.SetIntValue(value) + dp.SetStartTimestamp(pcommon.NewTimestampFromTime(Jan20)) + dp.SetTimestamp(pcommon.NewTimestampFromTime(Jan20)) + return metric +} + +// addSfxCompatibilityNanMetrics adds the meta-metrics to a given scope, but won't set values +// See https://github.com/signalfx/gateway/blob/main/protocol/prometheus/prometheuslistener.go#L188 +func addSfxCompatibilityNanMetrics(scopeMetrics pmetric.ScopeMetrics, value int64) pmetric.Metric { + metric := scopeMetrics.Metrics().AppendEmpty() + metric.SetName("prometheus.total_NAN_samples") + counter := metric.SetEmptySum() + counter.SetIsMonotonic(true) + counter.SetAggregationTemporality(pmetric.AggregationTemporalityCumulative) + dp := counter.DataPoints().AppendEmpty() + dp.SetIntValue(value) + dp.SetStartTimestamp(pcommon.NewTimestampFromTime(Jan20)) + dp.SetTimestamp(pcommon.NewTimestampFromTime(Jan20)) + return metric +} + +func FlattenWriteRequests(request []*prompb.WriteRequest) *prompb.WriteRequest { + var ts []prompb.TimeSeries + for _, req := range request { + ts = append(ts, req.Timeseries...) + } + return &prompb.WriteRequest{ + Timeseries: ts, + } +} + +func TestBasicNoMd(t *testing.T) { + wqs := GetWriteRequestsOfAllTypesWithoutMetadata() + require.NotNil(t, wqs) + for _, wq := range wqs { + for _, ts := range wq.Timeseries { + require.NotNil(t, ts) + assert.NotEmpty(t, ts.Labels) + } + require.Empty(t, wq.Metadata) + } +} diff --git a/internal/receiver/signalfxgatewayprometheusremotewritereceiver/prometheus_to_otel.go b/internal/receiver/signalfxgatewayprometheusremotewritereceiver/prometheus_to_otel.go new file mode 100644 index 0000000000..fe516aa2c9 --- /dev/null +++ b/internal/receiver/signalfxgatewayprometheusremotewritereceiver/prometheus_to_otel.go @@ -0,0 +1,266 @@ +// Copyright Splunk, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package signalfxgatewayprometheusremotewritereceiver + +import ( + "fmt" + "math" + "sync/atomic" + "time" + + "github.com/prometheus/prometheus/prompb" + "go.opentelemetry.io/collector/pdata/pcommon" + "go.opentelemetry.io/collector/pdata/pmetric" + "go.uber.org/multierr" + + "github.com/signalfx/splunk-otel-collector/internal/receiver/signalfxgatewayprometheusremotewritereceiver/internal" +) + +type metricData struct { + MetricName string + Labels []prompb.Label + Samples []prompb.Sample + Exemplars []prompb.Exemplar + Histograms []prompb.Histogram + MetricMetadata prompb.MetricMetadata +} + +type prometheusRemoteOtelParser struct { + totalNans atomic.Int64 + totalInvalidRequests atomic.Int64 + totalBadMetrics atomic.Int64 +} + +func (prwParser *prometheusRemoteOtelParser) fromPrometheusWriteRequestMetrics(request *prompb.WriteRequest) (pmetric.Metrics, error) { + var otelMetrics pmetric.Metrics + metricFamiliesAndData, err := prwParser.partitionWriteRequest(request) + if nil == err { + otelMetrics = prwParser.transformPrometheusRemoteWriteToOtel(metricFamiliesAndData) + } + if otelMetrics == pmetric.NewMetrics() { + otelMetrics.ResourceMetrics().AppendEmpty().ScopeMetrics().AppendEmpty() + } + startTime, endTime := getWriteRequestTimestampBounds(request) + scope := otelMetrics.ResourceMetrics().At(0).ScopeMetrics().At(0) + prwParser.addBadRequests(scope, startTime, endTime) + prwParser.addNanDataPoints(scope, startTime, endTime) + prwParser.addMetricsWithMissingName(scope, startTime, endTime) + return otelMetrics, err +} + +func (prwParser *prometheusRemoteOtelParser) transformPrometheusRemoteWriteToOtel(parsedPrwMetrics map[prompb.MetricMetadata_MetricType][]metricData) pmetric.Metrics { + metric := pmetric.NewMetrics() + rm := metric.ResourceMetrics().AppendEmpty() + ilm := rm.ScopeMetrics().AppendEmpty() + ilm.Scope().SetName(typeString) + ilm.Scope().SetVersion("0.1") + for metricType, metrics := range parsedPrwMetrics { + prwParser.addMetrics(ilm, metricType, metrics) + } + return metric +} + +func (prwParser *prometheusRemoteOtelParser) partitionWriteRequest(writeReq *prompb.WriteRequest) (map[prompb.MetricMetadata_MetricType][]metricData, error) { + partitions := make(map[prompb.MetricMetadata_MetricType][]metricData) + var translationErrors error + for index, ts := range writeReq.Timeseries { + metricName, err := internal.ExtractMetricNameLabel(ts.Labels) + if err != nil { + translationErrors = multierr.Append(translationErrors, err) + } + + metricType := internal.DetermineMetricTypeByConvention(metricName, ts.Labels) + metricMetadata := prompb.MetricMetadata{ + Type: metricType, + } + md := metricData{ + Labels: ts.Labels, + Samples: writeReq.Timeseries[index].Samples, + Exemplars: writeReq.Timeseries[index].Exemplars, + Histograms: writeReq.Timeseries[index].Histograms, + MetricName: metricName, + MetricMetadata: metricMetadata, + } + if len(md.Samples) < 1 { + translationErrors = multierr.Append(translationErrors, fmt.Errorf("no samples found for %s", metricName)) + } + partitions[metricType] = append(partitions[metricType], md) + } + + return partitions, translationErrors +} + +// This actually converts from a prometheus prompdb.MetaDataType to the closest equivalent otel type +// See https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/13bcae344506fe2169b59d213361d04094c651f6/receiver/prometheusreceiver/internal/util.go#L106 +func (prwParser *prometheusRemoteOtelParser) addMetrics(ilm pmetric.ScopeMetrics, metricType prompb.MetricMetadata_MetricType, metrics []metricData) { + + switch metricType { + case prompb.MetricMetadata_COUNTER, prompb.MetricMetadata_HISTOGRAM, prompb.MetricMetadata_GAUGEHISTOGRAM: + prwParser.addCounterMetrics(ilm, metrics) + default: + prwParser.addGaugeMetrics(ilm, metrics) + } +} + +func (prwParser *prometheusRemoteOtelParser) scaffoldNewMetric(ilm pmetric.ScopeMetrics, name string) pmetric.Metric { + nm := ilm.Metrics().AppendEmpty() + nm.SetName(name) + return nm +} + +// addBadRequests is used to report write requests with invalid data +func (prwParser *prometheusRemoteOtelParser) addBadRequests(ilm pmetric.ScopeMetrics, start time.Time, end time.Time) { + errMetric := ilm.Metrics().AppendEmpty() + errMetric.SetName("prometheus.invalid_requests") + errorSum := errMetric.SetEmptySum() + errorSum.SetAggregationTemporality(pmetric.AggregationTemporalityCumulative) + errorSum.SetIsMonotonic(true) + dp := errorSum.DataPoints().AppendEmpty() + dp.SetIntValue(prwParser.totalInvalidRequests.Load()) + dp.SetStartTimestamp(pcommon.NewTimestampFromTime(start)) + dp.SetTimestamp(pcommon.NewTimestampFromTime(end)) +} + +// addMetricsWithMissingName is used to report metrics in the remote write request without names +func (prwParser *prometheusRemoteOtelParser) addMetricsWithMissingName(ilm pmetric.ScopeMetrics, start time.Time, end time.Time) { + errMetric := ilm.Metrics().AppendEmpty() + errMetric.SetName("prometheus.total_bad_datapoints") + errorSum := errMetric.SetEmptySum() + errorSum.SetAggregationTemporality(pmetric.AggregationTemporalityCumulative) + errorSum.SetIsMonotonic(true) + dp := errorSum.DataPoints().AppendEmpty() + dp.SetIntValue(prwParser.totalBadMetrics.Load()) + + dp.SetStartTimestamp(pcommon.NewTimestampFromTime(start)) + dp.SetTimestamp(pcommon.NewTimestampFromTime(end)) +} + +// addNanDataPoints is an sfx compatibility error metric +func (prwParser *prometheusRemoteOtelParser) addNanDataPoints(ilm pmetric.ScopeMetrics, start time.Time, end time.Time) { + errMetric := ilm.Metrics().AppendEmpty() + errMetric.SetName("prometheus.total_NAN_samples") + errorSum := errMetric.SetEmptySum() + errorSum.SetAggregationTemporality(pmetric.AggregationTemporalityCumulative) + errorSum.SetIsMonotonic(true) + dp := errorSum.DataPoints().AppendEmpty() + dp.SetStartTimestamp(pcommon.NewTimestampFromTime(start)) + dp.SetTimestamp(pcommon.NewTimestampFromTime(end)) + dp.SetIntValue(prwParser.totalNans.Load()) +} + +// addGaugeMetrics handles any scalar metric family which can go up or down +func (prwParser *prometheusRemoteOtelParser) addGaugeMetrics(ilm pmetric.ScopeMetrics, metrics []metricData) { + for _, metricsData := range metrics { + if metricsData.MetricName == "" { + prwParser.totalBadMetrics.Add(1) + continue + } + nm := prwParser.scaffoldNewMetric(ilm, metricsData.MetricName) + nm.SetName(metricsData.MetricName) + gauge := nm.SetEmptyGauge() + for _, sample := range metricsData.Samples { + if math.IsNaN(sample.Value) { + prwParser.totalNans.Add(1) + continue + } + dp := gauge.DataPoints().AppendEmpty() + dp.SetTimestamp(prometheusToOtelTimestamp(sample.GetTimestamp())) + dp.SetStartTimestamp(prometheusToOtelTimestamp(sample.GetTimestamp())) + prwParser.setFloatOrInt(dp, sample) + prwParser.setAttributes(dp, metricsData.Labels) + } + } +} + +// addCounterMetrics handles any scalar metric family which can only goes up, and are cumulative +func (prwParser *prometheusRemoteOtelParser) addCounterMetrics(ilm pmetric.ScopeMetrics, metrics []metricData) { + for _, metricsData := range metrics { + if metricsData.MetricName == "" { + prwParser.totalBadMetrics.Add(1) + continue + } + nm := prwParser.scaffoldNewMetric(ilm, metricsData.MetricName) + sumMetric := nm.SetEmptySum() + sumMetric.SetAggregationTemporality(pmetric.AggregationTemporalityCumulative) + sumMetric.SetIsMonotonic(true) + for _, sample := range metricsData.Samples { + if math.IsNaN(sample.Value) { + prwParser.totalNans.Add(1) + continue + } + dp := nm.Sum().DataPoints().AppendEmpty() + dp.SetTimestamp(prometheusToOtelTimestamp(sample.GetTimestamp())) + dp.SetStartTimestamp(prometheusToOtelTimestamp(sample.GetTimestamp())) + prwParser.setFloatOrInt(dp, sample) + prwParser.setAttributes(dp, metricsData.Labels) + } + } +} + +func getSampleTimestampBounds(samples []prompb.Sample) (int64, int64) { + if len(samples) < 1 { + return -1, -1 + } + minTimestamp := int64(math.MaxInt64) + maxTimestamp := int64(math.MinInt64) + for _, sample := range samples { + if minTimestamp > sample.Timestamp { + minTimestamp = sample.Timestamp + } + if maxTimestamp < sample.GetTimestamp() { + maxTimestamp = sample.GetTimestamp() + } + } + return minTimestamp, maxTimestamp +} + +func getWriteRequestTimestampBounds(request *prompb.WriteRequest) (time.Time, time.Time) { + minTimestamp := int64(math.MaxInt64) + maxTimestamp := int64(math.MinInt64) + for _, ts := range request.Timeseries { + sampleMin, sampleMax := getSampleTimestampBounds(ts.Samples) + if sampleMin < minTimestamp { + minTimestamp = sampleMin + } + if sampleMax > maxTimestamp { + maxTimestamp = sampleMax + } + } + return time.UnixMilli(minTimestamp), time.UnixMilli(maxTimestamp) +} + +func (prwParser *prometheusRemoteOtelParser) setFloatOrInt(dp pmetric.NumberDataPoint, sample prompb.Sample) error { + if math.IsNaN(sample.Value) { + return fmt.Errorf("NAN value found") + } + if float64(int64(sample.Value)) == sample.Value { + dp.SetIntValue(int64(sample.Value)) + } else { + dp.SetDoubleValue(sample.Value) + } + return nil +} + +func prometheusToOtelTimestamp(ts int64) pcommon.Timestamp { + return pcommon.Timestamp(ts * int64(time.Millisecond)) +} + +func (prwParser *prometheusRemoteOtelParser) setAttributes(dp pmetric.NumberDataPoint, labels []prompb.Label) { + for _, attr := range labels { + if attr.Name != "__name__" { + dp.Attributes().PutStr(attr.Name, attr.Value) + } + } +} diff --git a/internal/receiver/signalfxgatewayprometheusremotewritereceiver/prometheus_to_otel_test.go b/internal/receiver/signalfxgatewayprometheusremotewritereceiver/prometheus_to_otel_test.go new file mode 100644 index 0000000000..daeaab7e13 --- /dev/null +++ b/internal/receiver/signalfxgatewayprometheusremotewritereceiver/prometheus_to_otel_test.go @@ -0,0 +1,117 @@ +// Copyright Splunk, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package signalfxgatewayprometheusremotewritereceiver + +import ( + "testing" + + "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/pdatatest/pmetrictest" + "github.com/prometheus/prometheus/prompb" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.opentelemetry.io/collector/pdata/pmetric" + "golang.org/x/exp/maps" +) + +func TestParseAndPartitionPrometheusRemoteWriteRequest(t *testing.T) { + reporter := newMockReporter() + require.NotNil(t, reporter) + parser := &prometheusRemoteOtelParser{} + + sampleWriteRequests := FlattenWriteRequests(GetWriteRequestsOfAllTypesWithoutMetadata()) + noMdPartitions, err := parser.partitionWriteRequest(sampleWriteRequests) + require.NoError(t, err) + require.Empty(t, sampleWriteRequests.Metadata, "NoMetadata (heuristical) portion of test contains metadata") + + noMdMap := make(map[prompb.MetricMetadata_MetricType]map[string][]metricData) + for key, partition := range noMdPartitions { + require.Nil(t, noMdMap[key]) + noMdMap[key] = make(map[string][]metricData) + + for _, md := range partition { + noMdMap[key][md.MetricName] = append(noMdMap[key][md.MetricName], md) + + assert.NotEmpty(t, md.MetricMetadata.Type) + } + } + + results := parser.transformPrometheusRemoteWriteToOtel(noMdPartitions) + + typesSeen := make(map[pmetric.MetricType][]string) + for resourceMetricsIndex := 0; resourceMetricsIndex < results.ResourceMetrics().Len(); resourceMetricsIndex++ { + rm := results.ResourceMetrics().At(resourceMetricsIndex) + for scopeMetricsIndex := 0; scopeMetricsIndex < rm.ScopeMetrics().Len(); scopeMetricsIndex++ { + sm := rm.ScopeMetrics().At(scopeMetricsIndex) + for metricsIndex := 0; metricsIndex < sm.Metrics().Len(); metricsIndex++ { + metric := sm.Metrics().At(metricsIndex) + typesSeen[metric.Type()] = append(typesSeen[metric.Type()], metric.Name()) + } + } + } + expectedTypesSeen := map[pmetric.MetricType][]string{ + pmetric.MetricTypeSum: {"http_requests_total", "api_request_duration_seconds_bucket", "api_request_duration_seconds_bucket", "api_request_duration_seconds_count", "request_duration_seconds_count"}, + pmetric.MetricTypeGauge: {"i_am_a_gauge", "request_duration_seconds", "request_duration_seconds", "request_duration_seconds_sum", "api_request_duration_seconds_sum"}, + } + require.ElementsMatch(t, maps.Keys(expectedTypesSeen), maps.Keys(typesSeen)) + for key, values := range typesSeen { + require.ElementsMatch(t, expectedTypesSeen[key], values) + } + +} + +func TestAddMetricsHappyPath(t *testing.T) { + + testCases := []struct { + Sample *prompb.WriteRequest + Expected pmetric.Metrics + Name string + }{ + { + Name: "test counters", + Sample: SampleCounterWq(), + Expected: AddSfxCompatibilityMetrics(ExpectedCounter(), 0, 0, 0), + }, + { + Name: "test gauges", + Sample: SampleGaugeWq(), + Expected: AddSfxCompatibilityMetrics(ExpectedGauge(), 0, 0, 0), + }, + { + Name: "test histograms", + Sample: SampleHistogramWq(), + Expected: AddSfxCompatibilityMetrics(ExpectedSfxCompatibleHistogram(), 0, 0, 0), + }, + { + Name: "test quantiles", + Sample: SampleSummaryWq(), + Expected: AddSfxCompatibilityMetrics(ExpectedSfxCompatibleQuantile(), 0, 0, 0), + }, + } + + for _, tc := range testCases { + t.Run(tc.Name, func(t *testing.T) { + reporter := newMockReporter() + require.NotNil(t, reporter) + parser := &prometheusRemoteOtelParser{} + actual, err := parser.fromPrometheusWriteRequestMetrics(tc.Sample) + assert.NoError(t, err) + + require.NoError(t, pmetrictest.CompareMetrics(tc.Expected, actual, + pmetrictest.IgnoreMetricDataPointsOrder(), + pmetrictest.IgnoreMetricsOrder())) + }) + + } +} diff --git a/internal/receiver/prometheusremotewritereceiver/receiver.go b/internal/receiver/signalfxgatewayprometheusremotewritereceiver/receiver.go similarity index 91% rename from internal/receiver/prometheusremotewritereceiver/receiver.go rename to internal/receiver/signalfxgatewayprometheusremotewritereceiver/receiver.go index a78309262b..ba65e370e1 100644 --- a/internal/receiver/prometheusremotewritereceiver/receiver.go +++ b/internal/receiver/signalfxgatewayprometheusremotewritereceiver/receiver.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package prometheusremotewritereceiver +package signalfxgatewayprometheusremotewritereceiver import ( "context" @@ -63,20 +63,22 @@ func New( // Start starts an HTTP server that can process Prometheus Remote Write Requests func (receiver *prometheusRemoteWriteReceiver) Start(ctx context.Context, host component.Host) error { metricsChannel := make(chan pmetric.Metrics, receiver.config.BufferSize) - cfg := &ServerConfig{ + cfg := &serverConfig{ HTTPServerSettings: receiver.config.HTTPServerSettings, Path: receiver.config.ListenPath, Mc: metricsChannel, + TelemetrySettings: receiver.settings.TelemetrySettings, Reporter: receiver.reporter, Host: host, + Parser: &prometheusRemoteOtelParser{}, } ctx, receiver.cancel = context.WithCancel(ctx) - server, err := newPrometheusRemoteWriteServer(ctx, cfg) + server, err := newPrometheusRemoteWriteServer(cfg) if err != nil { return err } if nil != receiver.server { - err := receiver.server.Close() + err := receiver.server.close() if err != nil { return err } @@ -94,7 +96,7 @@ func (receiver *prometheusRemoteWriteReceiver) startServer(host component.Host) if prometheusRemoteWriteServer == nil { host.ReportFatalError(fmt.Errorf("start called on null prometheusRemoteWriteServer for receiver %s", typeString)) } - if err := prometheusRemoteWriteServer.ListenAndServe(); err != nil { + if err := prometheusRemoteWriteServer.listenAndServe(); err != nil { // our receiver swallows http's ErrServeClosed, and we should only get "concerning" issues at this point in the code. host.ReportFatalError(err) receiver.reporter.OnDebugf("Error in %s/%s listening on %s/%s: %s", typeString, receiver.settings.ID, prometheusRemoteWriteServer.Addr, prometheusRemoteWriteServer.Path, err) @@ -127,7 +129,7 @@ func (receiver *prometheusRemoteWriteReceiver) Shutdown(context.Context) error { } defer receiver.cancel() if receiver.server != nil { - return receiver.server.Close() + return receiver.server.close() } return nil } diff --git a/internal/receiver/signalfxgatewayprometheusremotewritereceiver/receiver_test.go b/internal/receiver/signalfxgatewayprometheusremotewritereceiver/receiver_test.go new file mode 100644 index 0000000000..5f41d6c764 --- /dev/null +++ b/internal/receiver/signalfxgatewayprometheusremotewritereceiver/receiver_test.go @@ -0,0 +1,181 @@ +// Copyright Splunk, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package signalfxgatewayprometheusremotewritereceiver + +import ( + "context" + "errors" + "fmt" + "testing" + "time" + + "github.com/prometheus/prometheus/prompb" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.opentelemetry.io/collector/component/componenttest" + "go.opentelemetry.io/collector/consumer/consumertest" + "go.opentelemetry.io/collector/receiver/receivertest" +) + +func TestEmptySend(t *testing.T) { + timeout := time.Second * 10 + ctx, cancel := context.WithTimeout(context.Background(), timeout) + defer cancel() + + cfg := createDefaultConfig().(*Config) + freePort, err := GetFreePort() + require.NoError(t, err) + expectedEndpoint := fmt.Sprintf("localhost:%d", freePort) + + cfg.Endpoint = expectedEndpoint + cfg.ListenPath = "/metrics" + + nopHost := componenttest.NewNopHost() + mockSettings := receivertest.NewNopCreateSettings() + mockConsumer := consumertest.NewNop() + mockreporter := newMockReporter() + receiver, err := New(mockSettings, cfg, mockConsumer) + remoteWriteReceiver := receiver.(*prometheusRemoteWriteReceiver) + remoteWriteReceiver.reporter = mockreporter + + assert.NoError(t, err) + require.NotNil(t, remoteWriteReceiver) + require.NoError(t, remoteWriteReceiver.Start(ctx, nopHost)) + require.NotEmpty(t, remoteWriteReceiver.server) + require.NotEmpty(t, remoteWriteReceiver.cancel) + require.NotEmpty(t, remoteWriteReceiver.config) + require.Equal(t, remoteWriteReceiver.config.Endpoint, fmt.Sprintf("localhost:%d", freePort)) + require.NotEmpty(t, remoteWriteReceiver.settings) + require.NotNil(t, remoteWriteReceiver.reporter) + require.Equal(t, expectedEndpoint, remoteWriteReceiver.server.Addr) + + client, err := NewMockPrwClient( + cfg.Endpoint, + "metrics", + time.Second*5, + ) + require.NoError(t, err) + require.NotNil(t, client) + require.NoError(t, client.SendWriteRequest(&prompb.WriteRequest{ + Timeseries: []prompb.TimeSeries{}, + Metadata: []prompb.MetricMetadata{}, + })) + require.NoError(t, mockreporter.WaitAllOnMetricsProcessedCalls(10*time.Second)) + require.NoError(t, remoteWriteReceiver.Shutdown(ctx)) +} + +func TestSuccessfulSend(t *testing.T) { + timeout := time.Second * 10 + ctx, cancel := context.WithTimeout(context.Background(), timeout) + defer cancel() + + cfg := createDefaultConfig().(*Config) + freePort, err := GetFreePort() + require.NoError(t, err) + expectedEndpoint := fmt.Sprintf("localhost:%d", freePort) + + cfg.Endpoint = expectedEndpoint + cfg.ListenPath = "/metrics" + + nopHost := componenttest.NewNopHost() + mockSettings := receivertest.NewNopCreateSettings() + mockConsumer := consumertest.NewNop() + + sampleNoMdMetrics := GetWriteRequestsOfAllTypesWithoutMetadata() + mockreporter := newMockReporter() + + receiver, err := New(mockSettings, cfg, mockConsumer) + remoteWriteReceiver := receiver.(*prometheusRemoteWriteReceiver) + remoteWriteReceiver.reporter = mockreporter + + assert.NoError(t, err) + require.NotNil(t, remoteWriteReceiver) + require.NoError(t, remoteWriteReceiver.Start(ctx, nopHost)) + require.NotEmpty(t, remoteWriteReceiver.server) + require.NotEmpty(t, remoteWriteReceiver.cancel) + require.NotEmpty(t, remoteWriteReceiver.config) + require.Equal(t, remoteWriteReceiver.config.Endpoint, fmt.Sprintf("localhost:%d", freePort)) + require.NotEmpty(t, remoteWriteReceiver.settings) + require.NotNil(t, remoteWriteReceiver.reporter) + require.Equal(t, expectedEndpoint, remoteWriteReceiver.server.Addr) + + client, err := NewMockPrwClient( + cfg.Endpoint, + "metrics", + time.Second*5, + ) + require.NoError(t, err) + require.NotNil(t, client) + + for index, wq := range sampleNoMdMetrics { + mockreporter.AddExpectedStart(1) + mockreporter.AddExpectedSuccess(1) + err = client.SendWriteRequest(wq) + assert.NoError(t, err, "failed to write %d", index) + if nil != err { + assert.NoError(t, errors.Unwrap(err)) + } + // always will have 3 "health" metrics due to sfx gateway compatibility metrics + assert.GreaterOrEqual(t, mockreporter.TotalSuccessMetrics.Load(), int32(len(wq.Timeseries)+3)) + assert.Equal(t, mockreporter.TotalErrorMetrics.Load(), int32(0)) + } + + require.NoError(t, remoteWriteReceiver.Shutdown(ctx)) +} + +func TestRealReporter(t *testing.T) { + timeout := time.Second * 10 + ctx, cancel := context.WithTimeout(context.Background(), timeout) + defer cancel() + + cfg := createDefaultConfig().(*Config) + freePort, err := GetFreePort() + require.NoError(t, err) + expectedEndpoint := fmt.Sprintf("localhost:%d", freePort) + + cfg.Endpoint = expectedEndpoint + cfg.ListenPath = "/metrics" + + nopHost := componenttest.NewNopHost() + mockSettings := receivertest.NewNopCreateSettings() + mockConsumer := consumertest.NewNop() + + sampleNoMdMetrics := GetWriteRequestsOfAllTypesWithoutMetadata() + + receiver, err := New(mockSettings, cfg, mockConsumer) + remoteWriteReceiver := receiver.(*prometheusRemoteWriteReceiver) + + assert.NoError(t, err) + require.NotNil(t, remoteWriteReceiver) + require.NoError(t, remoteWriteReceiver.Start(ctx, nopHost)) + require.NotEmpty(t, remoteWriteReceiver.settings.TelemetrySettings) + require.NotEmpty(t, remoteWriteReceiver.settings.Logger) + require.NotEmpty(t, remoteWriteReceiver.settings.BuildInfo) + + client, err := NewMockPrwClient( + cfg.Endpoint, + "metrics", + time.Second*5, + ) + require.NoError(t, err) + require.NotNil(t, client) + + for index, wq := range sampleNoMdMetrics { + err = client.SendWriteRequest(wq) + require.NoError(t, err, "failed to write %d", index) + } + + require.NoError(t, remoteWriteReceiver.Shutdown(ctx)) +} diff --git a/internal/receiver/prometheusremotewritereceiver/reporter.go b/internal/receiver/signalfxgatewayprometheusremotewritereceiver/reporter.go similarity index 98% rename from internal/receiver/prometheusremotewritereceiver/reporter.go rename to internal/receiver/signalfxgatewayprometheusremotewritereceiver/reporter.go index d6899371cf..a23069c3e7 100644 --- a/internal/receiver/prometheusremotewritereceiver/reporter.go +++ b/internal/receiver/signalfxgatewayprometheusremotewritereceiver/reporter.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package prometheusremotewritereceiver +package signalfxgatewayprometheusremotewritereceiver import ( "context" diff --git a/internal/receiver/prometheusremotewritereceiver/server.go b/internal/receiver/signalfxgatewayprometheusremotewritereceiver/server.go similarity index 57% rename from internal/receiver/prometheusremotewritereceiver/server.go rename to internal/receiver/signalfxgatewayprometheusremotewritereceiver/server.go index b34cfe203f..ff3a3fea9d 100644 --- a/internal/receiver/prometheusremotewritereceiver/server.go +++ b/internal/receiver/signalfxgatewayprometheusremotewritereceiver/server.go @@ -12,14 +12,14 @@ // See the License for the specific language governing permissions and // limitations under the License. -package prometheusremotewritereceiver +package signalfxgatewayprometheusremotewritereceiver import ( - "context" "net/http" "sync" "github.com/gorilla/mux" + "github.com/prometheus/prometheus/storage/remote" "go.opentelemetry.io/collector/component" "go.opentelemetry.io/collector/config/confighttp" "go.opentelemetry.io/collector/pdata/pmetric" @@ -27,45 +27,45 @@ import ( type prometheusRemoteWriteServer struct { *http.Server - *ServerConfig + *serverConfig closeChannel *sync.Once } -type ServerConfig struct { +type serverConfig struct { Reporter reporter component.Host Mc chan<- pmetric.Metrics component.TelemetrySettings - Path string + Path string + Parser *prometheusRemoteOtelParser confighttp.HTTPServerSettings } -func newPrometheusRemoteWriteServer(ctx context.Context, config *ServerConfig) (*prometheusRemoteWriteServer, error) { +func newPrometheusRemoteWriteServer(config *serverConfig) (*prometheusRemoteWriteServer, error) { mx := mux.NewRouter() - handler := newHandler(ctx, config.Reporter, config, config.Mc) + handler := newHandler(config.Parser, config, config.Mc) mx.HandleFunc(config.Path, handler) mx.Host(config.Endpoint) - server, err := config.HTTPServerSettings.ToServer(config.Host, config.TelemetrySettings, handler) - // Currently this is not set, in favor of the pattern where they always explicitly pass the listener + server, err := config.HTTPServerSettings.ToServer(config.Host, config.TelemetrySettings, mx) server.Addr = config.Endpoint if err != nil { return nil, err } return &prometheusRemoteWriteServer{ Server: server, - ServerConfig: config, + serverConfig: config, closeChannel: &sync.Once{}, }, nil } -func (prw *prometheusRemoteWriteServer) Close() error { +func (prw *prometheusRemoteWriteServer) close() error { defer prw.closeChannel.Do(func() { close(prw.Mc) }) return prw.Server.Close() } -func (prw *prometheusRemoteWriteServer) ListenAndServe() error { +func (prw *prometheusRemoteWriteServer) listenAndServe() error { prw.Reporter.OnDebugf("Starting prometheus simple write server") - listener, err := prw.ServerConfig.ToListener() + listener, err := prw.serverConfig.ToListener() if err != nil { return err } @@ -77,11 +77,25 @@ func (prw *prometheusRemoteWriteServer) ListenAndServe() error { return err } -func newHandler(ctx context.Context, reporter reporter, _ *ServerConfig, _ chan<- pmetric.Metrics) http.HandlerFunc { +func newHandler(parser *prometheusRemoteOtelParser, sc *serverConfig, mc chan<- pmetric.Metrics) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - // THIS IS A STUB FUNCTION. You can see another branch with how I'm thinking this will look if you're curious - ctx2 := reporter.StartMetricsOp(ctx) - reporter.OnMetricsProcessed(ctx2, 0, nil) - w.WriteHeader(http.StatusNoContent) + sc.Reporter.OnDebugf("Processing write request %s", r.RequestURI) + req, err := remote.DecodeWriteRequest(r.Body) + if err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + if len(req.Timeseries) == 0 && len(req.Metadata) == 0 { + w.WriteHeader(http.StatusNoContent) + return + } + results, err := parser.fromPrometheusWriteRequestMetrics(req) + if nil != err { + http.Error(w, err.Error(), http.StatusBadRequest) + sc.Reporter.OnDebugf("prometheus_translation", err) + return + } + mc <- results + w.WriteHeader(http.StatusAccepted) } } diff --git a/internal/receiver/signalfxgatewayprometheusremotewritereceiver/server_test.go b/internal/receiver/signalfxgatewayprometheusremotewritereceiver/server_test.go new file mode 100644 index 0000000000..758644530b --- /dev/null +++ b/internal/receiver/signalfxgatewayprometheusremotewritereceiver/server_test.go @@ -0,0 +1,131 @@ +// Copyright Splunk, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package signalfxgatewayprometheusremotewritereceiver + +import ( + "context" + "fmt" + "sync" + "testing" + "time" + + "github.com/prometheus/prometheus/prompb" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.opentelemetry.io/collector/config/confighttp" + "go.opentelemetry.io/collector/pdata/pmetric" +) + +func TestWriteEmpty(t *testing.T) { + mc := make(chan<- pmetric.Metrics) + mockReporter := newMockReporter() + freePort, err := GetFreePort() + require.NoError(t, err) + expectedEndpoint := fmt.Sprintf("localhost:%d", freePort) + parser := &prometheusRemoteOtelParser{} + require.NoError(t, err) + cfg := &serverConfig{ + Path: "/metrics", + Reporter: mockReporter, + Mc: mc, + HTTPServerSettings: confighttp.HTTPServerSettings{ + Endpoint: expectedEndpoint, + }, + Parser: parser, + } + require.Equal(t, expectedEndpoint, cfg.Endpoint) + timeout := time.Second * 10 + ctx, cancel := context.WithTimeout(context.Background(), timeout) + defer cancel() + remoteWriteServer, err := newPrometheusRemoteWriteServer(cfg) + assert.NoError(t, err) + require.NotNil(t, remoteWriteServer) + + wg := sync.WaitGroup{} + wg.Add(1) + go func() { + t.Logf("starting server...") + require.NoError(t, remoteWriteServer.listenAndServe()) + t.Logf("stopped server...") + wg.Done() + }() + + client, err := NewMockPrwClient( + cfg.Endpoint, + "metrics", + timeout, + ) + require.NoError(t, err) + require.NotNil(t, client) + time.Sleep(100 * time.Millisecond) + require.NoError(t, client.SendWriteRequest(&prompb.WriteRequest{ + Timeseries: []prompb.TimeSeries{}, + Metadata: []prompb.MetricMetadata{}, + })) + + require.NoError(t, mockReporter.WaitAllOnMetricsProcessedCalls(time.Second*5)) + require.NoError(t, remoteWriteServer.Shutdown(ctx)) + require.Eventually(t, func() bool { wg.Wait(); return true }, time.Second*2, 100*time.Millisecond) +} + +func TestWriteMany(t *testing.T) { + mc := make(chan<- pmetric.Metrics, 1000) + mockReporter := newMockReporter() + freePort, err := GetFreePort() + require.NoError(t, err) + expectedEndpoint := fmt.Sprintf("localhost:%d", freePort) + parser := &prometheusRemoteOtelParser{} + require.NoError(t, err) + cfg := &serverConfig{ + Path: "/metrics", + Reporter: mockReporter, + Mc: mc, + HTTPServerSettings: confighttp.HTTPServerSettings{ + Endpoint: expectedEndpoint, + }, + Parser: parser, + } + require.Equal(t, expectedEndpoint, cfg.Endpoint) + timeout := time.Second * 1000 + ctx, cancel := context.WithTimeout(context.Background(), timeout) + defer cancel() + remoteWriteServer, err := newPrometheusRemoteWriteServer(cfg) + assert.NoError(t, err) + require.NotNil(t, remoteWriteServer) + + wg := sync.WaitGroup{} + wg.Add(1) + go func() { + require.NoError(t, remoteWriteServer.listenAndServe()) + wg.Done() + }() + + client, err := NewMockPrwClient( + cfg.Endpoint, + "metrics", + timeout, + ) + require.NoError(t, err) + require.NotNil(t, client) + time.Sleep(100 * time.Millisecond) + wqs := GetWriteRequestsOfAllTypesWithoutMetadata() + for _, wq := range wqs { + require.NoError(t, client.SendWriteRequest(wq)) + } + + require.NoError(t, mockReporter.WaitAllOnMetricsProcessedCalls(time.Second*5)) + require.NoError(t, remoteWriteServer.Shutdown(ctx)) + require.Eventually(t, func() bool { wg.Wait(); return true }, time.Second*2, 100*time.Millisecond) +} diff --git a/internal/receiver/prometheusremotewritereceiver/transport.go b/internal/receiver/signalfxgatewayprometheusremotewritereceiver/transport.go similarity index 97% rename from internal/receiver/prometheusremotewritereceiver/transport.go rename to internal/receiver/signalfxgatewayprometheusremotewritereceiver/transport.go index aca5bc1f14..6b8bb4b5e3 100644 --- a/internal/receiver/prometheusremotewritereceiver/transport.go +++ b/internal/receiver/signalfxgatewayprometheusremotewritereceiver/transport.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package prometheusremotewritereceiver +package signalfxgatewayprometheusremotewritereceiver import ( "context"