From a8abc29affb5e056c9c3ab4820efe6942b1a16e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20R=C3=BCger?= <manuel@rueg.eu> Date: Mon, 30 Oct 2023 20:39:02 +0100 Subject: [PATCH] WIP: Initial support for CEL MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This adds initial support for queries using the common expression language (CEL). ValueToJSON function is adapted from https://github.com/google/cel-go/blob/cfbf821f1b458533051306305a39b743db7c4bdb/codelab/codelab.go#L274 (Apache-2.0 Licensed) Signed-off-by: Manuel RĂ¼ger <manuel@rueg.eu> --- README.md | 20 +++++++- config/config.go | 13 ++++- examples/config.yml | 63 ++++++++++++++++++++--- exporter/collector.go | 115 ++++++++++++++++++++++++++++++++++++++---- exporter/util.go | 4 +- go.mod | 8 ++- go.sum | 12 +++++ 7 files changed, 213 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index f514a85d..d8d9ae3a 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ json_exporter ======================== [![CircleCI](https://circleci.com/gh/prometheus-community/json_exporter.svg?style=svg)](https://circleci.com/gh/prometheus-community/json_exporter) -A [prometheus](https://prometheus.io/) exporter which scrapes remote JSON by JSONPath. +A [prometheus](https://prometheus.io/) exporter which scrapes remote JSON by JSONPath or [CEL (Common Expression Language)](https://github.com/google/cel-spec). - [Supported JSONPath Syntax](https://kubernetes.io/docs/reference/kubectl/jsonpath/) - [Examples configurations](/examples) @@ -21,6 +21,24 @@ Serving HTTP on :: port 8000 (http://[::]:8000/) ... ## TEST with 'default' module $ curl "http://localhost:7979/probe?module=default&target=http://localhost:8000/examples/data.json" +# HELP example_cel_global_value Example of a top-level global value scrape in the json using cel +# TYPE example_cel_global_value gauge +example_cel_global_value{environment="beta",location="planet-mars"} 1234 +# HELP example_cel_timestamped_value_count Example of a timestamped value scrape in the json +# TYPE example_cel_timestamped_value_count untyped +example_cel_timestamped_value_count{environment="beta"} 2 +# HELP example_cel_value_active Example of sub-level value scrapes from a json +# TYPE example_cel_value_active untyped +example_cel_value_active{environment="beta",id="id-A"} 1 +example_cel_value_active{environment="beta",id="id-C"} 1 +# HELP example_cel_value_boolean Example of sub-level value scrapes from a json +# TYPE example_cel_value_boolean untyped +example_cel_value_boolean{environment="beta",id="id-A"} 1 +example_cel_value_boolean{environment="beta",id="id-C"} 0 +# HELP example_cel_value_count Example of sub-level value scrapes from a json +# TYPE example_cel_value_count untyped +example_cel_value_count{environment="beta",id="id-A"} 1 +example_cel_value_count{environment="beta",id="id-C"} 3 # HELP example_global_value Example of a top-level global value scrape in the json # TYPE example_global_value untyped example_global_value{environment="beta",location="planet-mars"} 1234 diff --git a/config/config.go b/config/config.go index 6cd0accb..6a69ed58 100644 --- a/config/config.go +++ b/config/config.go @@ -23,6 +23,7 @@ import ( // Metric contains values that define a metric type Metric struct { Name string + Engine EngineType Path string Labels map[string]string Type ScrapeType @@ -44,7 +45,14 @@ type ValueType string const ( ValueTypeGauge ValueType = "gauge" ValueTypeCounter ValueType = "counter" - ValueTypeUntyped ValueType = "untyped" + ValueTypeUntyped ValueType = "untyped" // default +) + +type EngineType string + +const ( + EngineTypeJSONPath EngineType = "jsonpath" // default + EngineTypeCEL EngineType = "cel" ) // Config contains multiple modules. @@ -89,6 +97,9 @@ func LoadConfig(configPath string) (Config, error) { if module.Metrics[i].ValueType == "" { module.Metrics[i].ValueType = ValueTypeUntyped } + if module.Metrics[i].Engine == "" { + module.Metrics[i].Engine = EngineTypeJSONPath + } } } diff --git a/examples/config.yml b/examples/config.yml index 9d0745c0..bdd9ce94 100644 --- a/examples/config.yml +++ b/examples/config.yml @@ -9,7 +9,17 @@ modules: help: Example of a top-level global value scrape in the json labels: environment: beta # static label - location: 'planet-{.location}' # dynamic label + location: 'planet-{ .location }' # dynamic label + + - name: example_cel_global_value + engine: cel + path: '.counter' + help: Example of a top-level global value scrape in the json using cel + valuetype: 'gauge' + labels: + environment: "\"beta\"" # static label. Quotes need to be escaped for CEL + location: "\"planet-\"+.location" # dynamic label. Quotes need to be escaped for CEL + - name: example_timestamped_value type: object path: '{ .values[?(@.state == "INACTIVE")] }' @@ -18,18 +28,44 @@ modules: labels: environment: beta # static label values: - count: '{.count}' # dynamic value + count: '{ .count }' # dynamic value + + - name: example_cel_timestamped_value + type: object + engine: cel + path: ".values.filter(i, i.state == \"INACTIVE\")" + epochTimestamp: '.timestamp' + help: Example of a timestamped value scrape in the json + labels: + environment: "\"beta\"" # static label + values: + count: '.count' # dynamic value + - name: example_value type: object help: Example of sub-level value scrapes from a json - path: '{.values[?(@.state == "ACTIVE")]}' + path: '{ .values[?(@.state == "ACTIVE")] }' labels: environment: beta # static label - id: '{.id}' # dynamic label + id: '{ .id }' # dynamic label values: active: 1 # static value - count: '{.count}' # dynamic value - boolean: '{.some_boolean}' + count: '{ .count }' # dynamic value + boolean: '{ .some_boolean }' + + - name: example_cel_value + type: object + engine: cel + help: Example of sub-level value scrapes from a json + path: ".values.filter(i, i.state == \"ACTIVE\")" + labels: + environment: "\"beta\"" # static label + id: '.id' # dynamic label + values: + active: 1 # static value + count: '.count' # dynamic value + boolean: '.some_boolean' + animals: metrics: @@ -43,6 +79,17 @@ modules: values: population: '{ .population }' + - name: animal_cel + type: object + engine: cel + help: Example of top-level lists in a separate module + path: '[*]' + labels: + name: '.noun' + predator: '.predator' + values: + population: '.population' + ## HTTP connection configurations can be set in 'modules.<module_name>.http_client_config' field. For full http client config parameters, ref: https://pkg.go.dev/github.com/prometheus/common/config?tab=doc#HTTPClientConfig # # http_client_config: @@ -59,11 +106,11 @@ modules: ## If 'modueles.<module_name>.body' field is set, it will be sent by the exporter as the body content in the scrape request. The HTTP method will also be set as 'POST' in this case. # body: # content: | - # {"time_diff": "1m25s", "anotherVar": "some value"} + # { "time_diff": "1m25s", "anotherVar": "some value" } ## The body content can also be a Go Template (https://golang.org/pkg/text/template), with all the functions from the Sprig library (https://masterminds.github.io/sprig/) available. All the query parameters sent by prometheus in the scrape query to the exporter, are available in the template. # body: # content: | - # {"time_diff": "{{ duration `95` }}","anotherVar": "{{ .myVal | first }}"} + # { "time_diff": "{{ duration `95` }}","anotherVar": "{{ .myVal | first }}" } # templatize: true diff --git a/exporter/collector.go b/exporter/collector.go index 4effc10f..59ef93fe 100644 --- a/exporter/collector.go +++ b/exporter/collector.go @@ -16,12 +16,19 @@ package exporter import ( "bytes" "encoding/json" + "fmt" + "reflect" "time" "github.com/go-kit/log" "github.com/go-kit/log/level" + "github.com/google/cel-go/cel" + "github.com/google/cel-go/common/types/ref" "github.com/prometheus-community/json_exporter/config" "github.com/prometheus/client_golang/prometheus" + "google.golang.org/protobuf/encoding/protojson" + "google.golang.org/protobuf/proto" + structpb "google.golang.org/protobuf/types/known/structpb" "k8s.io/client-go/util/jsonpath" ) @@ -34,6 +41,7 @@ type JSONMetricCollector struct { type JSONMetric struct { Desc *prometheus.Desc Type config.ScrapeType + EngineType config.EngineType KeyJSONPath string ValueJSONPath string LabelsJSONPaths []string @@ -51,7 +59,8 @@ func (mc JSONMetricCollector) Collect(ch chan<- prometheus.Metric) { for _, m := range mc.JSONMetrics { switch m.Type { case config.ValueScrape: - value, err := extractValue(mc.Logger, mc.Data, m.KeyJSONPath, false) + level.Debug(mc.Logger).Log("msg", "Extracting value for metric", "path", m.KeyJSONPath, "metric", m.Desc) + value, err := extractValue(mc.Logger, m.EngineType, mc.Data, m.KeyJSONPath, false) if err != nil { level.Error(mc.Logger).Log("msg", "Failed to extract value for metric", "path", m.KeyJSONPath, "err", err, "metric", m.Desc) continue @@ -62,7 +71,7 @@ func (mc JSONMetricCollector) Collect(ch chan<- prometheus.Metric) { m.Desc, m.ValueType, floatValue, - extractLabels(mc.Logger, mc.Data, m.LabelsJSONPaths)..., + extractLabels(mc.Logger, m.EngineType, mc.Data, m.LabelsJSONPaths)..., ) ch <- timestampMetric(mc.Logger, m, mc.Data, metric) } else { @@ -71,7 +80,8 @@ func (mc JSONMetricCollector) Collect(ch chan<- prometheus.Metric) { } case config.ObjectScrape: - values, err := extractValue(mc.Logger, mc.Data, m.KeyJSONPath, true) + level.Debug(mc.Logger).Log("msg", "Extracting object for metric", "path", m.KeyJSONPath, "metric", m.Desc) + values, err := extractValue(mc.Logger, m.EngineType, mc.Data, m.KeyJSONPath, true) if err != nil { level.Error(mc.Logger).Log("msg", "Failed to extract json objects for metric", "err", err, "metric", m.Desc) continue @@ -85,7 +95,7 @@ func (mc JSONMetricCollector) Collect(ch chan<- prometheus.Metric) { level.Error(mc.Logger).Log("msg", "Failed to marshal data to json", "path", m.ValueJSONPath, "err", err, "metric", m.Desc, "data", data) continue } - value, err := extractValue(mc.Logger, jdata, m.ValueJSONPath, false) + value, err := extractValue(mc.Logger, m.EngineType, jdata, m.ValueJSONPath, false) if err != nil { level.Error(mc.Logger).Log("msg", "Failed to extract value for metric", "path", m.ValueJSONPath, "err", err, "metric", m.Desc) continue @@ -96,7 +106,7 @@ func (mc JSONMetricCollector) Collect(ch chan<- prometheus.Metric) { m.Desc, m.ValueType, floatValue, - extractLabels(mc.Logger, jdata, m.LabelsJSONPaths)..., + extractLabels(mc.Logger, m.EngineType, jdata, m.LabelsJSONPaths)..., ) ch <- timestampMetric(mc.Logger, m, jdata, metric) } else { @@ -105,7 +115,7 @@ func (mc JSONMetricCollector) Collect(ch chan<- prometheus.Metric) { } } } else { - level.Error(mc.Logger).Log("msg", "Failed to convert extracted objects to json", "err", err, "metric", m.Desc) + level.Error(mc.Logger).Log("msg", "Failed to convert extracted objects to json", "value", values, "err", err, "metric", m.Desc) continue } default: @@ -115,8 +125,19 @@ func (mc JSONMetricCollector) Collect(ch chan<- prometheus.Metric) { } } +func extractValue(logger log.Logger, engine config.EngineType, data []byte, path string, enableJSONOutput bool) (string, error) { + switch engine { + case config.EngineTypeJSONPath: + return extractValueJSONPath(logger, data, path, enableJSONOutput) + case config.EngineTypeCEL: + return extractValueCEL(logger, data, path, enableJSONOutput) + default: + return "", fmt.Errorf("Unknown engine type: %s", engine) + } +} + // Returns the last matching value at the given json path -func extractValue(logger log.Logger, data []byte, path string, enableJSONOutput bool) (string, error) { +func extractValueJSONPath(logger log.Logger, data []byte, path string, enableJSONOutput bool) (string, error) { var jsonData interface{} buf := new(bytes.Buffer) @@ -148,11 +169,70 @@ func extractValue(logger log.Logger, data []byte, path string, enableJSONOutput return buf.String(), nil } +// Returns the last matching value at the given json path +func extractValueCEL(logger log.Logger, data []byte, expression string, enableJSONOutput bool) (string, error) { + + var jsonData map[string]any + + err := json.Unmarshal(data, &jsonData) + if err != nil { + level.Error(logger).Log("msg", "Failed to unmarshal data to json", "err", err, "data", data) + return "", err + } + + inputVars := make([]cel.EnvOption, 0, len(jsonData)) + for k := range jsonData { + inputVars = append(inputVars, cel.Variable(k, cel.DynType)) + } + + env, err := cel.NewEnv(inputVars...) + + if err != nil { + level.Error(logger).Log("msg", "Failed to set up CEL environment", "err", err, "data", data) + return "", err + } + + ast, issues := env.Compile(expression) + if issues != nil && issues.Err() != nil { + level.Error(logger).Log("CEL type-check error", issues.Err(), "expression", expression) + return "", err + } + prg, err := env.Program(ast) + if err != nil { + level.Error(logger).Log("CEL program construction error", err) + return "", err + } + + out, _, err := prg.Eval(jsonData) + if err != nil { + level.Error(logger).Log("msg", "Failed to evaluate cel query", "err", err, "expression", expression, "data", jsonData) + return "", err + } + + // Since we are finally going to extract only float64, unquote if necessary + + //res, err := jsonpath.UnquoteExtend(fmt.Sprintf("%g", out)) + //if err == nil { + // level.Error(logger).Log("msg","Triggered") + // return res, nil + //} + level.Error(logger).Log("msg", "Triggered later", "val", out) + if enableJSONOutput { + res, err := valueToJSON(out) + if err != nil { + return "", err + } + return res, nil + } + + return fmt.Sprintf("%v", out), nil +} + // Returns the list of labels created from the list of provided json paths -func extractLabels(logger log.Logger, data []byte, paths []string) []string { +func extractLabels(logger log.Logger, engine config.EngineType, data []byte, paths []string) []string { labels := make([]string, len(paths)) for i, path := range paths { - if result, err := extractValue(logger, data, path, false); err == nil { + if result, err := extractValue(logger, engine, data, path, false); err == nil { labels[i] = result } else { level.Error(logger).Log("msg", "Failed to extract label value", "err", err, "path", path, "data", data) @@ -165,7 +245,7 @@ func timestampMetric(logger log.Logger, m JSONMetric, data []byte, pm prometheus if m.EpochTimestampJSONPath == "" { return pm } - ts, err := extractValue(logger, data, m.EpochTimestampJSONPath, false) + ts, err := extractValue(logger, m.EngineType, data, m.EpochTimestampJSONPath, false) if err != nil { level.Error(logger).Log("msg", "Failed to extract timestamp for metric", "path", m.KeyJSONPath, "err", err, "metric", m.Desc) return pm @@ -178,3 +258,18 @@ func timestampMetric(logger log.Logger, m JSONMetric, data []byte, pm prometheus timestamp := time.UnixMilli(epochTime) return prometheus.NewMetricWithTimestamp(timestamp, pm) } + +// valueToJSON converts the CEL type to a protobuf JSON representation and +// marshals the result to a string. +func valueToJSON(val ref.Val) (string, error) { + v, err := val.ConvertToNative(reflect.TypeOf(&structpb.Value{})) + if err != nil { + return "", err + } + marshaller := protojson.MarshalOptions{Indent: " "} + bytes, err := marshaller.Marshal(v.(proto.Message)) + if err != nil { + return "", err + } + return string(bytes), err +} diff --git a/exporter/util.go b/exporter/util.go index 8374ddce..42e1077d 100644 --- a/exporter/util.go +++ b/exporter/util.go @@ -103,6 +103,7 @@ func CreateMetricsList(c config.Module) ([]JSONMetric, error) { variableLabels, nil, ), + EngineType: metric.Engine, KeyJSONPath: metric.Path, LabelsJSONPaths: variableLabelsValues, ValueType: valueType, @@ -125,6 +126,7 @@ func CreateMetricsList(c config.Module) ([]JSONMetric, error) { variableLabels, nil, ), + EngineType: metric.Engine, KeyJSONPath: metric.Path, ValueJSONPath: valuePath, LabelsJSONPaths: variableLabelsValues, @@ -134,7 +136,7 @@ func CreateMetricsList(c config.Module) ([]JSONMetric, error) { metrics = append(metrics, jsonMetric) } default: - return nil, fmt.Errorf("Unknown metric type: '%s', for metric: '%s'", metric.Type, metric.Name) + return nil, fmt.Errorf("unknown metric type: '%s', for metric: '%s'", metric.Type, metric.Name) } } return metrics, nil diff --git a/go.mod b/go.mod index 880ed840..4044eee9 100644 --- a/go.mod +++ b/go.mod @@ -6,9 +6,11 @@ require ( github.com/Masterminds/sprig/v3 v3.2.3 github.com/alecthomas/kingpin/v2 v2.4.0 github.com/go-kit/log v0.2.1 + github.com/google/cel-go v0.21.0 github.com/prometheus/client_golang v1.19.1 github.com/prometheus/common v0.55.0 github.com/prometheus/exporter-toolkit v0.11.0 + google.golang.org/protobuf v1.34.2 gopkg.in/yaml.v2 v2.4.0 k8s.io/client-go v0.28.3 ) @@ -17,6 +19,7 @@ require ( github.com/Masterminds/goutils v1.1.1 // indirect github.com/Masterminds/semver/v3 v3.2.0 // indirect github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 // indirect + github.com/antlr4-go/antlr/v4 v4.13.0 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/coreos/go-systemd/v22 v22.5.0 // indirect @@ -33,12 +36,15 @@ require ( github.com/prometheus/procfs v0.15.1 // indirect github.com/shopspring/decimal v1.2.0 // indirect github.com/spf13/cast v1.3.1 // indirect + github.com/stoewer/go-strcase v1.2.0 // indirect github.com/xhit/go-str2duration/v2 v2.1.0 // indirect golang.org/x/crypto v0.24.0 // indirect + golang.org/x/exp v0.0.0-20230515195305-f3d0a9c9a5cc // indirect golang.org/x/net v0.26.0 // indirect golang.org/x/oauth2 v0.21.0 // indirect golang.org/x/sync v0.7.0 // indirect golang.org/x/sys v0.21.0 // indirect golang.org/x/text v0.16.0 // indirect - google.golang.org/protobuf v1.34.2 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20230803162519-f966b187b2e5 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20230803162519-f966b187b2e5 // indirect ) diff --git a/go.sum b/go.sum index 1d1d6374..1788eda8 100644 --- a/go.sum +++ b/go.sum @@ -8,6 +8,8 @@ github.com/alecthomas/kingpin/v2 v2.4.0 h1:f48lwail6p8zpO1bC4TxtqACaGqHYA22qkHjH github.com/alecthomas/kingpin/v2 v2.4.0/go.mod h1:0gyi0zQnjuFk8xrkNKamJoyUo382HRL7ATRpFZCw6tE= 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/antlr4-go/antlr/v4 v4.13.0 h1:lxCg3LAv+EUK6t1i0y1V6/SLeUi0eKEKdhQAlS8TVTI= +github.com/antlr4-go/antlr/v4 v4.13.0/go.mod h1:pfChB/xh/Unjila75QW7+VU4TSnWnnk9UTnmpPaOR2g= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= @@ -22,6 +24,8 @@ github.com/go-kit/log v0.2.1/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBj github.com/go-logfmt/logfmt v0.5.1 h1:otpy5pqBCBZ1ng9RQ0dPu4PN7ba75Y/aA+UpowDyNVA= github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/google/cel-go v0.21.0 h1:cl6uW/gxN+Hy50tNYvI691+sXxioCnstFzLp2WO4GCI= +github.com/google/cel-go v0.21.0/go.mod h1:rHUlWCcBKgyEk+eV03RPdZUekPp6YcJwV0FxuUksYxc= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= @@ -63,6 +67,8 @@ github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXY github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng= github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/stoewer/go-strcase v1.2.0 h1:Z2iHWqGXH00XYgqDmNgQbIBxf3wrNq0F3feEy0ainaU= +github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= @@ -77,6 +83,8 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI= golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM= +golang.org/x/exp v0.0.0-20230515195305-f3d0a9c9a5cc h1:mCRnTeVUjcrhlRmO0VK8a6k6Rrf6TF9htwo2pJVSjIU= +golang.org/x/exp v0.0.0-20230515195305-f3d0a9c9a5cc/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= @@ -111,6 +119,10 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/genproto/googleapis/api v0.0.0-20230803162519-f966b187b2e5 h1:nIgk/EEq3/YlnmVVXVnm14rC2oxgs1o0ong4sD/rd44= +google.golang.org/genproto/googleapis/api v0.0.0-20230803162519-f966b187b2e5/go.mod h1:5DZzOUPCLYL3mNkQ0ms0F3EuUNZ7py1Bqeq6sxzI7/Q= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230803162519-f966b187b2e5 h1:eSaPbMR4T7WfH9FvABk36NBMacoTUKdWCvV0dx+KfOg= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230803162519-f966b187b2e5/go.mod h1:zBEcrKX2ZOcEkHWxBPAIvYUWOKKMIhYcmNiUIu2ji3I= google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=