From ff6d966af047f8c460d9fcf3802d122fe494b496 Mon Sep 17 00:00:00 2001 From: Raphael Koh Date: Mon, 21 Dec 2020 17:46:55 -0500 Subject: [PATCH 01/19] Create ECS Observer --- extension/observer/ecsobserver/config.go | 65 + extension/observer/ecsobserver/config_test.go | 77 + extension/observer/ecsobserver/extension.go | 63 + extension/observer/ecsobserver/factory.go | 61 + extension/observer/ecsobserver/go.mod | 12 + extension/observer/ecsobserver/go.sum | 1305 +++++++++++++++++ .../observer/ecsobserver/testdata/config.yaml | 35 + 7 files changed, 1618 insertions(+) create mode 100644 extension/observer/ecsobserver/config.go create mode 100644 extension/observer/ecsobserver/config_test.go create mode 100644 extension/observer/ecsobserver/extension.go create mode 100644 extension/observer/ecsobserver/factory.go create mode 100644 extension/observer/ecsobserver/go.mod create mode 100644 extension/observer/ecsobserver/go.sum create mode 100644 extension/observer/ecsobserver/testdata/config.yaml diff --git a/extension/observer/ecsobserver/config.go b/extension/observer/ecsobserver/config.go new file mode 100644 index 000000000000..5a6226bc8a61 --- /dev/null +++ b/extension/observer/ecsobserver/config.go @@ -0,0 +1,65 @@ +// Copyright The 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 ecsobserver + +import ( + "regexp" + "time" + + "go.opentelemetry.io/collector/config/configmodels" +) + +// DockerLabelConfig defines the configuration fo docker label-based service discovery. +type DockerLabelConfig struct { + JobNameLabel string `mapstructure:"job_name_label"` + PortLabel string `mapstructure:"port_label"` + MetricsPathLabel string `mapstructure:"metrics_path_label"` +} + +// TaskDefinitionConfig defines the configuration for task definition-based service discovery. +type TaskDefinitionConfig struct { + ContainerNamePattern string `mapstructure:"container_name_pattern"` + JobName string `mapstructure:"job_name"` + MetricsPath string `mapstructure:"metrics_path"` + MetricsPorts string `mapstructure:"metrics_ports"` + TaskDefArnPattern string `mapstructure:"task_definition_arn_pattern"` + + containerNameRegex *regexp.Regexp + taskDefRegex *regexp.Regexp + metricsPortList []int +} + +// Config defines the configuration for ECS observers. +type Config struct { + configmodels.ExtensionSettings `mapstructure:",squash"` + + // RefreshInterval determines how frequency at which the observer + // needs to poll for collecting information about new processes. + RefreshInterval time.Duration `mapstructure:"refresh_interval"` + // ClusterName is the target ECS cluster name for service discovery. + ClusterName string `mapstructure:"cluster_name"` + // ClusterRegion is the target ECS cluster's AWS region. + ClusterRegion string `mapstructure:"cluster_region"` + // ResultFile is the output path of the discovered targets YAML file (optional). + // This is mainly used in conjunction with the Prometheus receiver. + ResultFile string `mapstructure:"sd_result_file"` + // DockerLabel provides the configuration for docker label-based service discovery. + // If this is not provided, docker label-based service discovery is disabled. + DockerLabel *DockerLabelConfig `mapstructure:"docker_label"` + // TaskDefinitions is a list of task definition configurations for task + // definition-based service discovery (optional). If this is not provided, + // task definition-based SD is disabled. + TaskDefinitions []*TaskDefinitionConfig `mapstructure:"task_definitions"` +} diff --git a/extension/observer/ecsobserver/config_test.go b/extension/observer/ecsobserver/config_test.go new file mode 100644 index 000000000000..181c26cd5c83 --- /dev/null +++ b/extension/observer/ecsobserver/config_test.go @@ -0,0 +1,77 @@ +// Copyright The 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 ecsobserver + +import ( + "path" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.opentelemetry.io/collector/component/componenttest" + "go.opentelemetry.io/collector/config/configmodels" + "go.opentelemetry.io/collector/config/configtest" +) + +func TestLoadConfig(t *testing.T) { + factories, err := componenttest.ExampleComponents() + assert.NoError(t, err) + + factory := NewFactory() + factories.Extensions[typeStr] = factory + cfg, err := configtest.LoadConfigFile(t, path.Join(".", "testdata", "config.yaml"), factories) + + require.Nil(t, err) + require.NotNil(t, cfg) + + require.Len(t, cfg.Extensions, 2) + + ext0 := cfg.Extensions["ecs_observer"] + assert.Equal(t, factory.CreateDefaultConfig(), ext0) + + ext1 := cfg.Extensions["ecs_observer/1"] + assert.Equal(t, + &Config{ + ExtensionSettings: configmodels.ExtensionSettings{ + TypeVal: "ecs_observer", + NameVal: "ecs_observer/1", + }, + RefreshInterval: 15 * time.Second, + ClusterName: "EC2-Testing", + ClusterRegion: "us-west-2", + ResultFile: "/opt/aws/amazon-cloudwatch-agent/etc/ecs_sd_targets.yaml", + DockerLabel: &DockerLabelConfig{ + JobNameLabel: "ECS_PROMETHEUS_JOB_NAME", + MetricsPathLabel: "ECS_PROMETHEUS_METRICS_PATH", + PortLabel: "ECS_PROMETHEUS_EXPORTER_PORT_SUBSET_A", + }, + TaskDefinitions: []*TaskDefinitionConfig{ + &TaskDefinitionConfig{ + JobName: "task_def_1", + MetricsPath: "/stats/metrics", + MetricsPorts: "9901;9404;9406", + TaskDefArnPattern: ".*:task-definition/bugbash-java-fargate-awsvpc-task-def-only:[0-9]+", + }, + &TaskDefinitionConfig{ + ContainerNamePattern: "^bugbash-jar.*$", + MetricsPorts: "9902", + TaskDefArnPattern: ".*:task-definition/nginx:[0-9]+", + }, + }, + }, + ext1, + ) +} diff --git a/extension/observer/ecsobserver/extension.go b/extension/observer/ecsobserver/extension.go new file mode 100644 index 000000000000..4789eecb36bf --- /dev/null +++ b/extension/observer/ecsobserver/extension.go @@ -0,0 +1,63 @@ +// Copyright The 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 ecsobserver + +import ( + "context" + + "go.opentelemetry.io/collector/component" + "go.uber.org/zap" + + "github.com/open-telemetry/opentelemetry-collector-contrib/extension/observer" +) + +type ecsObserver struct { + observer.EndpointsWatcher +} + +type endpointsLister struct { + logger *zap.Logger + observerName string +} + +var _ component.ServiceExtension = (*ecsObserver)(nil) + +func newObserver(logger *zap.Logger, config *Config) (component.ServiceExtension, error) { + h := &ecsObserver{ + EndpointsWatcher: observer.EndpointsWatcher{ + RefreshInterval: config.RefreshInterval, + Endpointslister: endpointsLister{ + logger: logger, + observerName: config.Name(), + }, + }, + } + + return h, nil +} + +func (h *ecsObserver) Start(context.Context, component.Host) error { + return nil +} + +func (h *ecsObserver) Shutdown(context.Context) error { + h.StopListAndWatch() + return nil +} + +func (e endpointsLister) ListEndpoints() []observer.Endpoint { + // TODO: Implement this + return nil +} diff --git a/extension/observer/ecsobserver/factory.go b/extension/observer/ecsobserver/factory.go new file mode 100644 index 000000000000..e24e1e46673e --- /dev/null +++ b/extension/observer/ecsobserver/factory.go @@ -0,0 +1,61 @@ +// Copyright The 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 ecsobserver + +import ( + "context" + "time" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/config/configmodels" + "go.opentelemetry.io/collector/extension/extensionhelper" +) + +const ( + // The value of extension "type" in configuration. + typeStr configmodels.Type = "ecs_observer" + + defaultCollectionInterval = 10 +) + +// NewFactory creates a factory for HostObserver extension. +func NewFactory() component.ExtensionFactory { + return extensionhelper.NewFactory( + typeStr, + createDefaultConfig, + createExtension, + ) +} + +func createDefaultConfig() configmodels.Extension { + return &Config{ + ExtensionSettings: configmodels.ExtensionSettings{ + TypeVal: typeStr, + NameVal: string(typeStr), + }, + RefreshInterval: defaultCollectionInterval * time.Second, + ClusterName: "", + ClusterRegion: "", + } +} + +func createExtension( + _ context.Context, + params component.ExtensionCreateParams, + cfg configmodels.Extension, +) (component.ServiceExtension, error) { + config := cfg.(*Config) + return newObserver(params.Logger, config) +} diff --git a/extension/observer/ecsobserver/go.mod b/extension/observer/ecsobserver/go.mod new file mode 100644 index 000000000000..9ded80c55b06 --- /dev/null +++ b/extension/observer/ecsobserver/go.mod @@ -0,0 +1,12 @@ +module github.com/open-telemetry/opentelemetry-collector-contrib/extension/observer/ecsobserver + +go 1.14 + +require ( + github.com/open-telemetry/opentelemetry-collector-contrib/extension/observer v0.0.0-00010101000000-000000000000 + github.com/stretchr/testify v1.6.1 + go.opentelemetry.io/collector v0.17.0 + go.uber.org/zap v1.16.0 +) + +replace github.com/open-telemetry/opentelemetry-collector-contrib/extension/observer => ../ diff --git a/extension/observer/ecsobserver/go.sum b/extension/observer/ecsobserver/go.sum new file mode 100644 index 000000000000..74dae9c26777 --- /dev/null +++ b/extension/observer/ecsobserver/go.sum @@ -0,0 +1,1305 @@ +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.43.0/go.mod h1:BOSR3VbTLkk6FDC/TcffxP4NF/FFBGA5ku+jvKOP7pg= +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.51.0/go.mod h1:hWtGJ6gnXH+KgDv+V0zFGDvpi07n3z8ZNj3T1RW0Gcw= +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/bigtable v1.2.0/go.mod h1:JcVAOl45lrTmQfLj7T6TxyMzIN/3FGGcFm+2xVAli2o= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= +cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= +cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= +cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= +cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= +cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= +cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= +collectd.org v0.3.0/go.mod h1:A/8DzQBkF6abtvrT2j/AU/4tiBgJWYyh0y/oB/4MlWE= +contrib.go.opencensus.io/exporter/prometheus v0.2.0/go.mod h1:TYmVAyE8Tn1lyPcltF5IYYfWp2KHu7lQGIZnj8iZMys= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8= +github.com/Azure/azure-sdk-for-go v46.4.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= +github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= +github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= +github.com/Azure/go-autorest/autorest v0.9.0/go.mod h1:xyHB1BMZT0cuDHU7I0+g046+BFDTQ8rEZB0s4Yfa6bI= +github.com/Azure/go-autorest/autorest v0.9.6/go.mod h1:/FALq9T/kS7b5J5qsQ+RSTUdAmGFqi0vUdVNNx8q630= +github.com/Azure/go-autorest/autorest v0.11.10/go.mod h1:eipySxLmqSyC5s5k1CLupqet0PSENBEDP93LQ9a8QYw= +github.com/Azure/go-autorest/autorest/adal v0.5.0/go.mod h1:8Z9fGy2MpX0PvDjB1pEgQTmVqjGhiHBW7RJJEciWzS0= +github.com/Azure/go-autorest/autorest/adal v0.8.2/go.mod h1:ZjhuQClTqx435SRJ2iMlOxPYt3d2C/T/7TiQCVZSn3Q= +github.com/Azure/go-autorest/autorest/adal v0.9.5/go.mod h1:B7KF7jKIeC9Mct5spmyCB/A8CG/sEz1vwIRGv/bbw7A= +github.com/Azure/go-autorest/autorest/date v0.1.0/go.mod h1:plvfp3oPSKwf2DNjlBjWF/7vwR+cUD/ELuzDCXwHUVA= +github.com/Azure/go-autorest/autorest/date v0.2.0/go.mod h1:vcORJHLJEh643/Ioh9+vPmf1Ij9AEBM5FuBIXLmIy0g= +github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74= +github.com/Azure/go-autorest/autorest/mocks v0.1.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= +github.com/Azure/go-autorest/autorest/mocks v0.2.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= +github.com/Azure/go-autorest/autorest/mocks v0.3.0/go.mod h1:a8FDP3DYzQ4RYfVAxAN3SVSiiO77gL2j2ronKKP0syM= +github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= +github.com/Azure/go-autorest/autorest/to v0.3.0/go.mod h1:MgwOyqaIuKdG4TL/2ywSsIWKAfJfgHDo8ObuUk3t5sA= +github.com/Azure/go-autorest/autorest/validation v0.2.0/go.mod h1:3EEqHnBxQGHXRYq3HT1WyXAvT7LLY3tl70hw6tQIbjI= +github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc= +github.com/Azure/go-autorest/logger v0.2.0/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= +github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk= +github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= +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/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= +github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= +github.com/DataDog/zstd v1.3.6-0.20190409195224-796139022798/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo= +github.com/DataDog/zstd v1.4.4/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo= +github.com/HdrHistogram/hdrhistogram-go v0.9.0/go.mod h1:nxrse8/Tzg2tg3DZcZjm6qEclQKK70g0KxO61gFFZD4= +github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= +github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= +github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/OneOfOne/xxhash v1.2.5/go.mod h1:eZbhyaAYD41SGSSsnmcpxVoRiQ/MPUTjUdIIOT9Um7Q= +github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= +github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= +github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= +github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= +github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= +github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= +github.com/Shopify/sarama v1.22.2-0.20190604114437-cd910a683f9f/go.mod h1:XLH1GYJnLVE0XCr6KdJGVJRTwY30moWNJ4sERjXX6fs= +github.com/Shopify/sarama v1.27.2/go.mod h1:g5s5osgELxgM+Md9Qni9rzo7Rbt+vvFQI4bt/Mc93II= +github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= +github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= +github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= +github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c= +github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM= +github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw= +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/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= +github.com/antihax/optional v0.0.0-20180407024304-ca021399b1a6/go.mod h1:V8iCPQYkqmusNa815XgQio277wI47sdRh1dUOLdyC6Q= +github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +github.com/antonmedv/expr v1.8.9/go.mod h1:5qsM3oLGDND7sDmQGDXHkYfkjYMUX14qsgqmHhwGEk8= +github.com/apache/arrow/go/arrow v0.0.0-20191024131854-af6fa24be0db/go.mod h1:VTxUBvSJ3s3eHAg65PNgrsn5BtqCRPdmyXh6rAfdxN0= +github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= +github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= +github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= +github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= +github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= +github.com/armon/go-metrics v0.3.3/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb4QAOwNTFc= +github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A= +github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= +github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= +github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg= +github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU= +github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/aws/aws-sdk-go v1.35.5/go.mod h1:tlPOdRjfxPBpNIwqDj61rmsnA85v9jc0Ps9+muhnW+k= +github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932/go.mod h1:NOuUCSz6Q9T7+igc/hlvDOUdtWKryOrtFyIVABv/p7k= +github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= +github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= +github.com/bmizerany/pat v0.0.0-20170815010413-6226ea591a40/go.mod h1:8rLXio+WjiTceGBHIoTvn60HIbs7Hm7bcHjyrSqYB9c= +github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps= +github.com/bsm/sarama-cluster v2.1.13+incompatible/go.mod h1:r7ao+4tTNXvWm+VRpRJchr2kQhqxgmAp2iEX5W96gMM= +github.com/c-bata/go-prompt v0.2.2/go.mod h1:VzqtzE2ksDBcdln8G7mk2RX9QyGjH+OVqOCSiVIqS34= +github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= +github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= +github.com/cenkalti/backoff/v4 v4.0.2/go.mod h1:eEew/i+1Q6OrCDZh3WiXYv3+nJwBASZ8Bog/87DQnVg= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/census-instrumentation/opencensus-proto v0.3.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/cespare/xxhash/v2 v2.1.0/go.mod h1:dgIUBU3pDso/gPgZ1osOZ0iQf77oPR28Tjxl5dIMyVM= +github.com/cespare/xxhash/v2 v2.1.1/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/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag= +github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I= +github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= +github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= +github.com/containerd/containerd v1.3.4/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= +github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= +github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= +github.com/coreos/go-oidc v2.2.1+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc= +github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= +github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= +github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= +github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/crossdock/crossdock-go v0.0.0-20160816171116-049aabb0122b/go.mod h1:v9FBN7gdVTpiD/+LZ7Po0UKvROyT87uLVxTHVky/dlQ= +github.com/dave/jennifer v1.2.0/go.mod h1:fIb+770HOpJ2fmN9EPPKOqm1vMGhB+TwXKMZhrIygKg= +github.com/davecgh/go-spew v0.0.0-20161028175848-04cdfd42973b/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +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/dgraph-io/badger v1.6.2/go.mod h1:JW2yswe3V058sS0kZ2h/AXeDSqFjxnZcRrVH//y2UQE= +github.com/dgraph-io/ristretto v0.0.2/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E= +github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/dgryski/go-bitstream v0.0.0-20180413035011-3522498ce2c8/go.mod h1:VMaSuZ+SZcx/wljOQKvp5srsbCiKDEb6K2wC4+PiBmQ= +github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= +github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= +github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= +github.com/dgryski/go-sip13 v0.0.0-20200911182023-62edffca9245/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= +github.com/digitalocean/godo v1.46.0/go.mod h1:p7dOjjtSBqCTUksqtA5Fd3uaKs9kyTq2xcz76ulEJRU= +github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= +github.com/docker/docker v17.12.0-ce-rc1.0.20200706150819-a40b877fbb9e+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= +github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM= +github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= +github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= +github.com/eapache/go-resiliency v1.2.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= +github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= +github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= +github.com/eclipse/paho.mqtt.golang v1.2.0/go.mod h1:H9keYFcgq3Qr5OUJm/JZI/i6U7joQ8SYLhZwfeOo6Ts= +github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= +github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= +github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= +github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= +github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= +github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= +github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= +github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4= +github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20= +github.com/frankban/quicktest v1.7.3/go.mod h1:V1d2J5pfxYH6EjBAgSK7YNXcXlTWxUHdE1sVDXkjnig= +github.com/frankban/quicktest v1.10.2/go.mod h1:K+q6oSqb0W0Ininfk863uOk1lMy69l/P6txr3mVT54s= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg= +github.com/gdamore/tcell v1.3.0/go.mod h1:Hjvr+Ofd+gLglo7RYKxxnzCBmev3BzsS67MebKS4zMM= +github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= +github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= +github.com/glycerine/go-unsnap-stream v0.0.0-20180323001048-9f0cb55181dd/go.mod h1:/20jfyN9Y5QPEAprSgKAUr+glWDY39ZiUEAYOEv5dsE= +github.com/glycerine/goconvey v0.0.0-20190410193231-58a59202ab31/go.mod h1:Ogl1Tioa0aV7gstGFO7KhffUsb9M4ydbEbbxpcEDc24= +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/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o= +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-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= +github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= +github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM= +github.com/go-openapi/analysis v0.0.0-20180825180245-b006789cd277/go.mod h1:k70tL6pCuVxPJOHXQ+wIac1FUrvNkHolPie/cLEU6hI= +github.com/go-openapi/analysis v0.17.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik= +github.com/go-openapi/analysis v0.18.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik= +github.com/go-openapi/analysis v0.19.2/go.mod h1:3P1osvZa9jKjb8ed2TPng3f0i/UY9snX6gxi44djMjk= +github.com/go-openapi/analysis v0.19.4/go.mod h1:3P1osvZa9jKjb8ed2TPng3f0i/UY9snX6gxi44djMjk= +github.com/go-openapi/analysis v0.19.5/go.mod h1:hkEAkxagaIvIP7VTn8ygJNkd4kAYON2rCu0v0ObL0AU= +github.com/go-openapi/analysis v0.19.10/go.mod h1:qmhS3VNFxBlquFJ0RGoDtylO9y4pgTAUNE9AEEMdlJQ= +github.com/go-openapi/errors v0.17.0/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0= +github.com/go-openapi/errors v0.18.0/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0= +github.com/go-openapi/errors v0.19.2/go.mod h1:qX0BLWsyaKfvhluLejVpVNwNRdXZhEbTA4kxxpKBC94= +github.com/go-openapi/errors v0.19.3/go.mod h1:qX0BLWsyaKfvhluLejVpVNwNRdXZhEbTA4kxxpKBC94= +github.com/go-openapi/errors v0.19.4/go.mod h1:qX0BLWsyaKfvhluLejVpVNwNRdXZhEbTA4kxxpKBC94= +github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0= +github.com/go-openapi/jsonpointer v0.17.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M= +github.com/go-openapi/jsonpointer v0.18.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M= +github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg= +github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg= +github.com/go-openapi/jsonreference v0.17.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I= +github.com/go-openapi/jsonreference v0.18.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I= +github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc= +github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8= +github.com/go-openapi/loads v0.17.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU= +github.com/go-openapi/loads v0.18.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU= +github.com/go-openapi/loads v0.19.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU= +github.com/go-openapi/loads v0.19.2/go.mod h1:QAskZPMX5V0C2gvfkGZzJlINuP7Hx/4+ix5jWFxsNPs= +github.com/go-openapi/loads v0.19.3/go.mod h1:YVfqhUCdahYwR3f3iiwQLhicVRvLlU/WO5WPaZvcvSI= +github.com/go-openapi/loads v0.19.4/go.mod h1:zZVHonKd8DXyxyw4yfnVjPzBjIQcLt0CCsn0N0ZrQsk= +github.com/go-openapi/loads v0.19.5/go.mod h1:dswLCAdonkRufe/gSUC3gN8nTSaB9uaS2es0x5/IbjY= +github.com/go-openapi/runtime v0.0.0-20180920151709-4f900dc2ade9/go.mod h1:6v9a6LTXWQCdL8k1AO3cvqx5OtZY/Y9wKTgaoP6YRfA= +github.com/go-openapi/runtime v0.19.0/go.mod h1:OwNfisksmmaZse4+gpV3Ne9AyMOlP1lt4sK4FXt0O64= +github.com/go-openapi/runtime v0.19.4/go.mod h1:X277bwSUBxVlCYR3r7xgZZGKVvBd/29gLDlFGtJ8NL4= +github.com/go-openapi/runtime v0.19.15/go.mod h1:dhGWCTKRXlAfGnQG0ONViOZpjfg0m2gUt9nTQPQZuoo= +github.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc= +github.com/go-openapi/spec v0.17.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI= +github.com/go-openapi/spec v0.18.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI= +github.com/go-openapi/spec v0.19.2/go.mod h1:sCxk3jxKgioEJikev4fgkNmwS+3kuYdJtcsZsD5zxMY= +github.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo= +github.com/go-openapi/spec v0.19.6/go.mod h1:Hm2Jr4jv8G1ciIAo+frC/Ft+rR2kQDh8JHKHb3gWUSk= +github.com/go-openapi/spec v0.19.7/go.mod h1:Hm2Jr4jv8G1ciIAo+frC/Ft+rR2kQDh8JHKHb3gWUSk= +github.com/go-openapi/spec v0.19.8/go.mod h1:Hm2Jr4jv8G1ciIAo+frC/Ft+rR2kQDh8JHKHb3gWUSk= +github.com/go-openapi/strfmt v0.17.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU= +github.com/go-openapi/strfmt v0.18.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU= +github.com/go-openapi/strfmt v0.19.0/go.mod h1:+uW+93UVvGGq2qGaZxdDeJqSAqBqBdl+ZPMF/cC8nDY= +github.com/go-openapi/strfmt v0.19.2/go.mod h1:0yX7dbo8mKIvc3XSKp7MNfxw4JytCfCD6+bY1AVL9LU= +github.com/go-openapi/strfmt v0.19.3/go.mod h1:0yX7dbo8mKIvc3XSKp7MNfxw4JytCfCD6+bY1AVL9LU= +github.com/go-openapi/strfmt v0.19.4/go.mod h1:eftuHTlB/dI8Uq8JJOyRlieZf+WkkxUuk0dgdHXr2Qk= +github.com/go-openapi/strfmt v0.19.5/go.mod h1:eftuHTlB/dI8Uq8JJOyRlieZf+WkkxUuk0dgdHXr2Qk= +github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I= +github.com/go-openapi/swag v0.17.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg= +github.com/go-openapi/swag v0.18.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg= +github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-openapi/swag v0.19.7/go.mod h1:ao+8BpOPyKdpQz3AOJfbeEVpLmWAvlT1IfTe5McPyhY= +github.com/go-openapi/swag v0.19.9/go.mod h1:ao+8BpOPyKdpQz3AOJfbeEVpLmWAvlT1IfTe5McPyhY= +github.com/go-openapi/validate v0.18.0/go.mod h1:Uh4HdOzKt19xGIGm1qHf/ofbX1YQ4Y+MYsct2VUrAJ4= +github.com/go-openapi/validate v0.19.2/go.mod h1:1tRCw7m3jtI8eNWEEliiAqUIcBztB2KDnRCRMUi7GTA= +github.com/go-openapi/validate v0.19.3/go.mod h1:90Vh6jjkTn+OT1Eefm0ZixWNFjhtOH7vS9k0lo6zwJo= +github.com/go-openapi/validate v0.19.8/go.mod h1:8DJv2CVJQ6kGNpFW6eV9N3JviE1C85nY1c2z52x1Gk4= +github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= +github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/gobuffalo/attrs v0.0.0-20190224210810-a9411de4debd/go.mod h1:4duuawTqi2wkkpB4ePgWMaai6/Kc6WEz83bhFwpHzj0= +github.com/gobuffalo/depgen v0.0.0-20190329151759-d478694a28d3/go.mod h1:3STtPUQYuzV0gBVOY3vy6CfMm/ljR4pABfrTeHNLHUY= +github.com/gobuffalo/depgen v0.1.0/go.mod h1:+ifsuy7fhi15RWncXQQKjWS9JPkdah5sZvtHc2RXGlg= +github.com/gobuffalo/envy v1.6.15/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI= +github.com/gobuffalo/envy v1.7.0/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI= +github.com/gobuffalo/flect v0.1.0/go.mod h1:d2ehjJqGOH/Kjqcoz+F7jHTBbmDb38yXA598Hb50EGs= +github.com/gobuffalo/flect v0.1.1/go.mod h1:8JCgGVbRjJhVgD6399mQr4fx5rRfGKVzFjbj6RE/9UI= +github.com/gobuffalo/flect v0.1.3/go.mod h1:8JCgGVbRjJhVgD6399mQr4fx5rRfGKVzFjbj6RE/9UI= +github.com/gobuffalo/genny v0.0.0-20190329151137-27723ad26ef9/go.mod h1:rWs4Z12d1Zbf19rlsn0nurr75KqhYp52EAGGxTbBhNk= +github.com/gobuffalo/genny v0.0.0-20190403191548-3ca520ef0d9e/go.mod h1:80lIj3kVJWwOrXWWMRzzdhW3DsrdjILVil/SFKBzF28= +github.com/gobuffalo/genny v0.1.0/go.mod h1:XidbUqzak3lHdS//TPu2OgiFB+51Ur5f7CSnXZ/JDvo= +github.com/gobuffalo/genny v0.1.1/go.mod h1:5TExbEyY48pfunL4QSXxlDOmdsD44RRq4mVZ0Ex28Xk= +github.com/gobuffalo/gitgen v0.0.0-20190315122116-cc086187d211/go.mod h1:vEHJk/E9DmhejeLeNt7UVvlSGv3ziL+djtTr3yyzcOw= +github.com/gobuffalo/gogen v0.0.0-20190315121717-8f38393713f5/go.mod h1:V9QVDIxsgKNZs6L2IYiGR8datgMhB577vzTDqypH360= +github.com/gobuffalo/gogen v0.1.0/go.mod h1:8NTelM5qd8RZ15VjQTFkAW6qOMx5wBbW4dSCS3BY8gg= +github.com/gobuffalo/gogen v0.1.1/go.mod h1:y8iBtmHmGc4qa3urIyo1shvOD8JftTtfcKi+71xfDNE= +github.com/gobuffalo/logger v0.0.0-20190315122211-86e12af44bc2/go.mod h1:QdxcLw541hSGtBnhUc4gaNIXRjiDppFGaDqzbrBd3v8= +github.com/gobuffalo/mapi v1.0.1/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc= +github.com/gobuffalo/mapi v1.0.2/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc= +github.com/gobuffalo/packd v0.0.0-20190315124812-a385830c7fc0/go.mod h1:M2Juc+hhDXf/PnmBANFCqx4DM3wRbgDvnVWeG2RIxq4= +github.com/gobuffalo/packd v0.1.0/go.mod h1:M2Juc+hhDXf/PnmBANFCqx4DM3wRbgDvnVWeG2RIxq4= +github.com/gobuffalo/packr/v2 v2.0.9/go.mod h1:emmyGweYTm6Kdper+iywB6YK5YzuKchGtJQZ0Odn4pQ= +github.com/gobuffalo/packr/v2 v2.2.0/go.mod h1:CaAwI0GPIAv+5wKLtv8Afwl+Cm78K/I/VCm/3ptBN+0= +github.com/gobuffalo/syncx v0.0.0-20190224160051-33c29581e754/go.mod h1:HhnNqWY95UYwwW3uSASeV7vtgYkT2t16hJgV3AEPUpw= +github.com/gocql/gocql v0.0.0-20200228163523-cd4b606dd2fb/go.mod h1:DL0ekTmBSTdlNF25Orwt/JMzqIq3EJ4MVa/J/uK64OY= +github.com/gofrs/uuid v3.3.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= +github.com/gogo/googleapis v1.3.0/go.mod h1:d+q1s/xVJxZGKWwC/6UfPIF33J+G1Tq4GYv9Y+Tg/EU= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= +github.com/gogo/protobuf v1.3.0/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= +github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls= +github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= +github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= +github.com/golang/geo v0.0.0-20190916061304-5b978397cfec/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191027212112-611e8accdfc9/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 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/snappy v0.0.0-20170215233205-553a64147049/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.2/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/flatbuffers v1.11.0/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= +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.2/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-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20201007051231-1066cbb265c7/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/googleapis/gnostic v0.4.1/go.mod h1:LRhVm6pbyptWbWbuZ38d1eyptfvIytN3ir6b65WBswg= +github.com/gophercloud/gophercloud v0.13.0/go.mod h1:VX0Ibx85B60B5XOrZr6kaNwrmPUzcmMpwxvQ1WQIIWM= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= +github.com/gorilla/handlers v1.4.2/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ= +github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= +github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= +github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= +github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= +github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= +github.com/grpc-ecosystem/go-grpc-middleware v1.2.0/go.mod h1:mJzapYve32yjrKlk9GbyCZHuPgZsrbyIbyKhSzOpg6s= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= +github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/grpc-ecosystem/grpc-gateway v1.14.5/go.mod h1:UJ0EZAp832vCd54Wev9N1BMKEyvcZ5+IM0AwDrnlkEc= +github.com/grpc-ecosystem/grpc-gateway v1.15.0/go.mod h1:vO11I9oWA+KsxmfFQPhLnnIb1VDE24M+pdxZFiuZcA8= +github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo= +github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645/go.mod h1:6iZfnjpejD4L/4DwD7NryNaJyCQdzwWwH2MWhCA90Kw= +github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed/go.mod h1:tMWxXQ9wFIaZeTI9F+hmhFiGpFmhOHzyShyFUhRm0H4= +github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= +github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE= +github.com/hashicorp/consul/api v1.7.0/go.mod h1:1NSuaUUkFaJzMasbfq/11wKYWSR67Xn6r2DXKhuDNFg= +github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= +github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= +github.com/hashicorp/consul/sdk v0.6.0/go.mod h1:fY08Y9z5SvJqevyZNy6WWPXiG3KwBPAvlcdx16zZ0fM= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-hclog v0.0.0-20180709165350-ff2cf002a8dd/go.mod h1:9bjs9uLqI8l75knNv3lV1kA55veR+WUPSiKIWcQHudI= +github.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= +github.com/hashicorp/go-hclog v0.12.2/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= +github.com/hashicorp/go-hclog v0.14.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= +github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-immutable-radix v1.2.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= +github.com/hashicorp/go-msgpack v0.5.5/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= +github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA= +github.com/hashicorp/go-plugin v1.0.1/go.mod h1:++UyYGoz3o5w9ZzAdZxtQKrWWP+iqPBn3cQptSMzBuY= +github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= +github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= +github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= +github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= +github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A= +github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= +github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= +github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= +github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= +github.com/hashicorp/mdns v1.0.1/go.mod h1:4gW7WsVCke5TE7EPeYliwHlRUyBtfCwuFwuMg2DmyNY= +github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= +github.com/hashicorp/memberlist v0.2.2/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE= +github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= +github.com/hashicorp/serf v0.9.3/go.mod h1:UWDWwZeL5cuWDJdl0C6wrvrUwEqtQ4ZKBKKENpqIUyk= +github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= +github.com/hashicorp/yamux v0.0.0-20190923154419-df201c70410d/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= +github.com/hetznercloud/hcloud-go v1.22.0/go.mod h1:xng8lbDUg+xM1dgc0yGHX5EeqbwIq7UYlMWMTx3SQVg= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg= +github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/influxdata/flux v0.65.1/go.mod h1:J754/zds0vvpfwuq7Gc2wRdVwEodfpCFM7mYlOw2LqY= +github.com/influxdata/influxdb v1.8.3/go.mod h1:JugdFhsvvI8gadxOI6noqNeeBHvWNTbfYGtiAn+2jhI= +github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= +github.com/influxdata/influxql v1.1.1-0.20200828144457-65d3ef77d385/go.mod h1:gHp9y86a/pxhjJ+zMjNXiQAA197Xk9wLxaz+fGG+kWk= +github.com/influxdata/line-protocol v0.0.0-20180522152040-32c6aa80de5e/go.mod h1:4kt73NQhadE3daL3WhR5EJ/J2ocX0PZzwxQ0gXJ7oFE= +github.com/influxdata/promql/v2 v2.12.0/go.mod h1:fxOPu+DY0bqCTCECchSRtWfc+0X19ybifQhZoQNF5D8= +github.com/influxdata/roaring v0.4.13-0.20180809181101-fc520f41fab6/go.mod h1:bSgUQ7q5ZLSO+bKBGqJiCBGAl+9DxyW63zLTujjUlOE= +github.com/influxdata/tdigest v0.0.0-20181121200506-bf2b5ad3c0a9/go.mod h1:Js0mqiSBE6Ffsg94weZZ2c+v/ciT8QRHFOap7EKDrR0= +github.com/influxdata/usage-client v0.0.0-20160829180054-6d3895376368/go.mod h1:Wbbw6tYNvwa5dlB6304Sd+82Z3f7PmVZHVKU637d4po= +github.com/jaegertracing/jaeger v1.21.0/go.mod h1:PCTGGFohQBPQMR4j333V5lt6If7tj8aWJ+pQNgvZ+wU= +github.com/jcmturner/gofork v0.0.0-20190328161633-dc7c13fece03/go.mod h1:MK8+TM0La+2rjBD4jE12Kj1pCCxK7d2LK/UM3ncEo0o= +github.com/jcmturner/gofork v1.0.0/go.mod h1:MK8+TM0La+2rjBD4jE12Kj1pCCxK7d2LK/UM3ncEo0o= +github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= +github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= +github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= +github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= +github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= +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.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +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/jsternberg/zap-logfmt v1.0.0/go.mod h1:uvPs/4X51zdkcm5jXl5SYoN+4RK21K8mysFmDaM/h+o= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= +github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= +github.com/jwilder/encoding v0.0.0-20170811194829-b4e1701a28ef/go.mod h1:Ct9fl0F6iIOGgxJ5npU/IUOhOhqlVrGjyIZc8/MagT0= +github.com/karrick/godirwalk v1.8.0/go.mod h1:H5KPZjojv4lE+QYImBI8xVtrBRgYrIVsaRPx4tDPEn4= +github.com/karrick/godirwalk v1.10.3/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA= +github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= +github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.4.0/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= +github.com/klauspost/compress v1.9.5/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= +github.com/klauspost/compress v1.11.0/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= +github.com/klauspost/cpuid v0.0.0-20170728055534-ae7887de9fa5/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= +github.com/klauspost/crc32 v0.0.0-20161016154125-cb6bfca970f6/go.mod h1:+ZoRqAPRLkC4NPOvfYeR5KNOrY6TD+/sAC3HXPZgDYg= +github.com/klauspost/pgzip v1.0.2-0.20170402124221-0bf5dcad4ada/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= +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.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA= +github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/leoluk/perflib_exporter v0.1.0/go.mod h1:rpV0lYj7lemdTm31t7zpCqYqPnw7xs86f+BaaNBVYFM= +github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM= +github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= +github.com/lucasb-eyer/go-colorful v1.0.2/go.mod h1:0MS4r+7BZKSJ5mw4/S5MPN+qHFF1fYclkSPilDOKW0s= +github.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= +github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= +github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4= +github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs= +github.com/mailru/easyjson v0.7.1/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs= +github.com/markbates/oncer v0.0.0-20181203154359-bf2de49a0be2/go.mod h1:Ld9puTsIW75CHf65OeIOkyKbteujpZVXDpWK6YGZbxE= +github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0= +github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= +github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= +github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= +github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= +github.com/mattn/go-runewidth v0.0.8/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= +github.com/mattn/go-tty v0.0.0-20180907095812-13ff1204f104/go.mod h1:XPvLUNfbS4fJH25nqRHfWLMa1ONC8Amw+mIA639KxkE= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= +github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= +github.com/miekg/dns v1.1.31/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM= +github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= +github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI= +github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= +github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= +github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= +github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= +github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= +github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.2.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/mapstructure v1.3.2 h1:mRS76wmkOn3KkKAyXDu42V+6ebnXWIztFSYGN7GeoRg= +github.com/mitchellh/mapstructure v1.3.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mjibson/esc v0.2.0/go.mod h1:9Hw9gxxfHulMF5OJKCyhYD7PzlSdhzXyaGEBRPH1OPs= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= +github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= +github.com/mozilla/tls-observatory v0.0.0-20190404164649-a3c1b6cfecfd/go.mod h1:SrKMQvPiws7F7iqYp8/TX+IhxCYhzr6N/1yb8cwHsGk= +github.com/mschoch/smat v0.0.0-20160514031455-90eadee771ae/go.mod h1:qAyveg+e4CE+eKJXWVjKXM4ck2QobLqTDytGJbLLhJg= +github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= +github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg= +github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU= +github.com/nats-io/nats-server/v2 v2.1.2/go.mod h1:Afk+wRZqkMQs/p45uXdrVLuab3gwv3Z8C4HTBu8GD/k= +github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w= +github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= +github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= +github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= +github.com/nbutton23/zxcvbn-go v0.0.0-20180912185939-ae427f1e4c1d/go.mod h1:o96djdrsSGy3AWPyBgZMAGfxZNfgntdJG+11KU4QvbU= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs= +github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= +github.com/oklog/run v1.1.0/go.mod h1:sVPdnTZT1zYwAJeCMu2Th4T21pA3FPOQRfWjQlk7DVU= +github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= +github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= +github.com/olivere/elastic v6.2.27+incompatible/go.mod h1:J+q1zQJTgAz9woqsbVRqGeB5G1iqDKVBWLNSYW8yfJ8= +github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.0/go.mod h1:oUhWkIvk5aDxtKvDDuw8gItl8pKl42LzjC9KZE0HfGg= +github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.14.1/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= +github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= +github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.9.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA= +github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/onsi/gomega v1.10.2/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= +github.com/open-telemetry/opentelemetry-collector-contrib v0.17.0 h1:VEV/4zJlcpTswsiuMdiBcxMOLcKSWfvSsHVSkrLrvUU= +github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= +github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= +github.com/opentracing-contrib/go-grpc v0.0.0-20191001143057-db30781987df/go.mod h1:DYR5Eij8rJl8h7gblRrOZ8g0kW1umSpKqYIBTgeDtLo= +github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis= +github.com/opentracing-contrib/go-stdlib v0.0.0-20190519235532-cf7a6c988dc9/go.mod h1:PLldrQSroqzH70Xl+1DQcGnefIbqsKR7UDaiux3zV+w= +github.com/opentracing-contrib/go-stdlib v1.0.0/go.mod h1:qtI1ogk+2JhVPIXVc6q+NHziSmy2W5GbdQZFUHADCBU= +github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74= +github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/opentracing/opentracing-go v1.0.3-0.20180606204148-bd9c31933947/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= +github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5/go.mod h1:/wsWhb9smxSfWAKL3wpBW7V8scJMt8N8gnaMCS9E/cA= +github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw= +github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= +github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= +github.com/openzipkin/zipkin-go v0.2.5/go.mod h1:KpXfKdgRDnnhsxw4pNIH9Md5lyFqKUa4YDFlwRYAMyE= +github.com/orijtech/prometheus-go-metrics-exporter v0.0.6/go.mod h1:BiTx/ugZex8LheBk3j53tktWaRdFjV5FCfT2o0P7msE= +github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM= +github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/paulbellamy/ratecounter v0.2.0/go.mod h1:Hfx1hDpSGoqxkVVpBi/IlYD7kChlfo5C6hzIHwPqfFE= +github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= +github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/pelletier/go-toml v1.4.0/go.mod h1:PN7xzY2wHTK0K9p34ErDQMlFxa51Fk0OUruD3k1mMwo= +github.com/pelletier/go-toml v1.6.0/go.mod h1:5N711Q9dKgbdkxHL+MEfF31hpT7l0S0s/t2kKREewys= +github.com/pelletier/go-toml v1.8.0 h1:Keo9qb7iRJs2voHvunFtuuYFsbWeOBh8/P9v/kVMFtw= +github.com/pelletier/go-toml v1.8.0/go.mod h1:D6yutnOGMveHEPV7VQOuvI/gXY61bv+9bAOTRnLElKs= +github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac= +github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= +github.com/peterh/liner v1.0.1-0.20180619022028-8c1271fcf47f/go.mod h1:xIteQHvHuaLYG9IFj6mSxM0fCKrs34IrEQUhOYuGPHc= +github.com/philhofer/fwd v1.0.0/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU= +github.com/philhofer/fwd v1.1.1/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU= +github.com/pierrec/lz4 v0.0.0-20190327172049-315a67e90e41/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc= +github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc= +github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= +github.com/pierrec/lz4 v2.4.1+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= +github.com/pierrec/lz4 v2.5.2+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= +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/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA= +github.com/pkg/term v0.0.0-20180730021639-bffc007b7fd5/go.mod h1:eCbImbZ95eXtAUIbLAuAVnBnwf83mjf6QIVH8SHYwqQ= +github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= +github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s= +github.com/pquerna/cachecontrol v0.0.0-20200819021114-67c6ae64274f/go.mod h1:hoLfEwdY11HjRfKFH6KqnPsfxlo3BP6bJehpDv8t6sQ= +github.com/prometheus/alertmanager v0.21.0/go.mod h1:h7tJ81NA0VLWvWEayi1QltevFkLF3KxmC/malTcT8Go= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM= +github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs= +github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= +github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= +github.com/prometheus/client_golang v1.2.1/go.mod h1:XMU6Z2MjaRKVu/dC1qupJI9SiNkDYzz3xecMgSW/F+U= +github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og= +github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= +github.com/prometheus/client_golang v1.4.1/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= +github.com/prometheus/client_golang v1.5.1/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= +github.com/prometheus/client_golang v1.6.0/go.mod h1:ZLOG9ck3JLRdB5MgO8f+lLTe83AXG6ro35rLTxvnIl4= +github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= +github.com/prometheus/client_golang v1.8.0/go.mod h1:O9VU6huf47PktckDQfMTX0Y8tY0/7TSWwj+ITvv0TnM= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/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.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc= +github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA= +github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4= +github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= +github.com/prometheus/common v0.14.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16Clt/msog/s= +github.com/prometheus/common v0.15.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16Clt/msog/s= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.5/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= +github.com/prometheus/procfs v0.0.6/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= +github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= +github.com/prometheus/procfs v0.0.11/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= +github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= +github.com/prometheus/procfs v0.2.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= +github.com/prometheus/prometheus v1.8.2-0.20201105135750-00f16d1ac3a4/go.mod h1:XYjkJiog7fyQu3puQNivZPI2pNq1C/775EIoHfDvuvY= +github.com/prometheus/statsd_exporter v0.15.0/go.mod h1:Dv8HnkoLQkeEjkIE4/2ndAA7WL1zHKK7WMqFQqu72rw= +github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= +github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= +github.com/rcrowley/go-metrics v0.0.0-20190826022208-cac0b30c2563/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= +github.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= +github.com/retailnext/hllpp v1.0.1-0.20180308014038-101a6d2f8b52/go.mod h1:RDpi1RftBQPUCDRw6SmxeaREsAaRKnOclghuzp/WRzc= +github.com/rivo/tview v0.0.0-20200219210816-cd38d7432498/go.mod h1:6lkG1x+13OShEf0EaOCaTQYyB7d5nSbb181KtjlS+84= +github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= +github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= +github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.2.2/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= +github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= +github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E= +github.com/samuel/go-zookeeper v0.0.0-20200724154423-2164a8ac840e/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E= +github.com/sanity-io/litter v1.2.0/go.mod h1:JF6pZUFgu2Q0sBZ+HSV35P8TVPI1TTzEwyu9FXAw2W4= +github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= +github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= +github.com/securego/gosec v0.0.0-20200203094520-d13bb6d2420c/go.mod h1:gp0gaHj0WlmPh9BdsTmo1aq6C27yIPWdxCKGFGdVKBE= +github.com/segmentio/kafka-go v0.1.0/go.mod h1:X6itGqS9L4jDletMsxZ7Dz+JFWxM6JHfPOCvTvk+EJo= +github.com/segmentio/kafka-go v0.2.0/go.mod h1:X6itGqS9L4jDletMsxZ7Dz+JFWxM6JHfPOCvTvk+EJo= +github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= +github.com/shirou/gopsutil v3.20.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= +github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/shurcooL/vfsgen v0.0.0-20181202132449-6a9ea43bcacd/go.mod h1:TrYk7fJVaAttu97ZZKrO9UbRa8izdowaMIZcxYMbVaw= +github.com/shurcooL/vfsgen v0.0.0-20200824052919-0d455de96546/go.mod h1:TrYk7fJVaAttu97ZZKrO9UbRa8izdowaMIZcxYMbVaw= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= +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/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= +github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= +github.com/spf13/afero v1.2.2 h1:5jhuqJyZCZf2JRofRvN/nIFgIWNzPa3/Vz8mYylgbWc= +github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= +github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng= +github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= +github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= +github.com/spf13/cobra v1.1.1/go.mod h1:WnodtKOvamDL/PwE2M4iKs8aMDBZ5Q5klgD3qfVJQMI= +github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= +github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= +github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= +github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= +github.com/spf13/viper v1.6.2/go.mod h1:t3iDnF5Jlj76alVNuyFBk5oUMCvsrkbvZK0WQdfDi5k= +github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= +github.com/spf13/viper v1.7.1 h1:pM5oEahlgWv/WnHXpgbKz7iLIxRf65tye2Ci+XFK5sk= +github.com/spf13/viper v1.7.1/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= +github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= +github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= +github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI= +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.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= +github.com/stretchr/testify v0.0.0-20161117074351-18a02ba4a312/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.2.0/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +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.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= +github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= +github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= +github.com/tinylib/msgp v1.0.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE= +github.com/tinylib/msgp v1.1.5/go.mod h1:eQsjooMTnV42mHu917E26IogZ2930nFyBQdofk10Udg= +github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/ttacon/chalk v0.0.0-20160626202418-22c06c80ed31/go.mod h1:onvgF043R+lC5RZ8IT9rBXDaEDnpnw/Cl+HFiw+v/7Q= +github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= +github.com/uber/jaeger-client-go v2.23.1+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk= +github.com/uber/jaeger-client-go v2.25.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk= +github.com/uber/jaeger-lib v2.4.0+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U= +github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= +github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= +github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= +github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= +github.com/vektah/gqlparser v1.1.2/go.mod h1:1ycwN7Ij5njmMkPPAOaRFY4rET2Enx7IkVv3vaXspKw= +github.com/vektra/mockery v0.0.0-20181123154057-e78b021dcbb5/go.mod h1:ppEjwdhyy7Y31EnHRDm1JkChoC7LXIJ7Ex0VYLWtZtQ= +github.com/wadey/gocovmerge v0.0.0-20160331181800-b5bfa59ec0ad/go.mod h1:Hy8o65+MXnS6EwGElrSRjUzQDLXreJlzYLlWiHtt8hM= +github.com/willf/bitset v1.1.3/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4= +github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c/go.mod h1:lB8K/P019DLNhemzwFU4jHLhdvlE6uDZjXFejJXr49I= +github.com/xdg/stringprep v0.0.0-20180714160509-73f8eece6fdc/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y= +github.com/xdg/stringprep v1.0.0/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y= +github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= +github.com/xlab/treeprint v0.0.0-20180616005107-d6fb6747feb6/go.mod h1:ce1O1j6UtZfjr22oyGxGLbauSBp2YVXpARAosm7dHBg= +github.com/xlab/treeprint v1.0.0/go.mod h1:IoImgRak9i3zJyuxOKUP1v4UZd1tMoKkq/Cimt1uhCg= +github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= +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= +go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= +go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= +go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg= +go.mongodb.org/mongo-driver v1.0.3/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= +go.mongodb.org/mongo-driver v1.1.1/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= +go.mongodb.org/mongo-driver v1.1.2/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= +go.mongodb.org/mongo-driver v1.3.0/go.mod h1:MSWZXKOynuguX+JSvwP8i+58jYCXxbia8HS3gZBapIE= +go.mongodb.org/mongo-driver v1.3.2/go.mod h1:MSWZXKOynuguX+JSvwP8i+58jYCXxbia8HS3gZBapIE= +go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= +go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= +go.opentelemetry.io/collector v0.17.0 h1:Da/hGGexGLFjp444zMJ3y28y0R9Ll/kJTfNVpS2pliY= +go.opentelemetry.io/collector v0.17.0/go.mod h1:h9AuSwXGdKT5M+avYsM8KGL7FiObqUL/1s+BBLpLB80= +go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= +go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/automaxprocs v1.3.0/go.mod h1:9CWT6lKIep8U41DDaPiH6eFscnTyjfTANNQNx6LrIcA= +go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= +go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= +go.uber.org/multierr v1.5.0 h1:KCa4XfM8CWFCpxXRGok+Q0SS/0XBhMDbHHGABQLvD2A= +go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= +go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= +go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= +go.uber.org/zap v1.16.0 h1:uFRZXykJGK9lLY4HtgSw44DnIcAM+kRBP7x5m+NpAOM= +go.uber.org/zap v1.16.0/go.mod h1:MA8QOfq0BHJwdXa996Y4dYkAqRKB8/1K1QMMZVaNZjQ= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/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-20190320223903-b7391e95e576/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190404164418-38d8ce5564a5/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= +golang.org/x/crypto v0.0.0-20190422162423-af44ce270edf/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190530122614-20be4c3c3ed5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190617133340-57b3e21c3d56/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191202143827-86a70503ff7e/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= +golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= +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/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181005035420-146acd28ed58/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/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-20190320064053-1272bf9dcd53/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-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +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-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190921015927-1a5e07d1ff72/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191002035440-2ec189313ef0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200904194848-62affa334b73/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201006153459-a7d1128ccaa0/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201021035429-f5854403a974 h1:IX6qOQeG5uLjB/hjjwjedwfjND0hgjPMMyO1RoIXQNI= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/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-20190412183630-56d357773e84/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-20200930132711-30421366ff76/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190129075346-302c3dd5f1cc/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-20190222072716-a9d3bda3a223/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-20190321052220-f7bb7a8bee54/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190405154228-4b34438f7a67/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190419153524-e8e3143a4f4a/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-20190531175056-4c3a928424d2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190626150813-e07cf5db2756/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191113165036-4c7a9d0fe056/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191128015809-6d18c012aee9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/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-20200107162124-548cf772de50/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/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-20200420163511-1957bb5e6d1f/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-20200519105757-fe76b779f299/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-20200622214017-ed371f2e16b4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201008064518-c1f3e3309c71/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201015000850-e3ed0017c211 h1:9UQO31fZ+0aKQOFldThf7BKPMJTiBfWycGh/u3UoO88= +golang.org/x/sys v0.0.0-20201015000850-e3ed0017c211/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +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.4 h1:0YWbFKbhXG/wIiuHDSKpS0Iy7FSA+u45VtBMfQcFTTc= +golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181112210238-4b1f3b6b1646/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-20190125232054-d66bd3c5d5a6/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190329151228-23e29df326fe/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190416151739-9c9e1878f421/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190420181800-aa740d480789/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-20190531172133-b3315ee88b7d/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190617190820-da514acc4774/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-20190907020128-2ca718005c18/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-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +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-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200108203644-89082a384178/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-20200203023011-6f24f261dadb/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-20200410194907-79a7a3126eef/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +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-20200513201620-d5fe73897c97/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-20200603131246-cc40288be839/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-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= +golang.org/x/tools v0.0.0-20201008025239-9df69603baec/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU= +golang.org/x/tools v0.0.0-20201022035929-9cf592e881e9/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +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= +gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= +gonum.org/v1/gonum v0.0.0-20181121035319-3f7ecaa7e8ca/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= +gonum.org/v1/gonum v0.6.0/go.mod h1:9mxDZsDKxgMAuccQkewq682L+0eCu4dCN2yonUJTCLU= +gonum.org/v1/netlib v0.0.0-20181029234149-ec6d1f5cefe6/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= +gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= +gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc= +google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= +google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= +google.golang.org/api v0.32.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +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/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-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= +google.golang.org/genproto v0.0.0-20190716160619-c506a9f90610/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +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-20190927181202-20e1ac93f88c/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-20200108215221-bd8f9a0ef82f/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-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= +google.golang.org/genproto v0.0.0-20200624020401-64a14ca9d1ad/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d h1:92D1fum1bJLKSdr11OJ+54YeCMCGYIygTA7R/YZxH5M= +google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= +google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.22.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.24.0/go.mod h1:XDChyiUovWa60DnaeDeZmSW86xtLtjtZbwvSiRnRtcA= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= +google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= +google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.32.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= +google.golang.org/grpc v1.34.0 h1:raiipEjMOIC/TO2AvyTxP25XFdLxNIBwzDh3FM3XztI= +google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= +google.golang.org/grpc/examples v0.0.0-20200728065043-dfc0c05b2da9/go.mod h1:5j1uub0jRGhRiSghIlrThmBUgcgLXOVJQ/l1getT4uo= +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 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +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-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/fsnotify/fsnotify.v1 v1.4.7/go.mod h1:Fyux9zXlo4rWoMSIzpn9fDAYjalPqJ/K1qJ27s+7ltE= +gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= +gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/ini.v1 v1.52.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/ini.v1 v1.57.0 h1:9unxIsFcTt4I55uWluz+UmL95q4kdJ0buvQ1ZIqVQww= +gopkg.in/ini.v1 v1.57.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/jcmturner/aescts.v1 v1.0.1/go.mod h1:nsR8qBOg+OucoIW+WMhB3GspUQXq9XorLnQb9XtvcOo= +gopkg.in/jcmturner/dnsutils.v1 v1.0.1/go.mod h1:m3v+5svpVOhtFAP/wSz+yzh4Mc0Fg7eRhxkJMWSIz9Q= +gopkg.in/jcmturner/goidentity.v3 v3.0.0/go.mod h1:oG2kH0IvSYNIu80dVAyu/yoefjq1mNfM5bm88whjWx4= +gopkg.in/jcmturner/gokrb5.v7 v7.2.3/go.mod h1:l8VISx+WGYp+Fp7KRbsiUuXTTOnxIc3Tuvyavf11/WM= +gopkg.in/jcmturner/gokrb5.v7 v7.5.0/go.mod h1:l8VISx+WGYp+Fp7KRbsiUuXTTOnxIc3Tuvyavf11/WM= +gopkg.in/jcmturner/rpc.v1 v1.1.0/go.mod h1:YIdkC4XfD6GXbzje11McwsDuOlZQSb9W4vfLvuNnlv8= +gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= +gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= +gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 h1:tQIYjPdBoyREyB9XMu+nnTclpTYkz2zFM+lzLJFO4gQ= +gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= +honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +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= +honnef.co/go/tools v0.0.1-2020.1.6/go.mod h1:pyyisuGw24ruLjrr1ddx39WE0y9OooInRzEYLhQB2YY= +k8s.io/api v0.19.2/go.mod h1:IQpK0zFQ1xc5iNIQPqzgoOwuFugaYHK4iCknlAQP9nI= +k8s.io/apimachinery v0.19.2/go.mod h1:DnPGDnARWFvYa3pMHgSxtbZb7gpzzAZ1pTfaUNDVlmA= +k8s.io/client-go v0.19.2/go.mod h1:S5wPhCqyDNAlzM9CnEdgTGV4OqhsW3jGO1UM1epwfJA= +k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= +k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I= +k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= +k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= +k8s.io/klog/v2 v2.3.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= +k8s.io/kube-openapi v0.0.0-20200805222855-6aeccd4b50c6/go.mod h1:UuqjUnNftUyPE5H64/qeyjQoUZhGpeFDVdxjTeEVN2o= +k8s.io/utils v0.0.0-20200729134348-d5654de09c73/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= +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/structured-merge-diff/v4 v4.0.1/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= +sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= +sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= +sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU= diff --git a/extension/observer/ecsobserver/testdata/config.yaml b/extension/observer/ecsobserver/testdata/config.yaml new file mode 100644 index 000000000000..6f03afa06643 --- /dev/null +++ b/extension/observer/ecsobserver/testdata/config.yaml @@ -0,0 +1,35 @@ +extensions: + ecs_observer: + ecs_observer/1: + refresh_interval: 15s + cluster_name: 'EC2-Testing' + cluster_region: 'us-west-2' + sd_result_file: '/opt/aws/amazon-cloudwatch-agent/etc/ecs_sd_targets.yaml' + docker_label: + job_name_label: 'ECS_PROMETHEUS_JOB_NAME' + metrics_path_label: 'ECS_PROMETHEUS_METRICS_PATH' + port_label: 'ECS_PROMETHEUS_EXPORTER_PORT_SUBSET_A' + task_definitions: + - job_name: 'task_def_1' + metrics_path: '/stats/metrics' + metrics_ports: '9901;9404;9406' + task_definition_arn_pattern: '.*:task-definition/bugbash-java-fargate-awsvpc-task-def-only:[0-9]+' + - container_name_pattern: '^bugbash-jar.*$' + metrics_ports: '9902' + task_definition_arn_pattern: '.*:task-definition/nginx:[0-9]+' + +service: + extensions: [ecs_observer, ecs_observer/1] + pipelines: + traces: + receivers: [examplereceiver] + processors: [exampleprocessor] + exporters: [exampleexporter] + +# Data pipeline is required to load the config. +receivers: + examplereceiver: +processors: + exampleprocessor: +exporters: + exampleexporter: From a10d252f33751f96eb18acfb195557b8aa06c2a6 Mon Sep 17 00:00:00 2001 From: Raphael Koh Date: Mon, 21 Dec 2020 18:00:29 -0500 Subject: [PATCH 02/19] Add factory tests --- .../observer/ecsobserver/factory_test.go | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 extension/observer/ecsobserver/factory_test.go diff --git a/extension/observer/ecsobserver/factory_test.go b/extension/observer/ecsobserver/factory_test.go new file mode 100644 index 000000000000..d860a4d9e4e2 --- /dev/null +++ b/extension/observer/ecsobserver/factory_test.go @@ -0,0 +1,52 @@ +// Copyright The 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 ecsobserver + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/config/configcheck" + "go.uber.org/zap" +) + +func TestNewFactory(t *testing.T) { + f := NewFactory() + assert.NotNil(t, f) + assert.Equal(t, typeStr, f.Type()) +} + +func TestCreateDefaultConfig(t *testing.T) { + f := NewFactory() + cfg := f.CreateDefaultConfig() + assert.NoError(t, configcheck.ValidateConfig(cfg)) +} + +func TestCreateExtension(t *testing.T) { + f := NewFactory() + cfg := f.CreateDefaultConfig() + assert.NoError(t, configcheck.ValidateConfig(cfg)) + + ext, err := f.CreateExtension( + context.Background(), + component.ExtensionCreateParams{Logger: zap.NewNop()}, + cfg, + ) + require.NoError(t, err) + require.NotNil(t, ext) +} From d87dea20b8b308e607052ca5744211f4529d6217 Mon Sep 17 00:00:00 2001 From: Raphael Koh Date: Mon, 21 Dec 2020 18:15:51 -0500 Subject: [PATCH 03/19] Add init function for task definition config --- extension/observer/ecsobserver/config.go | 27 +++++++++++++++++++ extension/observer/ecsobserver/config_test.go | 25 +++++++++++++++++ 2 files changed, 52 insertions(+) diff --git a/extension/observer/ecsobserver/config.go b/extension/observer/ecsobserver/config.go index 5a6226bc8a61..de3af60f72e9 100644 --- a/extension/observer/ecsobserver/config.go +++ b/extension/observer/ecsobserver/config.go @@ -16,11 +16,19 @@ package ecsobserver import ( "regexp" + "strconv" + "strings" "time" "go.opentelemetry.io/collector/config/configmodels" ) +const ( + AwsSdkLevelRetryCount = 3 + + portSeparator = ";" +) + // DockerLabelConfig defines the configuration fo docker label-based service discovery. type DockerLabelConfig struct { JobNameLabel string `mapstructure:"job_name_label"` @@ -41,6 +49,25 @@ type TaskDefinitionConfig struct { metricsPortList []int } +// init initializes the task definition config by compiling regex patterns and extracting +// the list of metric ports. +func (t *TaskDefinitionConfig) init() { + t.taskDefRegex = regexp.MustCompile(t.TaskDefArnPattern) + + if t.ContainerNamePattern != "" { + t.containerNameRegex = regexp.MustCompile(t.ContainerNamePattern) + } + + ports := strings.Split(t.MetricsPorts, portSeparator) + for _, v := range ports { + if port, err := strconv.Atoi(strings.TrimSpace(v)); err != nil || port < 0 { + continue + } else { + t.metricsPortList = append(t.metricsPortList, port) + } + } +} + // Config defines the configuration for ECS observers. type Config struct { configmodels.ExtensionSettings `mapstructure:",squash"` diff --git a/extension/observer/ecsobserver/config_test.go b/extension/observer/ecsobserver/config_test.go index 181c26cd5c83..ec3f94766741 100644 --- a/extension/observer/ecsobserver/config_test.go +++ b/extension/observer/ecsobserver/config_test.go @@ -75,3 +75,28 @@ func TestLoadConfig(t *testing.T) { ext1, ) } + +func TestTaskDefinitionConfigInit(t *testing.T) { + config := TaskDefinitionConfig{ + JobName: "test_job_1", + MetricsPorts: "11;12; 13 ;a;14 ", + TaskDefArnPattern: "^task.*$", + } + + config.init() + assert.Nil(t, config.containerNameRegex) + assert.True(t, config.taskDefRegex.MatchString("task12")) + assert.False(t, config.taskDefRegex.MatchString("atask12")) + assert.Equal(t, config.metricsPortList, []int{11, 12, 13, 14}) + + config = TaskDefinitionConfig{ + ContainerNamePattern: "^container.*$", + MetricsPorts: "a;b; c ;d;e ", + } + + config.init() + assert.NotNil(t, config.containerNameRegex) + assert.True(t, config.containerNameRegex.MatchString("container12")) + assert.False(t, config.containerNameRegex.MatchString("acontainer12")) + assert.Nil(t, config.metricsPortList) +} From 5a173e5a694215b9d836c5eb073f8b9fe250bb72 Mon Sep 17 00:00:00 2001 From: Raphael Koh Date: Mon, 21 Dec 2020 20:49:01 -0500 Subject: [PATCH 04/19] Add ECS API --- extension/observer/ecsobserver/config.go | 5 +- extension/observer/ecsobserver/config_test.go | 2 + extension/observer/ecsobserver/ecstask.go | 54 ++++++++ extension/observer/ecsobserver/extension.go | 9 +- extension/observer/ecsobserver/factory.go | 9 +- extension/observer/ecsobserver/go.mod | 1 + extension/observer/ecsobserver/go.sum | 6 + .../observer/ecsobserver/servicediscovery.go | 119 ++++++++++++++++++ 8 files changed, 195 insertions(+), 10 deletions(-) create mode 100644 extension/observer/ecsobserver/ecstask.go create mode 100644 extension/observer/ecsobserver/servicediscovery.go diff --git a/extension/observer/ecsobserver/config.go b/extension/observer/ecsobserver/config.go index de3af60f72e9..c9d65460ea79 100644 --- a/extension/observer/ecsobserver/config.go +++ b/extension/observer/ecsobserver/config.go @@ -21,11 +21,10 @@ import ( "time" "go.opentelemetry.io/collector/config/configmodels" + "go.uber.org/zap" ) const ( - AwsSdkLevelRetryCount = 3 - portSeparator = ";" ) @@ -89,4 +88,6 @@ type Config struct { // definition-based service discovery (optional). If this is not provided, // task definition-based SD is disabled. TaskDefinitions []*TaskDefinitionConfig `mapstructure:"task_definitions"` + + logger *zap.Logger } diff --git a/extension/observer/ecsobserver/config_test.go b/extension/observer/ecsobserver/config_test.go index ec3f94766741..9cc546e623d8 100644 --- a/extension/observer/ecsobserver/config_test.go +++ b/extension/observer/ecsobserver/config_test.go @@ -24,6 +24,7 @@ import ( "go.opentelemetry.io/collector/component/componenttest" "go.opentelemetry.io/collector/config/configmodels" "go.opentelemetry.io/collector/config/configtest" + "go.uber.org/zap" ) func TestLoadConfig(t *testing.T) { @@ -71,6 +72,7 @@ func TestLoadConfig(t *testing.T) { TaskDefArnPattern: ".*:task-definition/nginx:[0-9]+", }, }, + logger: zap.NewNop(), }, ext1, ) diff --git a/extension/observer/ecsobserver/ecstask.go b/extension/observer/ecsobserver/ecstask.go new file mode 100644 index 000000000000..b9e39a0df27d --- /dev/null +++ b/extension/observer/ecsobserver/ecstask.go @@ -0,0 +1,54 @@ +// Copyright The 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 ecsobserver + +import ( + "github.com/aws/aws-sdk-go/service/ecs" +) + +const ( + containerNameLabel = "container_name" + taskFamilyLabel = "TaskDefinitionFamily" + taskRevisionLabel = "TaskRevision" + taskGroupLabel = "TaskGroup" + taskStartedbyLabel = "StartedBy" + taskLaunchTypeLabel = "LaunchType" + taskJobNameLabel = "job" + taskMetricsPathLabel = "__metrics_path__" + ec2InstanceTypeLabel = "InstanceType" + ec2VpcIdLabel = "VpcId" + ec2SubnetIdLabel = "SubnetId" + + //https://prometheus.io/docs/prometheus/latest/configuration/configuration/#scrape_config + defaultPrometheusMetricsPath = "/metrics" +) + +type EC2MetaData struct { + ContainerInstanceId string + ECInstanceId string + PrivateIP string + InstanceType string + VpcId string + SubnetId string +} + +type ECSTask struct { + Task *ecs.Task + TaskDefinition *ecs.TaskDefinition + EC2Info *EC2MetaData + + DockerLabelBased bool + TaskDefinitionBased bool +} diff --git a/extension/observer/ecsobserver/extension.go b/extension/observer/ecsobserver/extension.go index 4789eecb36bf..a25efd213ed9 100644 --- a/extension/observer/ecsobserver/extension.go +++ b/extension/observer/ecsobserver/extension.go @@ -28,19 +28,18 @@ type ecsObserver struct { } type endpointsLister struct { - logger *zap.Logger - observerName string + logger *zap.Logger + config *Config } var _ component.ServiceExtension = (*ecsObserver)(nil) -func newObserver(logger *zap.Logger, config *Config) (component.ServiceExtension, error) { +func newObserver(config *Config) (component.ServiceExtension, error) { h := &ecsObserver{ EndpointsWatcher: observer.EndpointsWatcher{ RefreshInterval: config.RefreshInterval, Endpointslister: endpointsLister{ - logger: logger, - observerName: config.Name(), + config: config, }, }, } diff --git a/extension/observer/ecsobserver/factory.go b/extension/observer/ecsobserver/factory.go index e24e1e46673e..c126b3aa9450 100644 --- a/extension/observer/ecsobserver/factory.go +++ b/extension/observer/ecsobserver/factory.go @@ -21,6 +21,7 @@ import ( "go.opentelemetry.io/collector/component" "go.opentelemetry.io/collector/config/configmodels" "go.opentelemetry.io/collector/extension/extensionhelper" + "go.uber.org/zap" ) const ( @@ -46,8 +47,9 @@ func createDefaultConfig() configmodels.Extension { NameVal: string(typeStr), }, RefreshInterval: defaultCollectionInterval * time.Second, - ClusterName: "", - ClusterRegion: "", + ClusterName: "", + ClusterRegion: "", + logger: zap.NewNop(), } } @@ -57,5 +59,6 @@ func createExtension( cfg configmodels.Extension, ) (component.ServiceExtension, error) { config := cfg.(*Config) - return newObserver(params.Logger, config) + config.logger = params.Logger + return newObserver(config) } diff --git a/extension/observer/ecsobserver/go.mod b/extension/observer/ecsobserver/go.mod index 9ded80c55b06..3f951b2a7afd 100644 --- a/extension/observer/ecsobserver/go.mod +++ b/extension/observer/ecsobserver/go.mod @@ -3,6 +3,7 @@ module github.com/open-telemetry/opentelemetry-collector-contrib/extension/obser go 1.14 require ( + github.com/aws/aws-sdk-go v1.36.13 github.com/open-telemetry/opentelemetry-collector-contrib/extension/observer v0.0.0-00010101000000-000000000000 github.com/stretchr/testify v1.6.1 go.opentelemetry.io/collector v0.17.0 diff --git a/extension/observer/ecsobserver/go.sum b/extension/observer/ecsobserver/go.sum index 74dae9c26777..0a48c08c7e23 100644 --- a/extension/observer/ecsobserver/go.sum +++ b/extension/observer/ecsobserver/go.sum @@ -110,7 +110,10 @@ github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:l github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg= github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU= github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/aws/aws-sdk-go v1.35.5 h1:doSEOxC0UkirPcle20Rc+1kAhJ4Ip+GSEeZ3nKl7Qlk= github.com/aws/aws-sdk-go v1.35.5/go.mod h1:tlPOdRjfxPBpNIwqDj61rmsnA85v9jc0Ps9+muhnW+k= +github.com/aws/aws-sdk-go v1.36.13 h1:RAyssUwg/yM7q874D2PQuIST6uhhyYFFPJtgVG/OujI= +github.com/aws/aws-sdk-go v1.36.13/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro= github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g= 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= @@ -482,6 +485,7 @@ github.com/jcmturner/gofork v0.0.0-20190328161633-dc7c13fece03/go.mod h1:MK8+TM0 github.com/jcmturner/gofork v1.0.0/go.mod h1:MK8+TM0La+2rjBD4jE12Kj1pCCxK7d2LK/UM3ncEo0o= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= +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/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= @@ -960,6 +964,8 @@ golang.org/x/net v0.0.0-20200904194848-62affa334b73/go.mod h1:/O7V0waA8r7cgGh81R golang.org/x/net v0.0.0-20201006153459-a7d1128ccaa0/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201021035429-f5854403a974 h1:IX6qOQeG5uLjB/hjjwjedwfjND0hgjPMMyO1RoIXQNI= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201110031124-69a78807bb2b h1:uwuIcX0g4Yl1NC5XAz37xsr2lTtcqevgzYNVt49waME= +golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 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= diff --git a/extension/observer/ecsobserver/servicediscovery.go b/extension/observer/ecsobserver/servicediscovery.go new file mode 100644 index 000000000000..4873edbe7449 --- /dev/null +++ b/extension/observer/ecsobserver/servicediscovery.go @@ -0,0 +1,119 @@ +// Copyright The 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 ecsobserver + +import ( + "fmt" + "os" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/ec2metadata" + "github.com/aws/aws-sdk-go/aws/session" + "github.com/aws/aws-sdk-go/service/ec2" + "github.com/aws/aws-sdk-go/service/ecs" + "go.uber.org/zap" +) + +const ( + AwsSdkLevelRetryCount = 3 +) + +type serviceDiscovery struct { + svcEcs *ecs.ECS + svcEc2 *ec2.EC2 + config *Config +} + +func (sd *serviceDiscovery) init() { + region := getAWSRegion(sd.config) + awsConfig := aws.NewConfig().WithRegion(region).WithMaxRetries(AwsSdkLevelRetryCount) + session := session.New(awsConfig) + sd.svcEcs = ecs.New(session, awsConfig) + sd.svcEc2 = ec2.New(session, awsConfig) +} + +func (sd *serviceDiscovery) getECSTasks() ([]*ECSTask, error) { + var taskList []*ECSTask + listTasksInput := &ecs.ListTasksInput{Cluster: &sd.config.ClusterName} + + for { + // List all running task ARNs in the cluster + listTasksResp, listTasksErr := sd.svcEcs.ListTasks(listTasksInput) + if listTasksErr != nil { + return taskList, fmt.Errorf("Failed to list task ARNs for %s. Error: %s", sd.config.ClusterName, listTasksErr.Error()) + } + + // Retrieve tasks from task ARNs + descTasksInput := &ecs.DescribeTasksInput{ + Cluster: &sd.config.ClusterName, + Tasks: listTasksResp.TaskArns, + } + descTasksResp, descTasksErr := sd.svcEcs.DescribeTasks(descTasksInput) + if descTasksErr != nil { + return taskList, fmt.Errorf("Failed to describe ECS Tasks for %s. Error: %s", sd.config.ClusterName, descTasksErr.Error()) + } + + for _, f := range descTasksResp.Failures { + sd.config.logger.Debug( + "DescribeTask Failure.", + zap.String("ARN", *f.Arn), + zap.String("Reason", *f.Reason), + zap.String("Detai;", *f.Detail), + ) + } + + for _, task := range descTasksResp.Tasks { + ecsTask := &ECSTask{ + Task: task, + } + taskList = append(taskList, ecsTask) + } + + if listTasksResp.NextToken == nil { + break + } + listTasksInput.NextToken = listTasksResp.NextToken + } + return taskList, nil +} + +// getAWSRegion retrieves the AWS region from the provided config, env var, or EC2 metadata. +func getAWSRegion(cfg *Config) (awsRegion string) { + awsRegion = cfg.ClusterRegion + + if awsRegion == "" { + if regionEnv := os.Getenv("AWS_REGION"); regionEnv != "" { + awsRegion = regionEnv + cfg.logger.Debug("Cluster region not defined. Fetched region from environment variables", zap.String("region", awsRegion)) + } else { + if s, err := session.NewSession(); err != nil { + cfg.logger.Error("Unable to create default session", zap.Error(err)) + } else { + awsRegion, err = ec2metadata.New(s).Region() + if err != nil { + cfg.logger.Error("Unable to retrieve the region from the EC2 instance", zap.Error(err)) + } else { + cfg.logger.Debug("Fetch region from EC2 metadata", zap.String("region", awsRegion)) + } + } + } + } + + if awsRegion == "" { + cfg.logger.Error("Cannot fetch region variable from config file, environment variables, or ec2 metadata.") + } + + return +} From d8fff43461fa8cbd9749a745474df8e0a009ac14 Mon Sep 17 00:00:00 2001 From: Raphael Koh Date: Tue, 22 Dec 2020 11:20:24 -0500 Subject: [PATCH 05/19] Add task retrieval processor --- extension/observer/ecsobserver/extension.go | 13 +-- extension/observer/ecsobserver/processor.go | 20 +++++ .../observer/ecsobserver/servicediscovery.go | 46 ---------- .../ecsobserver/taskretrievalprocessor.go | 83 +++++++++++++++++++ 4 files changed, 110 insertions(+), 52 deletions(-) create mode 100644 extension/observer/ecsobserver/processor.go create mode 100644 extension/observer/ecsobserver/taskretrievalprocessor.go diff --git a/extension/observer/ecsobserver/extension.go b/extension/observer/ecsobserver/extension.go index a25efd213ed9..758a8ab51cd6 100644 --- a/extension/observer/ecsobserver/extension.go +++ b/extension/observer/ecsobserver/extension.go @@ -17,10 +17,8 @@ package ecsobserver import ( "context" - "go.opentelemetry.io/collector/component" - "go.uber.org/zap" - "github.com/open-telemetry/opentelemetry-collector-contrib/extension/observer" + "go.opentelemetry.io/collector/component" ) type ecsObserver struct { @@ -28,18 +26,21 @@ type ecsObserver struct { } type endpointsLister struct { - logger *zap.Logger - config *Config + sd *serviceDiscovery + observerName string } var _ component.ServiceExtension = (*ecsObserver)(nil) func newObserver(config *Config) (component.ServiceExtension, error) { + sd := &serviceDiscovery{config: config} + sd.init() h := &ecsObserver{ EndpointsWatcher: observer.EndpointsWatcher{ RefreshInterval: config.RefreshInterval, Endpointslister: endpointsLister{ - config: config, + sd: sd, + observerName: config.Name(), }, }, } diff --git a/extension/observer/ecsobserver/processor.go b/extension/observer/ecsobserver/processor.go new file mode 100644 index 000000000000..87ebb53ae5ae --- /dev/null +++ b/extension/observer/ecsobserver/processor.go @@ -0,0 +1,20 @@ +// Copyright The 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 ecsobserver + +type Processor interface { + Process(clusterName string, taskList []*ECSTask) ([]*ECSTask, error) + ProcessorName() string +} diff --git a/extension/observer/ecsobserver/servicediscovery.go b/extension/observer/ecsobserver/servicediscovery.go index 4873edbe7449..837ee3714b31 100644 --- a/extension/observer/ecsobserver/servicediscovery.go +++ b/extension/observer/ecsobserver/servicediscovery.go @@ -15,7 +15,6 @@ package ecsobserver import ( - "fmt" "os" "github.com/aws/aws-sdk-go/aws" @@ -44,51 +43,6 @@ func (sd *serviceDiscovery) init() { sd.svcEc2 = ec2.New(session, awsConfig) } -func (sd *serviceDiscovery) getECSTasks() ([]*ECSTask, error) { - var taskList []*ECSTask - listTasksInput := &ecs.ListTasksInput{Cluster: &sd.config.ClusterName} - - for { - // List all running task ARNs in the cluster - listTasksResp, listTasksErr := sd.svcEcs.ListTasks(listTasksInput) - if listTasksErr != nil { - return taskList, fmt.Errorf("Failed to list task ARNs for %s. Error: %s", sd.config.ClusterName, listTasksErr.Error()) - } - - // Retrieve tasks from task ARNs - descTasksInput := &ecs.DescribeTasksInput{ - Cluster: &sd.config.ClusterName, - Tasks: listTasksResp.TaskArns, - } - descTasksResp, descTasksErr := sd.svcEcs.DescribeTasks(descTasksInput) - if descTasksErr != nil { - return taskList, fmt.Errorf("Failed to describe ECS Tasks for %s. Error: %s", sd.config.ClusterName, descTasksErr.Error()) - } - - for _, f := range descTasksResp.Failures { - sd.config.logger.Debug( - "DescribeTask Failure.", - zap.String("ARN", *f.Arn), - zap.String("Reason", *f.Reason), - zap.String("Detai;", *f.Detail), - ) - } - - for _, task := range descTasksResp.Tasks { - ecsTask := &ECSTask{ - Task: task, - } - taskList = append(taskList, ecsTask) - } - - if listTasksResp.NextToken == nil { - break - } - listTasksInput.NextToken = listTasksResp.NextToken - } - return taskList, nil -} - // getAWSRegion retrieves the AWS region from the provided config, env var, or EC2 metadata. func getAWSRegion(cfg *Config) (awsRegion string) { awsRegion = cfg.ClusterRegion diff --git a/extension/observer/ecsobserver/taskretrievalprocessor.go b/extension/observer/ecsobserver/taskretrievalprocessor.go new file mode 100644 index 000000000000..b5cafc191bbb --- /dev/null +++ b/extension/observer/ecsobserver/taskretrievalprocessor.go @@ -0,0 +1,83 @@ +// Copyright The 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 ecsobserver + +import ( + "fmt" + + "github.com/aws/aws-sdk-go/service/ecs" + "go.uber.org/zap" +) + +// Get all running tasks for the target cluster +type TaskRetrievalProcessor struct { + svcEcs *ecs.ECS + logger *zap.Logger +} + +func (p *TaskRetrievalProcessor) ProcessorName() string { + return "TaskRetrievalProcessor" +} + +// Process retrieves a list of ECS tasks using the ECS API. +func (p *TaskRetrievalProcessor) Process(clusterName string, taskList []*ECSTask) ([]*ECSTask, error) { + listTasksInput := &ecs.ListTasksInput{Cluster: &clusterName} + + for { + // List all running task ARNs in the cluster + listTasksResp, listTasksErr := p.svcEcs.ListTasks(listTasksInput) + if listTasksErr != nil { + return taskList, fmt.Errorf("Failed to list task ARNs for %s. Error: %s", clusterName, listTasksErr.Error()) + } + + // Retrieve tasks from task ARNs + descTasksInput := &ecs.DescribeTasksInput{ + Cluster: &clusterName, + Tasks: listTasksResp.TaskArns, + } + descTasksResp, descTasksErr := p.svcEcs.DescribeTasks(descTasksInput) + if descTasksErr != nil { + return taskList, fmt.Errorf("Failed to describe ECS Tasks for %s. Error: %s", clusterName, descTasksErr.Error()) + } + + for _, f := range descTasksResp.Failures { + p.logger.Debug( + "DescribeTask Failure.", + zap.String("ARN", *f.Arn), + zap.String("Reason", *f.Reason), + zap.String("Detail", *f.Detail), + ) + } + + for _, task := range descTasksResp.Tasks { + ecsTask := &ECSTask{ + Task: task, + } + taskList = append(taskList, ecsTask) + } + + if listTasksResp.NextToken == nil { + break + } + listTasksInput.NextToken = listTasksResp.NextToken + } + return taskList, nil +} + +func NewTaskProcessor(ecs *ecs.ECS) *TaskRetrievalProcessor { + return &TaskRetrievalProcessor{ + svcEcs: ecs, + } +} From b53eea83f5726f2ae676b3717f1453ce43ded430 Mon Sep 17 00:00:00 2001 From: Raphael Koh Date: Tue, 22 Dec 2020 12:52:18 -0500 Subject: [PATCH 06/19] Add metadata processor --- extension/observer/ecsobserver/go.mod | 1 + extension/observer/ecsobserver/go.sum | 1 + .../observer/ecsobserver/metadataprocessor.go | 182 ++++++++++++++++++ .../observer/ecsobserver/servicediscovery.go | 13 +- .../ecsobserver/taskretrievalprocessor.go | 7 +- 5 files changed, 200 insertions(+), 4 deletions(-) create mode 100644 extension/observer/ecsobserver/metadataprocessor.go diff --git a/extension/observer/ecsobserver/go.mod b/extension/observer/ecsobserver/go.mod index 3f951b2a7afd..7e19c337875f 100644 --- a/extension/observer/ecsobserver/go.mod +++ b/extension/observer/ecsobserver/go.mod @@ -4,6 +4,7 @@ go 1.14 require ( github.com/aws/aws-sdk-go v1.36.13 + github.com/hashicorp/golang-lru v0.5.4 github.com/open-telemetry/opentelemetry-collector-contrib/extension/observer v0.0.0-00010101000000-000000000000 github.com/stretchr/testify v1.6.1 go.opentelemetry.io/collector v0.17.0 diff --git a/extension/observer/ecsobserver/go.sum b/extension/observer/ecsobserver/go.sum index 0a48c08c7e23..c4c944975e39 100644 --- a/extension/observer/ecsobserver/go.sum +++ b/extension/observer/ecsobserver/go.sum @@ -453,6 +453,7 @@ github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09 github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= diff --git a/extension/observer/ecsobserver/metadataprocessor.go b/extension/observer/ecsobserver/metadataprocessor.go new file mode 100644 index 000000000000..05126659a2be --- /dev/null +++ b/extension/observer/ecsobserver/metadataprocessor.go @@ -0,0 +1,182 @@ +// Copyright The 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 ecsobserver + +import ( + "github.com/aws/aws-sdk-go/aws" + "fmt" + + "github.com/aws/aws-sdk-go/service/ec2" + "github.com/aws/aws-sdk-go/service/ecs" + "github.com/hashicorp/golang-lru/simplelru" + "go.uber.org/zap" +) + +const ( + // ECS Service Quota: https://docs.aws.amazon.com/AmazonECS/latest/developerguide/service-quotas.html + ec2metadataCacheSize = 2000 + batchSize = 100 +) + +// MetadataProcessor adds EC2 metadata for ECS Clusters. +type MetadataProcessor struct { + svcEc2 *ec2.EC2 + svcEcs *ecs.ECS + + ec2Cache *simplelru.LRU + logger *zap.Logger +} + +func (p *MetadataProcessor) ProcessorName() string { + return "MetadataProcessor" +} + +// Process retrieves EC2 metadata for ECS container instances. +func (p *MetadataProcessor) Process(clusterName string, taskList []*ECSTask) ([]*ECSTask, error) { + arnBatches := make([][]string, 0) + currBatch := make([]string, 0, batchSize) + + for _, task := range taskList { + if aws.StringValue(task.Task.LaunchType) != ecs.LaunchTypeEc2 { + continue + } + if ciArn := aws.StringValue(task.Task.ContainerInstanceArn); ciArn != "" { + if res, ok := p.ec2Cache.Get(ciArn); ok { + // Try retrieving from cache + task.EC2Info = res.(*EC2MetaData) + } else { + // Save for querying via API + if len(currBatch) >= batchSize { + arnBatches = append(arnBatches, currBatch) + currBatch = make([]string, 0, batchSize) + } + currBatch = append(currBatch, ciArn) + } + } + } + + if len(currBatch) > 0 { + arnBatches = append(arnBatches, currBatch) + } + + if len(arnBatches) == 0 { + return taskList, nil + } + + // Retrieve EC2 metadata from ARNs + ec2MetadataMap := make(map[string]*EC2MetaData) + for _, batch := range arnBatches { + err := p.getEC2Metadata(clusterName, batch, ec2MetadataMap) + if err != nil { + return taskList, err + } + } + + // Assign metadata to task + for _, task := range taskList { + ciArn := aws.StringValue(task.Task.ContainerInstanceArn) + if metadata, ok := ec2MetadataMap[ciArn]; ok { + task.EC2Info = metadata + } + } + + return taskList, nil +} + +func (p *MetadataProcessor) getEC2Metadata(clusterName string, arns []string, ec2MetadataMap map[string]*EC2MetaData) error { + // Get the ECS Instances from ARNs + ecsInput := &ecs.DescribeContainerInstancesInput{ + Cluster: &clusterName, + ContainerInstances: aws.StringSlice(arns), + } + resp, ecsErr := p.svcEcs.DescribeContainerInstances(ecsInput) + if ecsErr != nil { + return fmt.Errorf("Failed to DescribeContainerInstances. Error: %s", ecsErr.Error()) + } + + for _, f := range resp.Failures { + p.logger.Debug( + "DescribeContainerInstances Failure", + zap.String("ARN", *f.Arn), + zap.String("Reason", *f.Reason), + zap.String("Detail", *f.Detail), + ) + } + + ec2Ids := make([]*string, 0, len(arns)) + idToArnMap := make(map[string]*string) + + // Retrieve EC2 IDs from ECS container instances + for _, ci := range resp.ContainerInstances { + ec2Id := ci.Ec2InstanceId + ciArn := ci.ContainerInstanceArn + if ec2Id != nil && ciArn != nil { + ec2Ids = append(ec2Ids, ec2Id) + idToArnMap[aws.StringValue(ec2Id)] = ciArn + } + } + + // Get the EC2 Instances from EC2 IDs + ec2input := &ec2.DescribeInstancesInput{InstanceIds: ec2Ids} + for { + ec2resp, ec2err := p.svcEc2.DescribeInstances(ec2input) + if ec2err != nil { + return fmt.Errorf("Failed to DescribeInstances. Error: %s", ec2err.Error()) + } + + for _, rsv := range ec2resp.Reservations { + for _, instance := range rsv.Instances { + ec2Id := aws.StringValue(instance.InstanceId) + if ec2Id == "" { + continue + } + ciArnPtr, ok := idToArnMap[ec2Id] + if !ok { + continue + } + ciArn := aws.StringValue(ciArnPtr) + metadata := &EC2MetaData{ + ContainerInstanceId: ciArn, + ECInstanceId: ec2Id, + PrivateIP: aws.StringValue(instance.PrivateIpAddress), + InstanceType: aws.StringValue(instance.InstanceType), + VpcId: aws.StringValue(instance.VpcId), + SubnetId: aws.StringValue(instance.SubnetId), + } + ec2MetadataMap[ciArn] = metadata + p.ec2Cache.Add(ciArn, metadata) + } + } + + if ec2resp.NextToken == nil { + break + } + ec2input.NextToken = ec2resp.NextToken + } + + return nil +} + +func NewMetadataProcessor(ecs *ecs.ECS, ec2 *ec2.EC2, logger *zap.Logger) *MetadataProcessor { + // Initiate the container instance metadata LRU caching + lru, _ := simplelru.NewLRU(ec2metadataCacheSize, nil) + + return &MetadataProcessor{ + svcEcs: ecs, + svcEc2: ec2, + ec2Cache: lru, + logger: logger, + } +} diff --git a/extension/observer/ecsobserver/servicediscovery.go b/extension/observer/ecsobserver/servicediscovery.go index 837ee3714b31..31666f46b376 100644 --- a/extension/observer/ecsobserver/servicediscovery.go +++ b/extension/observer/ecsobserver/servicediscovery.go @@ -30,9 +30,12 @@ const ( ) type serviceDiscovery struct { + config *Config + svcEcs *ecs.ECS svcEc2 *ec2.EC2 - config *Config + + processors []Processor } func (sd *serviceDiscovery) init() { @@ -41,6 +44,14 @@ func (sd *serviceDiscovery) init() { session := session.New(awsConfig) sd.svcEcs = ecs.New(session, awsConfig) sd.svcEc2 = ec2.New(session, awsConfig) + sd.initProcessors() +} + +func (sd *serviceDiscovery) initProcessors() { + sd.processors = []Processor{ + NewTaskRetrievalProcessor(sd.svcEcs, sd.config.logger), + NewMetadataProcessor(sd.svcEcs, sd.svcEc2, sd.config.logger), + } } // getAWSRegion retrieves the AWS region from the provided config, env var, or EC2 metadata. diff --git a/extension/observer/ecsobserver/taskretrievalprocessor.go b/extension/observer/ecsobserver/taskretrievalprocessor.go index b5cafc191bbb..cd663be6c66d 100644 --- a/extension/observer/ecsobserver/taskretrievalprocessor.go +++ b/extension/observer/ecsobserver/taskretrievalprocessor.go @@ -21,7 +21,7 @@ import ( "go.uber.org/zap" ) -// Get all running tasks for the target cluster +// TaskRetrievalProcessor gets all running tasks for the target cluster. type TaskRetrievalProcessor struct { svcEcs *ecs.ECS logger *zap.Logger @@ -54,7 +54,7 @@ func (p *TaskRetrievalProcessor) Process(clusterName string, taskList []*ECSTask for _, f := range descTasksResp.Failures { p.logger.Debug( - "DescribeTask Failure.", + "DescribeTask Failure", zap.String("ARN", *f.Arn), zap.String("Reason", *f.Reason), zap.String("Detail", *f.Detail), @@ -76,8 +76,9 @@ func (p *TaskRetrievalProcessor) Process(clusterName string, taskList []*ECSTask return taskList, nil } -func NewTaskProcessor(ecs *ecs.ECS) *TaskRetrievalProcessor { +func NewTaskRetrievalProcessor(ecs *ecs.ECS, logger *zap.Logger) *TaskRetrievalProcessor { return &TaskRetrievalProcessor{ svcEcs: ecs, + logger: logger, } } From 94bf3a52b802e3fafb556704512d323f2f19565a Mon Sep 17 00:00:00 2001 From: Raphael Koh Date: Tue, 22 Dec 2020 13:16:19 -0500 Subject: [PATCH 07/19] Add task definition processor --- .../observer/ecsobserver/metadataprocessor.go | 26 ++--- .../ecsobserver/taskdefinitionprocessor.go | 95 +++++++++++++++++++ 2 files changed, 110 insertions(+), 11 deletions(-) create mode 100644 extension/observer/ecsobserver/taskdefinitionprocessor.go diff --git a/extension/observer/ecsobserver/metadataprocessor.go b/extension/observer/ecsobserver/metadataprocessor.go index 05126659a2be..a6c3006b8f62 100644 --- a/extension/observer/ecsobserver/metadataprocessor.go +++ b/extension/observer/ecsobserver/metadataprocessor.go @@ -52,18 +52,22 @@ func (p *MetadataProcessor) Process(clusterName string, taskList []*ECSTask) ([] if aws.StringValue(task.Task.LaunchType) != ecs.LaunchTypeEc2 { continue } - if ciArn := aws.StringValue(task.Task.ContainerInstanceArn); ciArn != "" { - if res, ok := p.ec2Cache.Get(ciArn); ok { - // Try retrieving from cache - task.EC2Info = res.(*EC2MetaData) - } else { - // Save for querying via API - if len(currBatch) >= batchSize { - arnBatches = append(arnBatches, currBatch) - currBatch = make([]string, 0, batchSize) - } - currBatch = append(currBatch, ciArn) + + ciArn := aws.StringValue(task.Task.ContainerInstanceArn) + if ciArn == "" { + continue + } + + if res, ok := p.ec2Cache.Get(ciArn); ok { + // Try retrieving from cache + task.EC2Info = res.(*EC2MetaData) + } else { + // Save for querying via API + if len(currBatch) >= batchSize { + arnBatches = append(arnBatches, currBatch) + currBatch = make([]string, 0, batchSize) } + currBatch = append(currBatch, ciArn) } } diff --git a/extension/observer/ecsobserver/taskdefinitionprocessor.go b/extension/observer/ecsobserver/taskdefinitionprocessor.go new file mode 100644 index 000000000000..9f17772ee59e --- /dev/null +++ b/extension/observer/ecsobserver/taskdefinitionprocessor.go @@ -0,0 +1,95 @@ +// Copyright The 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 ecsobserver + +import ( + "fmt" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/ecs" + "github.com/hashicorp/golang-lru/simplelru" + "go.uber.org/zap" +) + +const ( + // ECS Service Quota: https://docs.aws.amazon.com/AmazonECS/latest/developerguide/service-quotas.html + taskDefCacheSize = 2000 +) + +// TaskDefinitionProcessor gets all running tasks for the target cluster. +type TaskDefinitionProcessor struct { + svcEcs *ecs.ECS + + taskDefCache *simplelru.LRU + logger *zap.Logger +} + +func (p *TaskDefinitionProcessor) ProcessorName() string { + return "TaskDefinitionProcessor" +} + +// Process retrieves the task definition for each ECS task. +// Tasks that don't have a task definition are also filtered out. +func (p *TaskDefinitionProcessor) Process(cluster string, taskList []*ECSTask) ([]*ECSTask, error) { + numValidTasks := 0 + for _, task := range taskList { + arn := aws.StringValue(task.Task.TaskDefinitionArn) + if arn == "" { + continue + } + + if res, ok := p.taskDefCache.Get(arn); ok { + // Try retrieving from cache + task.TaskDefinition = res.(*ecs.TaskDefinition) + } else { + // Query API + input := &ecs.DescribeTaskDefinitionInput{TaskDefinition: &arn} + resp, err := p.svcEcs.DescribeTaskDefinition(input) + if err != nil { + return taskList, fmt.Errorf("Failed to describe task definition for %s. Error: %s", arn, err.Error()) + } + + if taskDef := resp.TaskDefinition; taskDef != nil { + task.TaskDefinition = taskDef + p.taskDefCache.Add(arn, taskDef) + numValidTasks++ + } + } + } + + // Filter out tasks that don't have a task definition + filteredTaskList := make([]*ECSTask, numValidTasks) + idx := 0 + for _, task := range taskList { + if task.TaskDefinition == nil { + continue + } + filteredTaskList[idx] = task + idx++ + } + + return filteredTaskList, nil +} + +func NewTaskDefinitionProcessor(ecs *ecs.ECS, logger *zap.Logger) *TaskDefinitionProcessor { + // Initiate the task definition LRU caching + lru, _ := simplelru.NewLRU(taskDefCacheSize, nil) + + return &TaskDefinitionProcessor{ + svcEcs: ecs, + taskDefCache: lru, + logger: logger, + } +} From 370e537d66afd03977ca3aeebb340a47f0caa0d5 Mon Sep 17 00:00:00 2001 From: Raphael Koh Date: Wed, 23 Dec 2020 14:20:53 -0500 Subject: [PATCH 08/19] Add Target Processor --- extension/observer/ecsobserver/ecstask.go | 207 +++++++++ .../observer/ecsobserver/ecstask_test.go | 406 ++++++++++++++++++ extension/observer/ecsobserver/go.mod | 1 + .../observer/ecsobserver/servicediscovery.go | 2 + .../observer/ecsobserver/targetprocessor.go | 80 ++++ .../ecsobserver/taskdefinitionprocessor.go | 2 +- 6 files changed, 697 insertions(+), 1 deletion(-) create mode 100644 extension/observer/ecsobserver/ecstask_test.go create mode 100644 extension/observer/ecsobserver/targetprocessor.go diff --git a/extension/observer/ecsobserver/ecstask.go b/extension/observer/ecsobserver/ecstask.go index b9e39a0df27d..3be5c518ff6a 100644 --- a/extension/observer/ecsobserver/ecstask.go +++ b/extension/observer/ecsobserver/ecstask.go @@ -15,6 +15,11 @@ package ecsobserver import ( + "fmt" + "regexp" + "strconv" + + "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/ecs" ) @@ -33,8 +38,14 @@ const ( //https://prometheus.io/docs/prometheus/latest/configuration/configuration/#scrape_config defaultPrometheusMetricsPath = "/metrics" + + // Prometheus definition: a string matching the regular expression [a-zA-Z_][a-zA-Z0-9_]* + // Regex pattern to filter out invalid labels + prometheusLabelNamePattern = "^[a-zA-Z_][a-zA-Z0-9_]*$" ) +var prometheusLabelNameRegex = regexp.MustCompile(prometheusLabelNamePattern) + type EC2MetaData struct { ContainerInstanceId string ECInstanceId string @@ -52,3 +63,199 @@ type ECSTask struct { DockerLabelBased bool TaskDefinitionBased bool } + +func (t *ECSTask) addPrometheusTargets(targets map[string]*PrometheusTarget, config *Config) { + ip := t.getPrivateIp() + if ip == "" { + return + } + for _, c := range t.TaskDefinition.ContainerDefinitions { + t.addDockerLabelBasedTarget(ip, c, targets, config) + t.addTaskDefinitionBasedTargets(ip, c, targets, config) + } +} + +// getPrivateIp retrieves the private ip of the ECS task. +func (t *ECSTask) getPrivateIp() (ip string) { + if t.TaskDefinition.NetworkMode == nil { + return + } + + // AWSVPC: Get Private IP from tasks->attachments (ElasticNetworkInterface -> privateIPv4Address) + if *t.TaskDefinition.NetworkMode == ecs.NetworkModeAwsvpc { + for _, v := range t.Task.Attachments { + if aws.StringValue(v.Type) != "ElasticNetworkInterface" { + continue + } + for _, d := range v.Details { + if aws.StringValue(d.Name) == "privateIPv4Address" { + return aws.StringValue(d.Value) + } + } + } + } + + if t.EC2Info != nil { + return t.EC2Info.PrivateIP + } + + return +} + +// addDockerLabelBasedTarget adds a Prometheus target based on docker labels. +func (t *ECSTask) addDockerLabelBasedTarget(ip string, c *ecs.ContainerDefinition, targets map[string]*PrometheusTarget, config *Config) { + if !t.DockerLabelBased { + return + } + + configuredPortStr, ok := c.DockerLabels[config.DockerLabel.PortLabel] + if !ok { + // skip the container without matching sd_port_label + return + } + + port, err := strconv.Atoi(aws.StringValue(configuredPortStr)) + if err != nil || port < 0 { + // an invalid port definition. + return + } + + hostPort := t.getHostPort(int64(port), c) + if hostPort == 0 { + return + } + + metricsPath := defaultPrometheusMetricsPath + metricsPathLabel := "" + if v, ok := c.DockerLabels[config.DockerLabel.MetricsPathLabel]; ok { + metricsPath = aws.StringValue(v) + metricsPathLabel = metricsPath + } + + targetAddr := fmt.Sprintf("%s:%d", ip, hostPort) + endpoint := targetAddr + metricsPath + if _, ok := targets[endpoint]; ok { + return + } + + customizedJobName := "" + if jobName, ok := c.DockerLabels[config.DockerLabel.JobNameLabel]; ok { + customizedJobName = aws.StringValue(jobName) + } + + targets[endpoint] = t.generatePrometheusTarget(c, targetAddr, metricsPathLabel, customizedJobName) +} + +// addTaskDefinitionBasedTargets adds Prometheus targets based on task definition. +func (t *ECSTask) addTaskDefinitionBasedTargets(ip string, c *ecs.ContainerDefinition, targets map[string]*PrometheusTarget, config *Config) { + if !t.TaskDefinitionBased { + return + } + + for _, taskDef := range config.TaskDefinitions { + // skip if task def regex mismatch + if !taskDef.taskDefRegex.MatchString(*t.Task.TaskDefinitionArn) { + continue + } + + // skip if there is container name regex pattern configured and container name mismatch + if taskDef.ContainerNamePattern != "" && !taskDef.containerNameRegex.MatchString(*c.Name) { + continue + } + + for _, port := range taskDef.metricsPortList { + // TODO: see if possible to optimize this instead of iterating through containers each time + hostPort := t.getHostPort(int64(port), c) + if hostPort == 0 { + continue + } + + metricsPath := defaultPrometheusMetricsPath + if taskDef.MetricsPath != "" { + metricsPath = taskDef.MetricsPath + } + + targetAddr := fmt.Sprintf("%s:%d", ip, hostPort) + endpoint := targetAddr + metricsPath + if _, ok := targets[endpoint]; !ok { + targets[endpoint] = t.generatePrometheusTarget(c, targetAddr, taskDef.MetricsPath, taskDef.JobName) + } + } + + } +} + +// getHostPort gets the host port of the container with the given container port. +func (t *ECSTask) getHostPort(containerPort int64, c *ecs.ContainerDefinition) int64 { + networkMode := aws.StringValue(t.TaskDefinition.NetworkMode) + if networkMode == "" || networkMode == ecs.NetworkModeNone { + // for network type: none, skipped directly + return 0 + } + + if networkMode == ecs.NetworkModeAwsvpc || networkMode == ecs.NetworkModeHost { + // for network type: awsvpc or host, get the mapped port from: taskDefinition->containerDefinitions->portMappings + for _, v := range c.PortMappings { + if aws.Int64Value(v.ContainerPort) == containerPort { + return aws.Int64Value(v.HostPort) + } + } + } else if networkMode == ecs.NetworkModeBridge { + // for network type: bridge, get the mapped port from: task->containers->networkBindings + containerName := aws.StringValue(c.Name) + for _, tc := range t.Task.Containers { + if containerName != aws.StringValue(tc.Name) { + continue + } + for _, v := range tc.NetworkBindings { + if aws.Int64Value(v.ContainerPort) == containerPort { + return aws.Int64Value(v.HostPort) + } + } + } + } + + return 0 +} + +// generatePrometheusTarget creates a Prometheus target with labels. +func (t *ECSTask) generatePrometheusTarget(c *ecs.ContainerDefinition, targetAddr string, metricsPath string, jobName string) *PrometheusTarget { + labels := make(map[string]string) + revisionStr := fmt.Sprintf("%d", *t.TaskDefinition.Revision) + + addTargetLabel(labels, containerNameLabel, c.Name) + addTargetLabel(labels, taskFamilyLabel, t.TaskDefinition.Family) + addTargetLabel(labels, taskGroupLabel, t.Task.Group) + addTargetLabel(labels, taskLaunchTypeLabel, t.Task.LaunchType) + addTargetLabel(labels, taskMetricsPathLabel, &metricsPath) + addTargetLabel(labels, taskRevisionLabel, &revisionStr) + addTargetLabel(labels, taskStartedbyLabel, t.Task.StartedBy) + + if t.EC2Info != nil { + addTargetLabel(labels, ec2InstanceTypeLabel, &t.EC2Info.InstanceType) + addTargetLabel(labels, ec2SubnetIdLabel, &t.EC2Info.SubnetId) + addTargetLabel(labels, ec2VpcIdLabel, &t.EC2Info.VpcId) + } + + for k, v := range c.DockerLabels { + if prometheusLabelNameRegex.MatchString(k) { + addTargetLabel(labels, k, v) + } + } + + // handle customized job label last, so the previous job docker label is overriden + addTargetLabel(labels, taskJobNameLabel, &jobName) + + return &PrometheusTarget{ + Targets: []string{targetAddr}, + Labels: labels, + } +} + +// addTargetLabel adds a label to the labels map if the given label value is valid. +func addTargetLabel(labels map[string]string, labelKey string, labelValuePtr *string) { + labelValue := aws.StringValue(labelValuePtr) + if labelValue != "" { + labels[labelKey] = labelValue + } +} diff --git a/extension/observer/ecsobserver/ecstask_test.go b/extension/observer/ecsobserver/ecstask_test.go new file mode 100644 index 000000000000..3d2145cf460b --- /dev/null +++ b/extension/observer/ecsobserver/ecstask_test.go @@ -0,0 +1,406 @@ +// Copyright The 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 ecsobserver + +import ( + "log" + "reflect" + "testing" + + "github.com/aws/aws-sdk-go/service/ecs" + "github.com/stretchr/testify/assert" +) + +func buildWorkloadFargateAwsvpc(dockerLabel bool, taskDef bool) *ECSTask { + networkMode := ecs.NetworkModeAwsvpc + taskAttachmentId := "775c6c63-b5f7-4a5b-8a60-8f8295a04cda" + taskAttachmentType := "ElasticNetworkInterface" + taskAttachmentStatus := "ATTACHING" + taskAttachmentDetailsKey1 := "networkInterfaceId" + taskAttachmentDetailsKey2 := "privateIPv4Address" + taskAttachmentDetailsValue1 := "eni-03de9d47faaa2e5ec" + taskAttachmentDetailsValue2 := "10.0.0.129" + + taskDefinitionArn := "arn:aws:ecs:us-east-2:211220956907:task-definition/prometheus-java-tomcat-fargate-awsvpc:1" + var taskRevision int64 = 4 + port9404String := "9404" + port9406String := "9406" + var port9404Int64 int64 = 9404 + var port9406Int64 int64 = 9406 + containerNameTomcat := "bugbash-tomcat-fargate-awsvpc-with-docker-label" + containerNameJar := "bugbash-jar-fargate-awsvpc-with-dockerlabel" + + jobNameLabel := "java-tomcat-fargate-awsvpc" + metricsPathLabel := "/metrics" + + return &ECSTask{ + DockerLabelBased: dockerLabel, + TaskDefinitionBased: taskDef, + Task: &ecs.Task{ + TaskDefinitionArn: &taskDefinitionArn, + Attachments: []*ecs.Attachment{ + { + Id: &taskAttachmentId, + Type: &taskAttachmentType, + Status: &taskAttachmentStatus, + Details: []*ecs.KeyValuePair{ + { + Name: &taskAttachmentDetailsKey1, + Value: &taskAttachmentDetailsValue1, + }, + { + Name: &taskAttachmentDetailsKey2, + Value: &taskAttachmentDetailsValue2, + }, + }, + }, + }, + }, + TaskDefinition: &ecs.TaskDefinition{ + NetworkMode: &networkMode, + TaskDefinitionArn: &taskDefinitionArn, + Revision: &taskRevision, + ContainerDefinitions: []*ecs.ContainerDefinition{ + { + Name: &containerNameTomcat, + DockerLabels: map[string]*string{ + "FARGATE_PROMETHEUS_EXPORTER_PORT": &port9404String, + "FARGATE_PROMETHEUS_JOB_NAME": &jobNameLabel, + }, + PortMappings: []*ecs.PortMapping{ + { + ContainerPort: &port9404Int64, + HostPort: &port9404Int64, + }, + }, + }, + { + Name: &containerNameJar, + DockerLabels: map[string]*string{ + "FARGATE_PROMETHEUS_EXPORTER_PORT": &port9406String, + "ECS_PROMETHEUS_METRICS_PATH": &metricsPathLabel, + }, + PortMappings: []*ecs.PortMapping{ + { + ContainerPort: &port9406Int64, + HostPort: &port9406Int64, + }, + }, + }, + }, + }, + } +} + +func buildWorkloadEC2BridgeDynamicPort(dockerLabel bool, taskDef bool) *ECSTask { + networkMode := ecs.NetworkModeBridge + taskContainersArn := "arn:aws:ecs:us-east-2:211220956907:container/3b288961-eb2c-4de5-a4c5-682c0a7cc625" + var taskContainersDynamicHostPort int64 = 32774 + var taskContainersMappedHostPort int64 = 9494 + + taskDefinitionArn := "arn:aws:ecs:us-east-2:211220956907:task-definition/prometheus-java-tomcat-ec2-awsvpc:1" + var taskRevision int64 = 5 + port9404String := "9404" + port9406String := "9406" + var port9404Int64 int64 = 9404 + var port9406Int64 int64 = 9406 + var port0Int64 int64 = 0 + + containerNameTomcat := "bugbash-tomcat-prometheus-workload-java-ec2-bridge-mapped-port" + containerNameJar := "bugbash-jar-prometheus-workload-java-ec2-bridge" + + jobNameLabelTomcat := "bugbash-tomcat-ec2-bridge-mapped-port" + metricsPathLabel := "/metrics" + + return &ECSTask{ + DockerLabelBased: dockerLabel, + TaskDefinitionBased: taskDef, + EC2Info: &EC2MetaData{ + ContainerInstanceId: "arn:aws:ecs:us-east-2:211220956907:container-instance/7b0a9662-ee0b-4cf6-9391-03f50ca501a5", + ECInstanceId: "i-02aa8e82e91b2c30e", + PrivateIP: "10.4.0.205", + InstanceType: "t3.medium", + VpcId: "vpc-03e9f55a92516a5e4", + SubnetId: "subnet-0d0b0212d14b70250", + }, + Task: &ecs.Task{ + TaskDefinitionArn: &taskDefinitionArn, + Attachments: []*ecs.Attachment{}, + Containers: []*ecs.Container{ + { + ContainerArn: &taskContainersArn, + Name: &containerNameTomcat, + NetworkBindings: []*ecs.NetworkBinding{ + { + ContainerPort: &port9404Int64, + HostPort: &taskContainersMappedHostPort, + }, + }, + }, + { + ContainerArn: &taskContainersArn, + Name: &containerNameJar, + NetworkBindings: []*ecs.NetworkBinding{ + { + ContainerPort: &port9404Int64, + HostPort: &taskContainersDynamicHostPort, + }, + }, + }, + }, + }, + TaskDefinition: &ecs.TaskDefinition{ + NetworkMode: &networkMode, + TaskDefinitionArn: &taskDefinitionArn, + Revision: &taskRevision, + ContainerDefinitions: []*ecs.ContainerDefinition{ + { + Name: &containerNameTomcat, + DockerLabels: map[string]*string{ + "EC2_PROMETHEUS_EXPORTER_PORT": &port9404String, + "EC2_PROMETHEUS_JOB_NAME": &jobNameLabelTomcat, + }, + PortMappings: []*ecs.PortMapping{ + { + ContainerPort: &port9404Int64, + HostPort: &port9404Int64, + }, + }, + }, + { + Name: &containerNameJar, + DockerLabels: map[string]*string{ + "EC2_PROMETHEUS_EXPORTER_PORT": &port9406String, + "EC2_PROMETHEUS_METRICS_PATH": &metricsPathLabel, + }, + PortMappings: []*ecs.PortMapping{ + { + ContainerPort: &port9406Int64, + HostPort: &port0Int64, + }, + }, + }, + }, + }, + } +} + +func TestAddPrometheusTargets_DockerLabelBased(t *testing.T) { + fullTask := buildWorkloadFargateAwsvpc(true, false) + assert.Equal(t, "10.0.0.129", fullTask.getPrivateIp()) + + config := &Config{ + DockerLabel: &DockerLabelConfig{ + JobNameLabel: "FARGATE_PROMETHEUS_JOB_NAME", + PortLabel: "FARGATE_PROMETHEUS_EXPORTER_PORT", + MetricsPathLabel: "ECS_PROMETHEUS_METRICS_PATH", + }, + } + + targets := make(map[string]*PrometheusTarget) + fullTask.addPrometheusTargets(targets, config) + + assert.Equal(t, 2, len(targets)) + target, ok := targets["10.0.0.129:9404/metrics"] + assert.True(t, ok, "Missing target: 10.0.0.129:9404/metrics") + + assert.Equal(t, 5, len(target.Labels)) + assert.Equal(t, "java-tomcat-fargate-awsvpc", target.Labels["job"]) + assert.Equal(t, "bugbash-tomcat-fargate-awsvpc-with-docker-label", target.Labels["container_name"]) + assert.Equal(t, "4", target.Labels["TaskRevision"]) + assert.Equal(t, "9404", target.Labels["FARGATE_PROMETHEUS_EXPORTER_PORT"]) + assert.Equal(t, "java-tomcat-fargate-awsvpc", target.Labels["FARGATE_PROMETHEUS_JOB_NAME"]) + + target, ok = targets["10.0.0.129:9406/metrics"] + assert.True(t, ok, "Missing target: 10.0.0.129:9406/metrics") + assert.Equal(t, 5, len(target.Labels)) + assert.Equal(t, "4", target.Labels["TaskRevision"]) + assert.Equal(t, "bugbash-jar-fargate-awsvpc-with-dockerlabel", target.Labels["container_name"]) + assert.Equal(t, "9406", target.Labels["FARGATE_PROMETHEUS_EXPORTER_PORT"]) + assert.Equal(t, "/metrics", target.Labels["__metrics_path__"]) + assert.Equal(t, "/metrics", target.Labels["ECS_PROMETHEUS_METRICS_PATH"]) +} + +func TestAddPrometheusTargets_TaskDefBased_Fargate(t *testing.T) { + fullTask := buildWorkloadFargateAwsvpc(false, true) + assert.Equal(t, "10.0.0.129", fullTask.getPrivateIp()) + config := &Config{ + TaskDefinitions: []*TaskDefinitionConfig{ + { + JobName: "", + MetricsPorts: "9404;9406", + TaskDefArnPattern: ".*:task-definition/prometheus-java-tomcat-fargate-awsvpc:[0-9]+", + MetricsPath: "/stats/metrics", + }, + }, + } + config.TaskDefinitions[0].init() + assert.Equal(t, []int{9404, 9406}, config.TaskDefinitions[0].metricsPortList) + + targets := make(map[string]*PrometheusTarget) + fullTask.addPrometheusTargets(targets, config) + + assert.Equal(t, 2, len(targets)) + target, ok := targets["10.0.0.129:9404/stats/metrics"] + assert.True(t, ok, "Missing target: 10.0.0.129:9404/stats/metrics") + + assert.Equal(t, 5, len(target.Labels)) + assert.Equal(t, "java-tomcat-fargate-awsvpc", target.Labels["FARGATE_PROMETHEUS_JOB_NAME"]) + assert.Equal(t, "bugbash-tomcat-fargate-awsvpc-with-docker-label", target.Labels["container_name"]) + assert.Equal(t, "4", target.Labels["TaskRevision"]) + assert.Equal(t, "9404", target.Labels["FARGATE_PROMETHEUS_EXPORTER_PORT"]) + assert.Equal(t, "/stats/metrics", target.Labels["__metrics_path__"]) + + target, ok = targets["10.0.0.129:9406/stats/metrics"] + assert.True(t, ok, "Missing target: 10.0.0.129:9406/stats/metrics") + assert.Equal(t, 5, len(target.Labels)) + assert.Equal(t, "4", target.Labels["TaskRevision"]) + assert.Equal(t, "bugbash-jar-fargate-awsvpc-with-dockerlabel", target.Labels["container_name"]) + assert.Equal(t, "9406", target.Labels["FARGATE_PROMETHEUS_EXPORTER_PORT"]) + assert.Equal(t, "/stats/metrics", target.Labels["__metrics_path__"]) + assert.Equal(t, "/metrics", target.Labels["ECS_PROMETHEUS_METRICS_PATH"]) +} + +func TestAddPrometheusTargets_TaskDefBased_EC2(t *testing.T) { + fullTask := buildWorkloadEC2BridgeDynamicPort(false, true) + assert.Equal(t, "10.4.0.205", fullTask.getPrivateIp()) + config := &Config{ + TaskDefinitions: []*TaskDefinitionConfig{ + { + JobName: "", + MetricsPorts: "9404;9406", + TaskDefArnPattern: ".*:task-definition/prometheus-java-tomcat-ec2-awsvpc:[0-9]+", + MetricsPath: "/metrics", + ContainerNamePattern: ".*tomcat-prometheus-workload-java-ec2.*", + }, + }, + } + config.TaskDefinitions[0].init() + assert.Equal(t, []int{9404, 9406}, config.TaskDefinitions[0].metricsPortList) + + targets := make(map[string]*PrometheusTarget) + fullTask.addPrometheusTargets(targets, config) + + assert.Equal(t, 1, len(targets)) + target, ok := targets["10.4.0.205:9494/metrics"] + log.Print(target) + assert.True(t, ok, "Missing target: 10.4.0.205:9494/metrics") + assert.Equal(t, 8, len(target.Labels)) + assert.Equal(t, "9404", target.Labels["EC2_PROMETHEUS_EXPORTER_PORT"]) + assert.Equal(t, "bugbash-tomcat-ec2-bridge-mapped-port", target.Labels["EC2_PROMETHEUS_JOB_NAME"]) + assert.Equal(t, "t3.medium", target.Labels["InstanceType"]) + assert.Equal(t, "subnet-0d0b0212d14b70250", target.Labels["SubnetId"]) + assert.Equal(t, "5", target.Labels["TaskRevision"]) + assert.Equal(t, "vpc-03e9f55a92516a5e4", target.Labels["VpcId"]) + assert.Equal(t, "/metrics", target.Labels["__metrics_path__"]) + assert.Equal(t, "bugbash-tomcat-prometheus-workload-java-ec2-bridge-mapped-port", target.Labels["container_name"]) +} + +func TestAddPrometheusTargets_Mixed_Fargate(t *testing.T) { + fullTask := buildWorkloadFargateAwsvpc(true, true) + log.Print(fullTask) + assert.Equal(t, "10.0.0.129", fullTask.getPrivateIp()) + config := &Config{ + DockerLabel: &DockerLabelConfig{ + JobNameLabel: "FARGATE_PROMETHEUS_JOB_NAME", + PortLabel: "FARGATE_PROMETHEUS_EXPORTER_PORT", + MetricsPathLabel: "ECS_PROMETHEUS_METRICS_PATH", + }, + TaskDefinitions: []*TaskDefinitionConfig{ + { + JobName: "", + MetricsPorts: "9404;9406", + TaskDefArnPattern: ".*:task-definition/prometheus-java-tomcat-fargate-awsvpc:[0-9]+", + MetricsPath: "/stats/metrics", + }, + }, + } + config.TaskDefinitions[0].init() + assert.Equal(t, []int{9404, 9406}, config.TaskDefinitions[0].metricsPortList) + + targets := make(map[string]*PrometheusTarget) + fullTask.addPrometheusTargets(targets, config) + assert.Equal(t, 4, len(targets)) + _, ok := targets["10.0.0.129:9404/stats/metrics"] + assert.True(t, ok, "Missing target: 10.0.0.129:9404/stats/metrics") + _, ok = targets["10.0.0.129:9406/stats/metrics"] + assert.True(t, ok, "Missing target: 10.0.0.129:9406/stats/metrics") + _, ok = targets["10.0.0.129:9404/metrics"] + assert.True(t, ok, "Missing target: 10.0.0.129:9404/metrics") + _, ok = targets["10.0.0.129:9406/metrics"] + assert.True(t, ok, "Missing target: 10.0.0.129:9406/metrics") +} + +func TestAddPrometheusTargets_Mixed_EC2(t *testing.T) { + fullTask := buildWorkloadEC2BridgeDynamicPort(true, true) + assert.Equal(t, "10.4.0.205", fullTask.getPrivateIp()) + config := &Config{ + DockerLabel: &DockerLabelConfig{ + JobNameLabel: "EC2_PROMETHEUS_JOB_NAME", + PortLabel: "EC2_PROMETHEUS_EXPORTER_PORT", + MetricsPathLabel: "ECS_PROMETHEUS_METRICS_PATH", + }, + TaskDefinitions: []*TaskDefinitionConfig{ + { + JobName: "", + MetricsPorts: "9404;9406", + TaskDefArnPattern: ".*:task-definition/prometheus-java-tomcat-ec2-awsvpc:[0-9]+", + MetricsPath: "/metrics", + }, + }, + } + config.TaskDefinitions[0].init() + assert.Equal(t, []int{9404, 9406}, config.TaskDefinitions[0].metricsPortList) + + targets := make(map[string]*PrometheusTarget) + fullTask.addPrometheusTargets(targets, config) + + assert.Equal(t, 2, len(targets)) + target, ok := targets["10.4.0.205:32774/metrics"] + assert.True(t, ok, "Missing target: 10.4.0.205:32774/metrics") + + assert.Equal(t, 8, len(target.Labels)) + assert.Equal(t, "/metrics", target.Labels["EC2_PROMETHEUS_METRICS_PATH"]) + assert.Equal(t, "9406", target.Labels["EC2_PROMETHEUS_EXPORTER_PORT"]) + assert.Equal(t, "t3.medium", target.Labels["InstanceType"]) + assert.Equal(t, "subnet-0d0b0212d14b70250", target.Labels["SubnetId"]) + assert.Equal(t, "5", target.Labels["TaskRevision"]) + assert.Equal(t, "vpc-03e9f55a92516a5e4", target.Labels["VpcId"]) + assert.Equal(t, "/metrics", target.Labels["__metrics_path__"]) + assert.Equal(t, "bugbash-jar-prometheus-workload-java-ec2-bridge", target.Labels["container_name"]) + + target, ok = targets["10.4.0.205:9494/metrics"] + assert.True(t, ok, "Missing target: 10.4.0.205:9494/metrics") + assert.Equal(t, 8, len(target.Labels)) + assert.Equal(t, "9404", target.Labels["EC2_PROMETHEUS_EXPORTER_PORT"]) + assert.Equal(t, "bugbash-tomcat-ec2-bridge-mapped-port", target.Labels["EC2_PROMETHEUS_JOB_NAME"]) + assert.Equal(t, "t3.medium", target.Labels["InstanceType"]) + assert.Equal(t, "subnet-0d0b0212d14b70250", target.Labels["SubnetId"]) + assert.Equal(t, "5", target.Labels["TaskRevision"]) + assert.Equal(t, "vpc-03e9f55a92516a5e4", target.Labels["VpcId"]) + assert.Equal(t, "bugbash-tomcat-prometheus-workload-java-ec2-bridge-mapped-port", target.Labels["container_name"]) + assert.Equal(t, "bugbash-tomcat-ec2-bridge-mapped-port", target.Labels["job"]) +} + +func TestAddTargetLabel(t *testing.T) { + labels := make(map[string]string) + var labelValueB string + labelValueC := "label_value_c" + addTargetLabel(labels, "Key_A", nil) + addTargetLabel(labels, "Key_B", &labelValueB) + addTargetLabel(labels, "Key_C", &labelValueC) + expected := map[string]string{"Key_C": "label_value_c"} + assert.True(t, reflect.DeepEqual(labels, expected)) +} diff --git a/extension/observer/ecsobserver/go.mod b/extension/observer/ecsobserver/go.mod index 7e19c337875f..69e5479719c5 100644 --- a/extension/observer/ecsobserver/go.mod +++ b/extension/observer/ecsobserver/go.mod @@ -9,6 +9,7 @@ require ( github.com/stretchr/testify v1.6.1 go.opentelemetry.io/collector v0.17.0 go.uber.org/zap v1.16.0 + gopkg.in/yaml.v2 v2.4.0 ) replace github.com/open-telemetry/opentelemetry-collector-contrib/extension/observer => ../ diff --git a/extension/observer/ecsobserver/servicediscovery.go b/extension/observer/ecsobserver/servicediscovery.go index 31666f46b376..725bffb4b713 100644 --- a/extension/observer/ecsobserver/servicediscovery.go +++ b/extension/observer/ecsobserver/servicediscovery.go @@ -50,7 +50,9 @@ func (sd *serviceDiscovery) init() { func (sd *serviceDiscovery) initProcessors() { sd.processors = []Processor{ NewTaskRetrievalProcessor(sd.svcEcs, sd.config.logger), + NewTaskDefinitionProcessor(sd.svcEcs, sd.config.logger), NewMetadataProcessor(sd.svcEcs, sd.svcEc2, sd.config.logger), + NewTargetProcessor(sd.svcEcs, sd.svcEc2, sd.config.logger), } } diff --git a/extension/observer/ecsobserver/targetprocessor.go b/extension/observer/ecsobserver/targetprocessor.go new file mode 100644 index 000000000000..e7d3aac47761 --- /dev/null +++ b/extension/observer/ecsobserver/targetprocessor.go @@ -0,0 +1,80 @@ +// Copyright The 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 ecsobserver + +import ( + "fmt" + "gopkg.in/yaml.v2" + "io/ioutil" + "os" +) + +type PrometheusTarget struct { + Targets []string `yaml:"targets"` + Labels map[string]string `yaml:"labels"` +} + +// TargetProcessor converts ECS tasks to Prometheus targets. +type TargetProcessor struct { + tmpResultFilePath string + config *Config +} + +func (p *TargetProcessor) ProcessorName() string { + return "TargetProcessor" +} + +// Process generates the scrape targets from discovered tasks. +func (p *TargetProcessor) Process(clusterName string, taskList []*ECSTask) ([]*ECSTask, error) { + // Dedup Key for Targets: target + metricsPath + // e.g. 10.0.0.28:9404/metrics + // 10.0.0.28:9404/stats/metrics + targets := make(map[string]*PrometheusTarget) + for _, t := range taskList { + t.addPrometheusTargets(targets, p.config) + } + + targetsArr := make([]*PrometheusTarget, len(targets)) + idx := 0 + for _, target := range targets { + targetsArr[idx] = target + idx++ + } + + m, err := yaml.Marshal(targetsArr) + if err != nil { + return nil, fmt.Errorf("Failed to marshal Prometheus targets. Error: %s", err.Error()) + } + + err = ioutil.WriteFile(p.tmpResultFilePath, m, 0644) + if err != nil { + return nil, fmt.Errorf("Failed to marshal Prometheus targets into file: %s. Error: %s", p.tmpResultFilePath, err.Error()) + } + + err = os.Rename(p.tmpResultFilePath, p.config.ResultFile) + if err != nil { + os.Remove(p.tmpResultFilePath) + return nil, fmt.Errorf("Failed to rename tmp result file %s to %s. Error: %s", p.tmpResultFilePath, p.config.ResultFile, err.Error()) + } + + return nil, nil +} + +func NewTargetProcessor(config *Config) *TargetProcessor { + return &TargetProcessor{ + tmpResultFilePath: config.ResultFile + "_temp", + config: config, + } +} diff --git a/extension/observer/ecsobserver/taskdefinitionprocessor.go b/extension/observer/ecsobserver/taskdefinitionprocessor.go index 9f17772ee59e..981cb1d7e020 100644 --- a/extension/observer/ecsobserver/taskdefinitionprocessor.go +++ b/extension/observer/ecsobserver/taskdefinitionprocessor.go @@ -60,7 +60,7 @@ func (p *TaskDefinitionProcessor) Process(cluster string, taskList []*ECSTask) ( if err != nil { return taskList, fmt.Errorf("Failed to describe task definition for %s. Error: %s", arn, err.Error()) } - + if taskDef := resp.TaskDefinition; taskDef != nil { task.TaskDefinition = taskDef p.taskDefCache.Add(arn, taskDef) From 921921fe3ba57c3fab14eb6b5f2390b479381b17 Mon Sep 17 00:00:00 2001 From: Raphael Koh Date: Wed, 23 Dec 2020 16:25:35 -0500 Subject: [PATCH 09/19] Implement targets to endpoints conversion Fix --- extension/observer/ecsobserver/ecstask.go | 56 +-- .../observer/ecsobserver/ecstask_test.go | 428 ++++++++++-------- extension/observer/ecsobserver/extension.go | 62 ++- .../observer/ecsobserver/servicediscovery.go | 71 ++- .../observer/ecsobserver/targetprocessor.go | 80 ---- extension/observer/endpoints.go | 15 + 6 files changed, 409 insertions(+), 303 deletions(-) delete mode 100644 extension/observer/ecsobserver/targetprocessor.go diff --git a/extension/observer/ecsobserver/ecstask.go b/extension/observer/ecsobserver/ecstask.go index 3be5c518ff6a..83055c4be3b1 100644 --- a/extension/observer/ecsobserver/ecstask.go +++ b/extension/observer/ecsobserver/ecstask.go @@ -16,7 +16,6 @@ package ecsobserver import ( "fmt" - "regexp" "strconv" "github.com/aws/aws-sdk-go/aws" @@ -35,17 +34,8 @@ const ( ec2InstanceTypeLabel = "InstanceType" ec2VpcIdLabel = "VpcId" ec2SubnetIdLabel = "SubnetId" - - //https://prometheus.io/docs/prometheus/latest/configuration/configuration/#scrape_config - defaultPrometheusMetricsPath = "/metrics" - - // Prometheus definition: a string matching the regular expression [a-zA-Z_][a-zA-Z0-9_]* - // Regex pattern to filter out invalid labels - prometheusLabelNamePattern = "^[a-zA-Z_][a-zA-Z0-9_]*$" ) -var prometheusLabelNameRegex = regexp.MustCompile(prometheusLabelNamePattern) - type EC2MetaData struct { ContainerInstanceId string ECInstanceId string @@ -64,7 +54,7 @@ type ECSTask struct { TaskDefinitionBased bool } -func (t *ECSTask) addPrometheusTargets(targets map[string]*PrometheusTarget, config *Config) { +func (t *ECSTask) addTargets(targets map[string]*Target, config *Config) { ip := t.getPrivateIp() if ip == "" { return @@ -102,8 +92,8 @@ func (t *ECSTask) getPrivateIp() (ip string) { return } -// addDockerLabelBasedTarget adds a Prometheus target based on docker labels. -func (t *ECSTask) addDockerLabelBasedTarget(ip string, c *ecs.ContainerDefinition, targets map[string]*PrometheusTarget, config *Config) { +// addDockerLabelBasedTarget adds a target based on docker labels. +func (t *ECSTask) addDockerLabelBasedTarget(ip string, c *ecs.ContainerDefinition, targets map[string]*Target, config *Config) { if !t.DockerLabelBased { return } @@ -125,11 +115,9 @@ func (t *ECSTask) addDockerLabelBasedTarget(ip string, c *ecs.ContainerDefinitio return } - metricsPath := defaultPrometheusMetricsPath - metricsPathLabel := "" + metricsPath := "" if v, ok := c.DockerLabels[config.DockerLabel.MetricsPathLabel]; ok { metricsPath = aws.StringValue(v) - metricsPathLabel = metricsPath } targetAddr := fmt.Sprintf("%s:%d", ip, hostPort) @@ -143,11 +131,15 @@ func (t *ECSTask) addDockerLabelBasedTarget(ip string, c *ecs.ContainerDefinitio customizedJobName = aws.StringValue(jobName) } - targets[endpoint] = t.generatePrometheusTarget(c, targetAddr, metricsPathLabel, customizedJobName) + targets[endpoint] = &Target{ + Address: targetAddr, + MetricsPath: metricsPath, + Labels: t.generateTargetLabels(c, metricsPath, customizedJobName), + } } -// addTaskDefinitionBasedTargets adds Prometheus targets based on task definition. -func (t *ECSTask) addTaskDefinitionBasedTargets(ip string, c *ecs.ContainerDefinition, targets map[string]*PrometheusTarget, config *Config) { +// addTaskDefinitionBasedTargets adds targets based on task definition. +func (t *ECSTask) addTaskDefinitionBasedTargets(ip string, c *ecs.ContainerDefinition, targets map[string]*Target, config *Config) { if !t.TaskDefinitionBased { return } @@ -170,15 +162,14 @@ func (t *ECSTask) addTaskDefinitionBasedTargets(ip string, c *ecs.ContainerDefin continue } - metricsPath := defaultPrometheusMetricsPath - if taskDef.MetricsPath != "" { - metricsPath = taskDef.MetricsPath - } - targetAddr := fmt.Sprintf("%s:%d", ip, hostPort) - endpoint := targetAddr + metricsPath + endpoint := targetAddr + taskDef.MetricsPath if _, ok := targets[endpoint]; !ok { - targets[endpoint] = t.generatePrometheusTarget(c, targetAddr, taskDef.MetricsPath, taskDef.JobName) + targets[endpoint] = &Target{ + Address: targetAddr, + MetricsPath: taskDef.MetricsPath, + Labels: t.generateTargetLabels(c, taskDef.MetricsPath, taskDef.JobName), + } } } @@ -218,8 +209,8 @@ func (t *ECSTask) getHostPort(containerPort int64, c *ecs.ContainerDefinition) i return 0 } -// generatePrometheusTarget creates a Prometheus target with labels. -func (t *ECSTask) generatePrometheusTarget(c *ecs.ContainerDefinition, targetAddr string, metricsPath string, jobName string) *PrometheusTarget { +// generateTargetLabels creates labels for discovered target. +func (t *ECSTask) generateTargetLabels(c *ecs.ContainerDefinition, metricsPath string, jobName string) map[string]string { labels := make(map[string]string) revisionStr := fmt.Sprintf("%d", *t.TaskDefinition.Revision) @@ -238,18 +229,13 @@ func (t *ECSTask) generatePrometheusTarget(c *ecs.ContainerDefinition, targetAdd } for k, v := range c.DockerLabels { - if prometheusLabelNameRegex.MatchString(k) { - addTargetLabel(labels, k, v) - } + addTargetLabel(labels, k, v) } // handle customized job label last, so the previous job docker label is overriden addTargetLabel(labels, taskJobNameLabel, &jobName) - return &PrometheusTarget{ - Targets: []string{targetAddr}, - Labels: labels, - } + return labels } // addTargetLabel adds a label to the labels map if the given label value is valid. diff --git a/extension/observer/ecsobserver/ecstask_test.go b/extension/observer/ecsobserver/ecstask_test.go index 3d2145cf460b..e0658389762b 100644 --- a/extension/observer/ecsobserver/ecstask_test.go +++ b/extension/observer/ecsobserver/ecstask_test.go @@ -15,7 +15,6 @@ package ecsobserver import ( - "log" "reflect" "testing" @@ -40,7 +39,7 @@ func buildWorkloadFargateAwsvpc(dockerLabel bool, taskDef bool) *ECSTask { var port9404Int64 int64 = 9404 var port9406Int64 int64 = 9406 containerNameTomcat := "bugbash-tomcat-fargate-awsvpc-with-docker-label" - containerNameJar := "bugbash-jar-fargate-awsvpc-with-dockerlabel" + containerNameJar := "bugbash-jar-fargate-awsvpc-with-docker-label" jobNameLabel := "java-tomcat-fargate-awsvpc" metricsPathLabel := "/metrics" @@ -197,201 +196,258 @@ func buildWorkloadEC2BridgeDynamicPort(dockerLabel bool, taskDef bool) *ECSTask } } -func TestAddPrometheusTargets_DockerLabelBased(t *testing.T) { - fullTask := buildWorkloadFargateAwsvpc(true, false) - assert.Equal(t, "10.0.0.129", fullTask.getPrivateIp()) - - config := &Config{ - DockerLabel: &DockerLabelConfig{ - JobNameLabel: "FARGATE_PROMETHEUS_JOB_NAME", - PortLabel: "FARGATE_PROMETHEUS_EXPORTER_PORT", - MetricsPathLabel: "ECS_PROMETHEUS_METRICS_PATH", - }, - } - - targets := make(map[string]*PrometheusTarget) - fullTask.addPrometheusTargets(targets, config) - - assert.Equal(t, 2, len(targets)) - target, ok := targets["10.0.0.129:9404/metrics"] - assert.True(t, ok, "Missing target: 10.0.0.129:9404/metrics") - - assert.Equal(t, 5, len(target.Labels)) - assert.Equal(t, "java-tomcat-fargate-awsvpc", target.Labels["job"]) - assert.Equal(t, "bugbash-tomcat-fargate-awsvpc-with-docker-label", target.Labels["container_name"]) - assert.Equal(t, "4", target.Labels["TaskRevision"]) - assert.Equal(t, "9404", target.Labels["FARGATE_PROMETHEUS_EXPORTER_PORT"]) - assert.Equal(t, "java-tomcat-fargate-awsvpc", target.Labels["FARGATE_PROMETHEUS_JOB_NAME"]) - - target, ok = targets["10.0.0.129:9406/metrics"] - assert.True(t, ok, "Missing target: 10.0.0.129:9406/metrics") - assert.Equal(t, 5, len(target.Labels)) - assert.Equal(t, "4", target.Labels["TaskRevision"]) - assert.Equal(t, "bugbash-jar-fargate-awsvpc-with-dockerlabel", target.Labels["container_name"]) - assert.Equal(t, "9406", target.Labels["FARGATE_PROMETHEUS_EXPORTER_PORT"]) - assert.Equal(t, "/metrics", target.Labels["__metrics_path__"]) - assert.Equal(t, "/metrics", target.Labels["ECS_PROMETHEUS_METRICS_PATH"]) -} - -func TestAddPrometheusTargets_TaskDefBased_Fargate(t *testing.T) { - fullTask := buildWorkloadFargateAwsvpc(false, true) - assert.Equal(t, "10.0.0.129", fullTask.getPrivateIp()) - config := &Config{ - TaskDefinitions: []*TaskDefinitionConfig{ - { - JobName: "", - MetricsPorts: "9404;9406", - TaskDefArnPattern: ".*:task-definition/prometheus-java-tomcat-fargate-awsvpc:[0-9]+", - MetricsPath: "/stats/metrics", +func TestAddTargets(t *testing.T) { + testCases := []struct { + testName string + task *ECSTask + config *Config + expectedTargets map[string]*Target + }{ + { + "docker label-based", + buildWorkloadFargateAwsvpc(true, false), + &Config{ + DockerLabel: &DockerLabelConfig{ + JobNameLabel: "FARGATE_PROMETHEUS_JOB_NAME", + PortLabel: "FARGATE_PROMETHEUS_EXPORTER_PORT", + MetricsPathLabel: "ECS_PROMETHEUS_METRICS_PATH", + }, }, - }, - } - config.TaskDefinitions[0].init() - assert.Equal(t, []int{9404, 9406}, config.TaskDefinitions[0].metricsPortList) - - targets := make(map[string]*PrometheusTarget) - fullTask.addPrometheusTargets(targets, config) - - assert.Equal(t, 2, len(targets)) - target, ok := targets["10.0.0.129:9404/stats/metrics"] - assert.True(t, ok, "Missing target: 10.0.0.129:9404/stats/metrics") - - assert.Equal(t, 5, len(target.Labels)) - assert.Equal(t, "java-tomcat-fargate-awsvpc", target.Labels["FARGATE_PROMETHEUS_JOB_NAME"]) - assert.Equal(t, "bugbash-tomcat-fargate-awsvpc-with-docker-label", target.Labels["container_name"]) - assert.Equal(t, "4", target.Labels["TaskRevision"]) - assert.Equal(t, "9404", target.Labels["FARGATE_PROMETHEUS_EXPORTER_PORT"]) - assert.Equal(t, "/stats/metrics", target.Labels["__metrics_path__"]) - - target, ok = targets["10.0.0.129:9406/stats/metrics"] - assert.True(t, ok, "Missing target: 10.0.0.129:9406/stats/metrics") - assert.Equal(t, 5, len(target.Labels)) - assert.Equal(t, "4", target.Labels["TaskRevision"]) - assert.Equal(t, "bugbash-jar-fargate-awsvpc-with-dockerlabel", target.Labels["container_name"]) - assert.Equal(t, "9406", target.Labels["FARGATE_PROMETHEUS_EXPORTER_PORT"]) - assert.Equal(t, "/stats/metrics", target.Labels["__metrics_path__"]) - assert.Equal(t, "/metrics", target.Labels["ECS_PROMETHEUS_METRICS_PATH"]) -} - -func TestAddPrometheusTargets_TaskDefBased_EC2(t *testing.T) { - fullTask := buildWorkloadEC2BridgeDynamicPort(false, true) - assert.Equal(t, "10.4.0.205", fullTask.getPrivateIp()) - config := &Config{ - TaskDefinitions: []*TaskDefinitionConfig{ - { - JobName: "", - MetricsPorts: "9404;9406", - TaskDefArnPattern: ".*:task-definition/prometheus-java-tomcat-ec2-awsvpc:[0-9]+", - MetricsPath: "/metrics", - ContainerNamePattern: ".*tomcat-prometheus-workload-java-ec2.*", + map[string]*Target{ + "10.0.0.129:9404": &Target{ + Address: "10.0.0.129:9404", + MetricsPath: "", + Labels: map[string]string{ + "job": "java-tomcat-fargate-awsvpc", + "container_name": "bugbash-tomcat-fargate-awsvpc-with-docker-label", + "TaskRevision": "4", + "FARGATE_PROMETHEUS_EXPORTER_PORT": "9404", + "FARGATE_PROMETHEUS_JOB_NAME": "java-tomcat-fargate-awsvpc", + }, + }, + "10.0.0.129:9406/metrics": &Target{ + Address: "10.0.0.129:9406", + MetricsPath: "/metrics", + Labels: map[string]string{ + "container_name": "bugbash-jar-fargate-awsvpc-with-docker-label", + "TaskRevision": "4", + "FARGATE_PROMETHEUS_EXPORTER_PORT": "9406", + "__metrics_path__": "/metrics", + "ECS_PROMETHEUS_METRICS_PATH": "/metrics", + }, + }, }, }, - } - config.TaskDefinitions[0].init() - assert.Equal(t, []int{9404, 9406}, config.TaskDefinitions[0].metricsPortList) - - targets := make(map[string]*PrometheusTarget) - fullTask.addPrometheusTargets(targets, config) - - assert.Equal(t, 1, len(targets)) - target, ok := targets["10.4.0.205:9494/metrics"] - log.Print(target) - assert.True(t, ok, "Missing target: 10.4.0.205:9494/metrics") - assert.Equal(t, 8, len(target.Labels)) - assert.Equal(t, "9404", target.Labels["EC2_PROMETHEUS_EXPORTER_PORT"]) - assert.Equal(t, "bugbash-tomcat-ec2-bridge-mapped-port", target.Labels["EC2_PROMETHEUS_JOB_NAME"]) - assert.Equal(t, "t3.medium", target.Labels["InstanceType"]) - assert.Equal(t, "subnet-0d0b0212d14b70250", target.Labels["SubnetId"]) - assert.Equal(t, "5", target.Labels["TaskRevision"]) - assert.Equal(t, "vpc-03e9f55a92516a5e4", target.Labels["VpcId"]) - assert.Equal(t, "/metrics", target.Labels["__metrics_path__"]) - assert.Equal(t, "bugbash-tomcat-prometheus-workload-java-ec2-bridge-mapped-port", target.Labels["container_name"]) -} - -func TestAddPrometheusTargets_Mixed_Fargate(t *testing.T) { - fullTask := buildWorkloadFargateAwsvpc(true, true) - log.Print(fullTask) - assert.Equal(t, "10.0.0.129", fullTask.getPrivateIp()) - config := &Config{ - DockerLabel: &DockerLabelConfig{ - JobNameLabel: "FARGATE_PROMETHEUS_JOB_NAME", - PortLabel: "FARGATE_PROMETHEUS_EXPORTER_PORT", - MetricsPathLabel: "ECS_PROMETHEUS_METRICS_PATH", + { + "task definition-based, fargate", + buildWorkloadFargateAwsvpc(false, true), + &Config{ + TaskDefinitions: []*TaskDefinitionConfig{ + { + JobName: "", + MetricsPorts: "9404;9406", + TaskDefArnPattern: ".*:task-definition/prometheus-java-tomcat-fargate-awsvpc:[0-9]+", + MetricsPath: "/stats/metrics", + }, + }, + }, + map[string]*Target{ + "10.0.0.129:9404/stats/metrics": &Target{ + Address: "10.0.0.129:9404", + MetricsPath: "/stats/metrics", + Labels: map[string]string{ + "container_name": "bugbash-tomcat-fargate-awsvpc-with-docker-label", + "TaskRevision": "4", + "FARGATE_PROMETHEUS_EXPORTER_PORT": "9404", + "__metrics_path__": "/stats/metrics", + "FARGATE_PROMETHEUS_JOB_NAME": "java-tomcat-fargate-awsvpc", + }, + }, + "10.0.0.129:9406/stats/metrics": &Target{ + Address: "10.0.0.129:9406", + MetricsPath: "/stats/metrics", + Labels: map[string]string{ + "container_name": "bugbash-jar-fargate-awsvpc-with-docker-label", + "TaskRevision": "4", + "FARGATE_PROMETHEUS_EXPORTER_PORT": "9406", + "__metrics_path__": "/stats/metrics", + "ECS_PROMETHEUS_METRICS_PATH": "/metrics", + }, + }, + }, }, - TaskDefinitions: []*TaskDefinitionConfig{ - { - JobName: "", - MetricsPorts: "9404;9406", - TaskDefArnPattern: ".*:task-definition/prometheus-java-tomcat-fargate-awsvpc:[0-9]+", - MetricsPath: "/stats/metrics", + { + "task definition-based, ec2", + buildWorkloadEC2BridgeDynamicPort(false, true), + &Config{ + TaskDefinitions: []*TaskDefinitionConfig{ + { + JobName: "", + MetricsPorts: "9404;9406", + TaskDefArnPattern: ".*:task-definition/prometheus-java-tomcat-ec2-awsvpc:[0-9]+", + MetricsPath: "/metrics", + ContainerNamePattern: ".*tomcat-prometheus-workload-java-ec2.*", + }, + }, + }, + map[string]*Target{ + "10.4.0.205:9494/metrics": &Target{ + Address: "10.4.0.205:9494", + MetricsPath: "/metrics", + Labels: map[string]string{ + "container_name": "bugbash-tomcat-prometheus-workload-java-ec2-bridge-mapped-port", + "TaskRevision": "5", + "VpcId": "vpc-03e9f55a92516a5e4", + "EC2_PROMETHEUS_EXPORTER_PORT": "9404", + "__metrics_path__": "/metrics", + "EC2_PROMETHEUS_JOB_NAME": "bugbash-tomcat-ec2-bridge-mapped-port", + "InstanceType": "t3.medium", + "SubnetId": "subnet-0d0b0212d14b70250", + }, + }, }, }, - } - config.TaskDefinitions[0].init() - assert.Equal(t, []int{9404, 9406}, config.TaskDefinitions[0].metricsPortList) - - targets := make(map[string]*PrometheusTarget) - fullTask.addPrometheusTargets(targets, config) - assert.Equal(t, 4, len(targets)) - _, ok := targets["10.0.0.129:9404/stats/metrics"] - assert.True(t, ok, "Missing target: 10.0.0.129:9404/stats/metrics") - _, ok = targets["10.0.0.129:9406/stats/metrics"] - assert.True(t, ok, "Missing target: 10.0.0.129:9406/stats/metrics") - _, ok = targets["10.0.0.129:9404/metrics"] - assert.True(t, ok, "Missing target: 10.0.0.129:9404/metrics") - _, ok = targets["10.0.0.129:9406/metrics"] - assert.True(t, ok, "Missing target: 10.0.0.129:9406/metrics") -} - -func TestAddPrometheusTargets_Mixed_EC2(t *testing.T) { - fullTask := buildWorkloadEC2BridgeDynamicPort(true, true) - assert.Equal(t, "10.4.0.205", fullTask.getPrivateIp()) - config := &Config{ - DockerLabel: &DockerLabelConfig{ - JobNameLabel: "EC2_PROMETHEUS_JOB_NAME", - PortLabel: "EC2_PROMETHEUS_EXPORTER_PORT", - MetricsPathLabel: "ECS_PROMETHEUS_METRICS_PATH", + { + "mixed targets, fargate", + buildWorkloadFargateAwsvpc(true, true), + &Config{ + DockerLabel: &DockerLabelConfig{ + JobNameLabel: "FARGATE_PROMETHEUS_JOB_NAME", + PortLabel: "FARGATE_PROMETHEUS_EXPORTER_PORT", + MetricsPathLabel: "ECS_PROMETHEUS_METRICS_PATH", + }, + TaskDefinitions: []*TaskDefinitionConfig{ + { + JobName: "", + MetricsPorts: "9404;9406", + TaskDefArnPattern: ".*:task-definition/prometheus-java-tomcat-fargate-awsvpc:[0-9]+", + MetricsPath: "/stats/metrics", + }, + }, + }, + map[string]*Target{ + "10.0.0.129:9404": &Target{ + Address: "10.0.0.129:9404", + MetricsPath: "", + Labels: map[string]string{ + "job": "java-tomcat-fargate-awsvpc", + "container_name": "bugbash-tomcat-fargate-awsvpc-with-docker-label", + "TaskRevision": "4", + "FARGATE_PROMETHEUS_EXPORTER_PORT": "9404", + "FARGATE_PROMETHEUS_JOB_NAME": "java-tomcat-fargate-awsvpc", + }, + }, + "10.0.0.129:9406/metrics": &Target{ + Address: "10.0.0.129:9406", + MetricsPath: "/metrics", + Labels: map[string]string{ + "container_name": "bugbash-jar-fargate-awsvpc-with-docker-label", + "TaskRevision": "4", + "FARGATE_PROMETHEUS_EXPORTER_PORT": "9406", + "__metrics_path__": "/metrics", + "ECS_PROMETHEUS_METRICS_PATH": "/metrics", + }, + }, + "10.0.0.129:9404/stats/metrics": &Target{ + Address: "10.0.0.129:9404", + MetricsPath: "/stats/metrics", + Labels: map[string]string{ + "container_name": "bugbash-tomcat-fargate-awsvpc-with-docker-label", + "TaskRevision": "4", + "FARGATE_PROMETHEUS_EXPORTER_PORT": "9404", + "__metrics_path__": "/stats/metrics", + "FARGATE_PROMETHEUS_JOB_NAME": "java-tomcat-fargate-awsvpc", + }, + }, + "10.0.0.129:9406/stats/metrics": &Target{ + Address: "10.0.0.129:9406", + MetricsPath: "/stats/metrics", + Labels: map[string]string{ + "container_name": "bugbash-jar-fargate-awsvpc-with-docker-label", + "TaskRevision": "4", + "FARGATE_PROMETHEUS_EXPORTER_PORT": "9406", + "__metrics_path__": "/stats/metrics", + "ECS_PROMETHEUS_METRICS_PATH": "/metrics", + }, + }, + }, }, - TaskDefinitions: []*TaskDefinitionConfig{ - { - JobName: "", - MetricsPorts: "9404;9406", - TaskDefArnPattern: ".*:task-definition/prometheus-java-tomcat-ec2-awsvpc:[0-9]+", - MetricsPath: "/metrics", + { + "mixed targets, ec2", + buildWorkloadEC2BridgeDynamicPort(true, true), + &Config{ + DockerLabel: &DockerLabelConfig{ + JobNameLabel: "EC2_PROMETHEUS_JOB_NAME", + PortLabel: "EC2_PROMETHEUS_EXPORTER_PORT", + MetricsPathLabel: "ECS_PROMETHEUS_METRICS_PATH", + }, + TaskDefinitions: []*TaskDefinitionConfig{ + { + JobName: "", + MetricsPorts: "9404;9406", + TaskDefArnPattern: ".*:task-definition/prometheus-java-tomcat-ec2-awsvpc:[0-9]+", + MetricsPath: "/metrics", + }, + }, + }, + map[string]*Target{ + "10.4.0.205:32774/metrics": &Target{ + Address: "10.4.0.205:32774", + MetricsPath: "/metrics", + Labels: map[string]string{ + "InstanceType": "t3.medium", + "SubnetId": "subnet-0d0b0212d14b70250", + "VpcId": "vpc-03e9f55a92516a5e4", + "container_name": "bugbash-jar-prometheus-workload-java-ec2-bridge", + "TaskRevision": "5", + "EC2_PROMETHEUS_EXPORTER_PORT": "9406", + "EC2_PROMETHEUS_METRICS_PATH": "/metrics", + "__metrics_path__": "/metrics", + }, + }, + "10.4.0.205:9494": &Target{ + Address: "10.4.0.205:9494", + MetricsPath: "", + Labels: map[string]string{ + "job": "bugbash-tomcat-ec2-bridge-mapped-port", + "InstanceType": "t3.medium", + "SubnetId": "subnet-0d0b0212d14b70250", + "VpcId": "vpc-03e9f55a92516a5e4", + "container_name": "bugbash-tomcat-prometheus-workload-java-ec2-bridge-mapped-port", + "TaskRevision": "5", + "EC2_PROMETHEUS_JOB_NAME": "bugbash-tomcat-ec2-bridge-mapped-port", + "EC2_PROMETHEUS_EXPORTER_PORT": "9404", + }, + }, + "10.4.0.205:9494/metrics": &Target{ + Address: "10.4.0.205:9494", + MetricsPath: "/metrics", + Labels: map[string]string{ + "InstanceType": "t3.medium", + "SubnetId": "subnet-0d0b0212d14b70250", + "VpcId": "vpc-03e9f55a92516a5e4", + "container_name": "bugbash-tomcat-prometheus-workload-java-ec2-bridge-mapped-port", + "TaskRevision": "5", + "EC2_PROMETHEUS_JOB_NAME": "bugbash-tomcat-ec2-bridge-mapped-port", + "EC2_PROMETHEUS_EXPORTER_PORT": "9404", + "__metrics_path__": "/metrics", + }, + }, }, }, } - config.TaskDefinitions[0].init() - assert.Equal(t, []int{9404, 9406}, config.TaskDefinitions[0].metricsPortList) - targets := make(map[string]*PrometheusTarget) - fullTask.addPrometheusTargets(targets, config) - - assert.Equal(t, 2, len(targets)) - target, ok := targets["10.4.0.205:32774/metrics"] - assert.True(t, ok, "Missing target: 10.4.0.205:32774/metrics") - - assert.Equal(t, 8, len(target.Labels)) - assert.Equal(t, "/metrics", target.Labels["EC2_PROMETHEUS_METRICS_PATH"]) - assert.Equal(t, "9406", target.Labels["EC2_PROMETHEUS_EXPORTER_PORT"]) - assert.Equal(t, "t3.medium", target.Labels["InstanceType"]) - assert.Equal(t, "subnet-0d0b0212d14b70250", target.Labels["SubnetId"]) - assert.Equal(t, "5", target.Labels["TaskRevision"]) - assert.Equal(t, "vpc-03e9f55a92516a5e4", target.Labels["VpcId"]) - assert.Equal(t, "/metrics", target.Labels["__metrics_path__"]) - assert.Equal(t, "bugbash-jar-prometheus-workload-java-ec2-bridge", target.Labels["container_name"]) - - target, ok = targets["10.4.0.205:9494/metrics"] - assert.True(t, ok, "Missing target: 10.4.0.205:9494/metrics") - assert.Equal(t, 8, len(target.Labels)) - assert.Equal(t, "9404", target.Labels["EC2_PROMETHEUS_EXPORTER_PORT"]) - assert.Equal(t, "bugbash-tomcat-ec2-bridge-mapped-port", target.Labels["EC2_PROMETHEUS_JOB_NAME"]) - assert.Equal(t, "t3.medium", target.Labels["InstanceType"]) - assert.Equal(t, "subnet-0d0b0212d14b70250", target.Labels["SubnetId"]) - assert.Equal(t, "5", target.Labels["TaskRevision"]) - assert.Equal(t, "vpc-03e9f55a92516a5e4", target.Labels["VpcId"]) - assert.Equal(t, "bugbash-tomcat-prometheus-workload-java-ec2-bridge-mapped-port", target.Labels["container_name"]) - assert.Equal(t, "bugbash-tomcat-ec2-bridge-mapped-port", target.Labels["job"]) + for _, tc := range testCases { + t.Run(tc.testName, func(t *testing.T) { + for _, taskDef := range tc.config.TaskDefinitions { + taskDef.init() + assert.Equal(t, []int{9404, 9406}, taskDef.metricsPortList) + } + targets := make(map[string]*Target) + tc.task.addTargets(targets, tc.config) + assert.Equal(t, tc.expectedTargets, targets) + }) + } } func TestAddTargetLabel(t *testing.T) { diff --git a/extension/observer/ecsobserver/extension.go b/extension/observer/ecsobserver/extension.go index 758a8ab51cd6..a420917d65c5 100644 --- a/extension/observer/ecsobserver/extension.go +++ b/extension/observer/ecsobserver/extension.go @@ -16,9 +16,14 @@ package ecsobserver import ( "context" + "fmt" + "gopkg.in/yaml.v2" + "io/ioutil" + "os" "github.com/open-telemetry/opentelemetry-collector-contrib/extension/observer" "go.opentelemetry.io/collector/component" + "go.uber.org/zap" ) type ecsObserver struct { @@ -58,6 +63,61 @@ func (h *ecsObserver) Shutdown(context.Context) error { } func (e endpointsLister) ListEndpoints() []observer.Endpoint { - // TODO: Implement this + targets := e.sd.discoverTargets() + + // If there is a result file path configured, we output the scraped targets to a file + // instead of notifying listeners. + if filePath := e.sd.config.ResultFile; filePath != "" { + err := writeTargetsToFile(targets, filePath) + if err != nil { + e.sd.config.logger.Error( + "Failed to write targets to file", + zap.String("file path", filePath), + zap.String("error", err.Error()), + ) + } + return nil + } + + // Otherwise, convert to endpoints + endpoints := make([]observer.Endpoint, len(targets)) + idx := 0 + for _, target := range targets { + endpoints[idx] = *target.toEndpoint() + idx++ + } + + return endpoints +} + +// writeTargetsToFile writes targets to the given file path. +func writeTargetsToFile(targets map[string]*Target, filePath string) error { + tmpFilePath := filePath + "_temp" + + // Convert to serializable Prometheus targets + promTargets := make([]*PrometheusTarget, len(targets)) + idx := 0 + for _, target := range targets { + promTargets[idx] = target.toPrometheusTarget() + idx++ + } + + + m, err := yaml.Marshal(promTargets) + if err != nil { + return fmt.Errorf("Failed to marshal Prometheus targets. Error: %s", err.Error()) + } + + err = ioutil.WriteFile(tmpFilePath, m, 0644) + if err != nil { + return fmt.Errorf("Failed to marshal Prometheus targets into file: %s. Error: %s", tmpFilePath, err.Error()) + } + + err = os.Rename(tmpFilePath, filePath) + if err != nil { + os.Remove(tmpFilePath) + return fmt.Errorf("Failed to rename tmp result file %s to %s. Error: %s", tmpFilePath, filePath, err.Error()) + } + return nil } diff --git a/extension/observer/ecsobserver/servicediscovery.go b/extension/observer/ecsobserver/servicediscovery.go index 725bffb4b713..ad5ab18e319d 100644 --- a/extension/observer/ecsobserver/servicediscovery.go +++ b/extension/observer/ecsobserver/servicediscovery.go @@ -23,12 +23,55 @@ import ( "github.com/aws/aws-sdk-go/service/ec2" "github.com/aws/aws-sdk-go/service/ecs" "go.uber.org/zap" + + "github.com/open-telemetry/opentelemetry-collector-contrib/extension/observer" ) const ( AwsSdkLevelRetryCount = 3 + + //https://prometheus.io/docs/prometheus/latest/configuration/configuration/#scrape_config + defaultPrometheusMetricsPath = "/metrics" ) +// PrometheusTarget represents a discovered Prometheus target. +type PrometheusTarget struct { + Targets []string `yaml:"targets"` + Labels map[string]string `yaml:"labels"` +} + +// Target represents a discovered ECS endpoint. +type Target struct { + Address string + MetricsPath string + Labels map[string]string +} + +// toPrometheusTarget converts target into seriablizable Prometheus target. +func (t *Target) toPrometheusTarget() *PrometheusTarget { + metricsPath := t.MetricsPath + if metricsPath == "" { + metricsPath = defaultPrometheusMetricsPath + } + + return &PrometheusTarget{ + Targets: []string{t.Address + metricsPath}, + Labels: t.Labels, + } +} + +// toEndpoint converts target into seriablizable Prometheus target. +func (t *Target) toEndpoint() *observer.Endpoint { + return &observer.Endpoint{ + ID: observer.EndpointID(t.Address + t.MetricsPath), + Target: t.Address, + Details: observer.Task{ + MetricsPath: t.MetricsPath, + Labels: t.Labels, + }, + } +} + type serviceDiscovery struct { config *Config @@ -52,10 +95,36 @@ func (sd *serviceDiscovery) initProcessors() { NewTaskRetrievalProcessor(sd.svcEcs, sd.config.logger), NewTaskDefinitionProcessor(sd.svcEcs, sd.config.logger), NewMetadataProcessor(sd.svcEcs, sd.svcEc2, sd.config.logger), - NewTargetProcessor(sd.svcEcs, sd.svcEc2, sd.config.logger), } } +func (sd *serviceDiscovery) discoverTargets() map[string]*Target { + var taskList []*ECSTask + var err error + for _, p := range sd.processors { + taskList, err = p.Process(sd.config.ClusterName, taskList) + // Ignore partial result to avoid overwriting existing targets + if err != nil { + sd.config.logger.Error( + "ECS Service Discovery failed to discover targets", + zap.String("processor name", p.ProcessorName()), + zap.String("error", err.Error()), + ) + return nil + } + } + + // Dedup Key for Targets: target + metricsPath + // e.g. 10.0.0.28:9404/metrics + // 10.0.0.28:9404/stats/metrics + targets := make(map[string]*Target) + for _, t := range taskList { + t.addTargets(targets, sd.config) + } + + return targets +} + // getAWSRegion retrieves the AWS region from the provided config, env var, or EC2 metadata. func getAWSRegion(cfg *Config) (awsRegion string) { awsRegion = cfg.ClusterRegion diff --git a/extension/observer/ecsobserver/targetprocessor.go b/extension/observer/ecsobserver/targetprocessor.go deleted file mode 100644 index e7d3aac47761..000000000000 --- a/extension/observer/ecsobserver/targetprocessor.go +++ /dev/null @@ -1,80 +0,0 @@ -// Copyright The 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 ecsobserver - -import ( - "fmt" - "gopkg.in/yaml.v2" - "io/ioutil" - "os" -) - -type PrometheusTarget struct { - Targets []string `yaml:"targets"` - Labels map[string]string `yaml:"labels"` -} - -// TargetProcessor converts ECS tasks to Prometheus targets. -type TargetProcessor struct { - tmpResultFilePath string - config *Config -} - -func (p *TargetProcessor) ProcessorName() string { - return "TargetProcessor" -} - -// Process generates the scrape targets from discovered tasks. -func (p *TargetProcessor) Process(clusterName string, taskList []*ECSTask) ([]*ECSTask, error) { - // Dedup Key for Targets: target + metricsPath - // e.g. 10.0.0.28:9404/metrics - // 10.0.0.28:9404/stats/metrics - targets := make(map[string]*PrometheusTarget) - for _, t := range taskList { - t.addPrometheusTargets(targets, p.config) - } - - targetsArr := make([]*PrometheusTarget, len(targets)) - idx := 0 - for _, target := range targets { - targetsArr[idx] = target - idx++ - } - - m, err := yaml.Marshal(targetsArr) - if err != nil { - return nil, fmt.Errorf("Failed to marshal Prometheus targets. Error: %s", err.Error()) - } - - err = ioutil.WriteFile(p.tmpResultFilePath, m, 0644) - if err != nil { - return nil, fmt.Errorf("Failed to marshal Prometheus targets into file: %s. Error: %s", p.tmpResultFilePath, err.Error()) - } - - err = os.Rename(p.tmpResultFilePath, p.config.ResultFile) - if err != nil { - os.Remove(p.tmpResultFilePath) - return nil, fmt.Errorf("Failed to rename tmp result file %s to %s. Error: %s", p.tmpResultFilePath, p.config.ResultFile, err.Error()) - } - - return nil, nil -} - -func NewTargetProcessor(config *Config) *TargetProcessor { - return &TargetProcessor{ - tmpResultFilePath: config.ResultFile + "_temp", - config: config, - } -} diff --git a/extension/observer/endpoints.go b/extension/observer/endpoints.go index 3a675c5fc4c1..d26aa7d3bd6b 100644 --- a/extension/observer/endpoints.go +++ b/extension/observer/endpoints.go @@ -32,6 +32,12 @@ func (e *Endpoint) String() string { return fmt.Sprintf("Endpoint{ID: %v, Target: %v, Details: %T%+v}", e.ID, e.Target, e.Details, e.Details) } +// Task is a discovered ECS task. +type Task struct { + MetricsPath string + Labels map[string]string +} + // Pod is a discovered k8s pod. type Pod struct { // Name of the pod. @@ -77,9 +83,18 @@ func EndpointToEnv(endpoint Endpoint) (EndpointEnv, error) { ruleTypes := map[string]interface{}{ "port": false, "pod": false, + "task": false, } switch o := endpoint.Details.(type) { + case Task: + ruleTypes["task"] = true + return map[string]interface{}{ + "type": ruleTypes, + "endpoint": endpoint.Target, + "metricsPath": o.MetricsPath, + "labels": o.Labels, + }, nil case Pod: ruleTypes["pod"] = true return map[string]interface{}{ From 8eac694d8d0f66c57114d3cc881bdee053e72bad Mon Sep 17 00:00:00 2001 From: Raphael Koh Date: Thu, 24 Dec 2020 01:35:29 -0500 Subject: [PATCH 10/19] Initalize task defs --- extension/observer/ecsobserver/ecstask.go | 6 ++++-- extension/observer/ecsobserver/ecstask_test.go | 2 ++ extension/observer/ecsobserver/extension.go | 1 - extension/observer/ecsobserver/factory.go | 5 +++++ extension/observer/ecsobserver/taskdefinitionprocessor.go | 1 + 5 files changed, 12 insertions(+), 3 deletions(-) diff --git a/extension/observer/ecsobserver/ecstask.go b/extension/observer/ecsobserver/ecstask.go index 83055c4be3b1..ff89ed507d9f 100644 --- a/extension/observer/ecsobserver/ecstask.go +++ b/extension/observer/ecsobserver/ecstask.go @@ -145,13 +145,15 @@ func (t *ECSTask) addTaskDefinitionBasedTargets(ip string, c *ecs.ContainerDefin } for _, taskDef := range config.TaskDefinitions { + taskDefArn := aws.StringValue(t.Task.TaskDefinitionArn) // skip if task def regex mismatch - if !taskDef.taskDefRegex.MatchString(*t.Task.TaskDefinitionArn) { + if !taskDef.taskDefRegex.MatchString(taskDefArn) { continue } + containerName := aws.StringValue(c.Name) // skip if there is container name regex pattern configured and container name mismatch - if taskDef.ContainerNamePattern != "" && !taskDef.containerNameRegex.MatchString(*c.Name) { + if taskDef.ContainerNamePattern != "" && !taskDef.containerNameRegex.MatchString(containerName) { continue } diff --git a/extension/observer/ecsobserver/ecstask_test.go b/extension/observer/ecsobserver/ecstask_test.go index e0658389762b..f03939ade4db 100644 --- a/extension/observer/ecsobserver/ecstask_test.go +++ b/extension/observer/ecsobserver/ecstask_test.go @@ -20,6 +20,7 @@ import ( "github.com/aws/aws-sdk-go/service/ecs" "github.com/stretchr/testify/assert" + "go.uber.org/zap" ) func buildWorkloadFargateAwsvpc(dockerLabel bool, taskDef bool) *ECSTask { @@ -438,6 +439,7 @@ func TestAddTargets(t *testing.T) { } for _, tc := range testCases { + tc.config.logger = zap.NewNop() t.Run(tc.testName, func(t *testing.T) { for _, taskDef := range tc.config.TaskDefinitions { taskDef.init() diff --git a/extension/observer/ecsobserver/extension.go b/extension/observer/ecsobserver/extension.go index a420917d65c5..3283bd947025 100644 --- a/extension/observer/ecsobserver/extension.go +++ b/extension/observer/ecsobserver/extension.go @@ -102,7 +102,6 @@ func writeTargetsToFile(targets map[string]*Target, filePath string) error { idx++ } - m, err := yaml.Marshal(promTargets) if err != nil { return fmt.Errorf("Failed to marshal Prometheus targets. Error: %s", err.Error()) diff --git a/extension/observer/ecsobserver/factory.go b/extension/observer/ecsobserver/factory.go index c126b3aa9450..e0b1a3d4a09d 100644 --- a/extension/observer/ecsobserver/factory.go +++ b/extension/observer/ecsobserver/factory.go @@ -60,5 +60,10 @@ func createExtension( ) (component.ServiceExtension, error) { config := cfg.(*Config) config.logger = params.Logger + + for _, taskDef := range config.TaskDefinitions { + taskDef.init() + } + return newObserver(config) } diff --git a/extension/observer/ecsobserver/taskdefinitionprocessor.go b/extension/observer/ecsobserver/taskdefinitionprocessor.go index 981cb1d7e020..81c7dcd9e4c4 100644 --- a/extension/observer/ecsobserver/taskdefinitionprocessor.go +++ b/extension/observer/ecsobserver/taskdefinitionprocessor.go @@ -53,6 +53,7 @@ func (p *TaskDefinitionProcessor) Process(cluster string, taskList []*ECSTask) ( if res, ok := p.taskDefCache.Get(arn); ok { // Try retrieving from cache task.TaskDefinition = res.(*ecs.TaskDefinition) + numValidTasks++ } else { // Query API input := &ecs.DescribeTaskDefinitionInput{TaskDefinition: &arn} From 2d5dbef7015a8618aba752e2c87dc1b1bfb5a89a Mon Sep 17 00:00:00 2001 From: Raphael Koh Date: Thu, 24 Dec 2020 02:11:00 -0500 Subject: [PATCH 11/19] Add task filter processor --- .../observer/ecsobserver/servicediscovery.go | 3 +- .../ecsobserver/taskdefinitionprocessor.go | 5 +- .../ecsobserver/taskfilterprocessor.go | 100 ++++++++++++++++++ 3 files changed, 103 insertions(+), 5 deletions(-) create mode 100644 extension/observer/ecsobserver/taskfilterprocessor.go diff --git a/extension/observer/ecsobserver/servicediscovery.go b/extension/observer/ecsobserver/servicediscovery.go index ad5ab18e319d..91f1cd47acd0 100644 --- a/extension/observer/ecsobserver/servicediscovery.go +++ b/extension/observer/ecsobserver/servicediscovery.go @@ -93,7 +93,8 @@ func (sd *serviceDiscovery) init() { func (sd *serviceDiscovery) initProcessors() { sd.processors = []Processor{ NewTaskRetrievalProcessor(sd.svcEcs, sd.config.logger), - NewTaskDefinitionProcessor(sd.svcEcs, sd.config.logger), + NewTaskDefinitionProcessor(sd.svcEcs), + NewTaskFilterProcessor(sd.config.TaskDefinitions, sd.config.DockerLabel), NewMetadataProcessor(sd.svcEcs, sd.svcEc2, sd.config.logger), } } diff --git a/extension/observer/ecsobserver/taskdefinitionprocessor.go b/extension/observer/ecsobserver/taskdefinitionprocessor.go index 81c7dcd9e4c4..be96ce9962e2 100644 --- a/extension/observer/ecsobserver/taskdefinitionprocessor.go +++ b/extension/observer/ecsobserver/taskdefinitionprocessor.go @@ -20,7 +20,6 @@ import ( "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/ecs" "github.com/hashicorp/golang-lru/simplelru" - "go.uber.org/zap" ) const ( @@ -33,7 +32,6 @@ type TaskDefinitionProcessor struct { svcEcs *ecs.ECS taskDefCache *simplelru.LRU - logger *zap.Logger } func (p *TaskDefinitionProcessor) ProcessorName() string { @@ -84,13 +82,12 @@ func (p *TaskDefinitionProcessor) Process(cluster string, taskList []*ECSTask) ( return filteredTaskList, nil } -func NewTaskDefinitionProcessor(ecs *ecs.ECS, logger *zap.Logger) *TaskDefinitionProcessor { +func NewTaskDefinitionProcessor(ecs *ecs.ECS) *TaskDefinitionProcessor { // Initiate the task definition LRU caching lru, _ := simplelru.NewLRU(taskDefCacheSize, nil) return &TaskDefinitionProcessor{ svcEcs: ecs, taskDefCache: lru, - logger: logger, } } diff --git a/extension/observer/ecsobserver/taskfilterprocessor.go b/extension/observer/ecsobserver/taskfilterprocessor.go new file mode 100644 index 000000000000..2221df263bda --- /dev/null +++ b/extension/observer/ecsobserver/taskfilterprocessor.go @@ -0,0 +1,100 @@ +// Copyright The 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 ecsobserver + +import ( + "github.com/aws/aws-sdk-go/aws" +) + +// TaskFilterProcessor filters out tasks that don't match config. +type TaskFilterProcessor struct { + taskDefsConfig []*TaskDefinitionConfig + dockerPortLabel string +} + +func (p *TaskFilterProcessor) ProcessorName() string { + return "TaskFilterProcessor" +} + +// Process marks tasks that match the docker label config or task definition configs and filters +// out tasks that don't match any. +func (p *TaskFilterProcessor) Process(cluster string, taskList []*ECSTask) ([]*ECSTask, error) { + var filteredTasks []*ECSTask + + for _, task := range taskList { + p.discoverDockerLabelBased(task) + p.discoverTaskDefinitionBased(task) + + if task.DockerLabelBased || task.TaskDefinitionBased { + filteredTasks = append(filteredTasks, task) + } + } + + return filteredTasks, nil +} + +// discoverDockerLabelBased checks if task matches docker label config and marks it if so. +func (p *TaskFilterProcessor) discoverDockerLabelBased(task *ECSTask) { + if p.dockerPortLabel == "" { + return + } + + for _, containerDef := range task.TaskDefinition.ContainerDefinitions { + if _, ok := containerDef.DockerLabels[p.dockerPortLabel]; ok { + task.DockerLabelBased = true + return + } + } +} + +// discoverTaskDefinitionBased checks if task matches task definition configs and marks it if so. +func (p *TaskFilterProcessor) discoverTaskDefinitionBased(task *ECSTask) { + if task.TaskDefinition.TaskDefinitionArn == nil { + return + } + + taskDefArn := aws.StringValue(task.TaskDefinition.TaskDefinitionArn) + for _, taskDefConfig := range p.taskDefsConfig { + if taskDefConfig.taskDefRegex.MatchString(taskDefArn) { + if taskDefConfig.ContainerNamePattern == "" || containerNameMatches(task, taskDefConfig) { + task.TaskDefinitionBased = true + return + } + } + } +} + +// containerNameMatches checks if any of the task's container names matches the regex pattern +// defined in the task definition config. +func containerNameMatches(task *ECSTask, config *TaskDefinitionConfig) bool { + for _, c := range task.TaskDefinition.ContainerDefinitions { + if config.containerNameRegex.MatchString(aws.StringValue(c.Name)) { + return true + } + } + return false +} + +func NewTaskFilterProcessor(taskDefsConfig []*TaskDefinitionConfig, dockerLabelConfig *DockerLabelConfig) *TaskFilterProcessor { + dockerPortLabel := "" + if dockerLabelConfig != nil { + dockerPortLabel = dockerLabelConfig.PortLabel + } + + return &TaskFilterProcessor{ + taskDefsConfig: taskDefsConfig, + dockerPortLabel: dockerPortLabel, + } +} From 65fc92b5582bc3d32e39d83c8aa5398d17bda136 Mon Sep 17 00:00:00 2001 From: Raphael Koh Date: Thu, 24 Dec 2020 13:09:48 -0500 Subject: [PATCH 12/19] Fix prometheus target address --- extension/observer/ecsobserver/servicediscovery.go | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/extension/observer/ecsobserver/servicediscovery.go b/extension/observer/ecsobserver/servicediscovery.go index 91f1cd47acd0..b7e84b0d0c4e 100644 --- a/extension/observer/ecsobserver/servicediscovery.go +++ b/extension/observer/ecsobserver/servicediscovery.go @@ -49,13 +49,8 @@ type Target struct { // toPrometheusTarget converts target into seriablizable Prometheus target. func (t *Target) toPrometheusTarget() *PrometheusTarget { - metricsPath := t.MetricsPath - if metricsPath == "" { - metricsPath = defaultPrometheusMetricsPath - } - return &PrometheusTarget{ - Targets: []string{t.Address + metricsPath}, + Targets: []string{t.Address}, Labels: t.Labels, } } From 52d716b514a9e54f7245af5cc730ce67db408fcc Mon Sep 17 00:00:00 2001 From: Raphael Koh Date: Tue, 29 Dec 2020 16:45:59 -0500 Subject: [PATCH 13/19] Add rule to receivercreator --- receiver/receivercreator/observerhandler.go | 2 +- receiver/receivercreator/rules.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/receiver/receivercreator/observerhandler.go b/receiver/receivercreator/observerhandler.go index 19809e88d739..09cfbda2edbb 100644 --- a/receiver/receivercreator/observerhandler.go +++ b/receiver/receivercreator/observerhandler.go @@ -113,7 +113,7 @@ func (obs *observerHandler) OnAdd(added []observer.Endpoint) { }, resolvedDiscoveredConfig) if err != nil { - obs.logger.Error("failed to start receiver", zap.String("receiver", template.fullName)) + obs.logger.Error("failed to start receiver", zap.String("receiver", template.fullName), zap.Error(err)) continue } diff --git a/receiver/receivercreator/rules.go b/receiver/receivercreator/rules.go index 1c8598d8a9ff..9b2827c17028 100644 --- a/receiver/receivercreator/rules.go +++ b/receiver/receivercreator/rules.go @@ -30,7 +30,7 @@ type rule struct { } // ruleRe is used to verify the rule starts type check. -var ruleRe = regexp.MustCompile(`^type\.(pod|port)`) +var ruleRe = regexp.MustCompile(`^type\.(pod|port|task)`) // newRule creates a new rule instance. func newRule(ruleStr string) (rule, error) { From dc823857748b84bf0e589b7895b3e8adf53bf3ff Mon Sep 17 00:00:00 2001 From: Raphael Koh Date: Tue, 29 Dec 2020 18:08:47 -0500 Subject: [PATCH 14/19] Add documentation --- extension/observer/README.md | 1 + extension/observer/ecsobserver/README.md | 150 ++++++++++++++++++ extension/observer/ecsobserver/ecstask.go | 12 +- .../observer/ecsobserver/ecstask_test.go | 36 +++-- extension/observer/ecsobserver/go.mod | 2 +- extension/observer/ecsobserver/go.sum | 2 + .../observer/ecsobserver/servicediscovery.go | 13 +- extension/observer/endpoints.go | 21 ++- receiver/receivercreator/README.md | 26 ++- 9 files changed, 229 insertions(+), 34 deletions(-) create mode 100644 extension/observer/ecsobserver/README.md diff --git a/extension/observer/README.md b/extension/observer/README.md index 15edaa7548eb..9264c1f7271b 100644 --- a/extension/observer/README.md +++ b/extension/observer/README.md @@ -8,3 +8,4 @@ Currently the only component that uses observers is the [receiver_creator](../.. * [k8sobserver](k8sobserver/README.md) * [hostobserver](hostobserver/README.md) +* [ecsobserver](ecsobserver/README.md) diff --git a/extension/observer/ecsobserver/README.md b/extension/observer/ecsobserver/README.md new file mode 100644 index 000000000000..51e7b4b5e076 --- /dev/null +++ b/extension/observer/ecsobserver/README.md @@ -0,0 +1,150 @@ +# ECS Observer Extension + +**Status: beta** + +The `ecs_observer` uses the ECS/EC2 API to discover tasks and containers running in an ECS cluster. + +There are 2 modes to discover the targets based on the config: +* **Mode 1: Docker label-Based:** Add docker labels to the containers to indicate the port and metric path. The ECS Observer can then be configured to discover targets with the matching docker label and container port. +* **Mode 2: ECS Task Definition ARN-based:** The ECS Observer can be configured to discover targets when the target's ECS task definition ARN matches the configured regex and it matches the configured container ports. + +Two modes can be enabled together and the ECS Observer will de-duplicate the discovered targets based on: *{private_ip}:{port}/{metrics_path}* + +### Configuration + +| Name | | Description | +|---------------------|-------------|----------------------------------------------------------------| +| cluster_name | Mandatory | target ECS cluster name for service discovery | +| cluster_region | Mandatory | target ECS cluster's AWS region name | +| refresh_interval | Optional | how often to look for changes in endpoints (default: 10s) | +| sd_result_file | Optional | path of YAML file for the scrape target results. If this is enabled, the default endpoint update will be disabled and listeners will not get updates | +| docker_label | Optional | docker label-based service discovery configuration (see [here](#docker-label-based-service-discovery-configuration)). If this is enabled, docker label-based SD will be enabled | +| task_definitions | Optional | list of task definition ARN-based service discovery configurations (see [here](#task-definition-arn-based-service-discovery-configuration)). If this list is non-empty, task definition ARN-based SD will be enabled | + +#### Docker Label-Based Service Discovery Configuration + +| Name | | Description | +|---------------------|-------------|---------------------------------------------------------------| +| port_label | Mandatory | container's docker label name that specifies the metrics port | +| job_name_label | Optional | container's docker label name that specifies the scrape job name. (Default: "") | +| metrics_path_label | Optional | container's docker label name that specifies the metrics path. (Default: "") | + +#### Task Definition ARN-Based Service Discovery Configuration + +| Name | | Description | +|---------------------|-------------|---------------------------------------------------------------| +| task_definition_arn_pattern | Mandatory | Regex pattern to match against ECS task definition ARN | +| metrics_ports | Mandatory | container ports separated by semicolon. Only containers that expose these ports will be discovered | +| container_name_pattern | Optional | ECS task container name regex pattern | +| metrics_path | Optional | metrics path. (Default: "") | +| job_name | Optional | Scrape job name. (Default: "") | + +### Endpoint Variables + +Endpoint variables exposed by this observer are as follows. + +| Variable | Description | +|-------------|--------------------------------------------------------------------| +| type.task | `true` | +| port | port number | +| metricsPath | metrics path | +| labels | labels generated by the ECS Observer. Mainly used with Prometheus. | + +### AWS Permissions +The following permissions need to be granted so that the ECS Observer can query the ECS/EC2 API to get the task metadata: +- ECS:ListTasks, +- ECS:DescribeContainerInstances, +- ECS:DescribeTasks, +- ECS:DescribeTaskDefinition +- EC2:DescribeInstances + +### Writing targets to a static file + +There are 2 ways to export discovered endpoints: + +1. Notification-based: this is the default method where another component listens in on endpoint updates. +2. Write to a file: if `sd_result_file` is configured, the scraped targets will be written to the file path given by `sd_result_file`. The default notification-based behaviour will be disabled. This is mainly used in conjunction with the Prometheus receiver. + +The main motivation of this method is that using a single Prometheus receiver to scrape multiple targets is currently more efficient than spawning a separate receiver for each scrape target using the receiver creator. For a more detailed discussion, refer to [this issue](https://github.com/open-telemetry/opentelemetry-collector-contrib/issues/1395). + +An example of the contents of the `sd_result_file` looks like this: +```yaml +- targets: + - 10.6.1.95:32785 + labels: + __metrics_path__: /metrics + ECS_PROMETHEUS_EXPORTER_PORT_SUBSET_B: "9406" + ECS_PROMETHEUS_JOB_NAME: demo-jar-ec2-bridge-subset-b-dynamic + ECS_PROMETHEUS_METRICS_PATH: /metrics + InstanceType: t3.medium + LaunchType: EC2 + SubnetId: subnet-0347624eeea6c5969 + TaskDefinitionFamily: demo-jar-ec2-bridge-dynamic-port-subset-b + TaskGroup: family:demo-jar-ec2-bridge-dynamic-port-subset-b + TaskRevision: "7" + VpcId: vpc-033b021cd7ecbcedb + container_name: demo-jar-ec2-bridge-dynamic-port-subset-b + job: task_def_2 +- targets: + - 10.6.1.95:32783 + labels: + __metrics_path__: /metrics + ECS_PROMETHEUS_EXPORTER_PORT_SUBSET_B: "9406" + ECS_PROMETHEUS_JOB_NAME: demo-jar-ec2-bridge-subset-b-dynamic + ECS_PROMETHEUS_METRICS_PATH: /metrics + InstanceType: t3.medium + LaunchType: EC2 + SubnetId: subnet-0347624eeea6c5969 + TaskDefinitionFamily: demo-jar-ec2-bridge-dynamic-port-subset-b + TaskGroup: family:demo-jar-ec2-bridge-dynamic-port-subset-b + TaskRevision: "7" + VpcId: vpc-033b021cd7ecbcedb + container_name: demo-jar-ec2-bridge-dynamic-port-subset-b + job: task_def_2 +``` + +## Example + +```yaml +extensions: + ecs_observer: + refresh_interval: 15s + cluster_name: 'Cluster-1' + cluster_region: 'us-west-2' + sd_result_file: '/etc/ecs_sd_targets.yaml' + docker_label: + job_name_label: 'ECS_PROMETHEUS_JOB_NAME' + metrics_path_label: 'ECS_PROMETHEUS_METRICS_PATH' + port_label: 'ECS_PROMETHEUS_EXPORTER_PORT_SUBSET_A' + task_definitions: + - job_name: 'task_def_1' + metrics_path: '/metrics' + metrics_ports: '9113;9090' + task_definition_arn_pattern: '.*:task-definition/nginx:[0-9]+' + +receivers: + prometheus: + config: + scrape_configs: + - job_name: "task_def_1" + file_sd_configs: + - files: + - '/etc/ecs_sd_targets.yaml' + +processors: + exampleprocessor: + +exporters: + exampleexporter: + +service: + pipelines: + metrics: + receivers: [receiver_creator] + processors: [exampleprocessor] + exporters: [exampleexporter] + extensions: [ecs_observer] +``` + +The full list of settings exposed for this receiver are documented [here](./config.go) +with detailed sample configurations [here](./testdata/config.yaml). diff --git a/extension/observer/ecsobserver/ecstask.go b/extension/observer/ecsobserver/ecstask.go index ff89ed507d9f..22ffe21069a8 100644 --- a/extension/observer/ecsobserver/ecstask.go +++ b/extension/observer/ecsobserver/ecstask.go @@ -120,8 +120,7 @@ func (t *ECSTask) addDockerLabelBasedTarget(ip string, c *ecs.ContainerDefinitio metricsPath = aws.StringValue(v) } - targetAddr := fmt.Sprintf("%s:%d", ip, hostPort) - endpoint := targetAddr + metricsPath + endpoint := fmt.Sprintf("%s:%d%s", ip, hostPort, metricsPath) if _, ok := targets[endpoint]; ok { return } @@ -132,7 +131,8 @@ func (t *ECSTask) addDockerLabelBasedTarget(ip string, c *ecs.ContainerDefinitio } targets[endpoint] = &Target{ - Address: targetAddr, + IP: ip, + Port: hostPort, MetricsPath: metricsPath, Labels: t.generateTargetLabels(c, metricsPath, customizedJobName), } @@ -164,11 +164,11 @@ func (t *ECSTask) addTaskDefinitionBasedTargets(ip string, c *ecs.ContainerDefin continue } - targetAddr := fmt.Sprintf("%s:%d", ip, hostPort) - endpoint := targetAddr + taskDef.MetricsPath + endpoint := fmt.Sprintf("%s:%d%s", ip, hostPort, taskDef.MetricsPath) if _, ok := targets[endpoint]; !ok { targets[endpoint] = &Target{ - Address: targetAddr, + IP: ip, + Port: hostPort, MetricsPath: taskDef.MetricsPath, Labels: t.generateTargetLabels(c, taskDef.MetricsPath, taskDef.JobName), } diff --git a/extension/observer/ecsobserver/ecstask_test.go b/extension/observer/ecsobserver/ecstask_test.go index f03939ade4db..bf26ba7969dd 100644 --- a/extension/observer/ecsobserver/ecstask_test.go +++ b/extension/observer/ecsobserver/ecstask_test.go @@ -216,7 +216,8 @@ func TestAddTargets(t *testing.T) { }, map[string]*Target{ "10.0.0.129:9404": &Target{ - Address: "10.0.0.129:9404", + IP: "10.0.0.129", + Port: 9404, MetricsPath: "", Labels: map[string]string{ "job": "java-tomcat-fargate-awsvpc", @@ -227,7 +228,8 @@ func TestAddTargets(t *testing.T) { }, }, "10.0.0.129:9406/metrics": &Target{ - Address: "10.0.0.129:9406", + IP: "10.0.0.129", + Port: 9406, MetricsPath: "/metrics", Labels: map[string]string{ "container_name": "bugbash-jar-fargate-awsvpc-with-docker-label", @@ -254,7 +256,8 @@ func TestAddTargets(t *testing.T) { }, map[string]*Target{ "10.0.0.129:9404/stats/metrics": &Target{ - Address: "10.0.0.129:9404", + IP: "10.0.0.129", + Port: 9404, MetricsPath: "/stats/metrics", Labels: map[string]string{ "container_name": "bugbash-tomcat-fargate-awsvpc-with-docker-label", @@ -265,7 +268,8 @@ func TestAddTargets(t *testing.T) { }, }, "10.0.0.129:9406/stats/metrics": &Target{ - Address: "10.0.0.129:9406", + IP: "10.0.0.129", + Port: 9406, MetricsPath: "/stats/metrics", Labels: map[string]string{ "container_name": "bugbash-jar-fargate-awsvpc-with-docker-label", @@ -293,7 +297,8 @@ func TestAddTargets(t *testing.T) { }, map[string]*Target{ "10.4.0.205:9494/metrics": &Target{ - Address: "10.4.0.205:9494", + IP: "10.4.0.205", + Port: 9494, MetricsPath: "/metrics", Labels: map[string]string{ "container_name": "bugbash-tomcat-prometheus-workload-java-ec2-bridge-mapped-port", @@ -328,7 +333,8 @@ func TestAddTargets(t *testing.T) { }, map[string]*Target{ "10.0.0.129:9404": &Target{ - Address: "10.0.0.129:9404", + IP: "10.0.0.129", + Port: 9404, MetricsPath: "", Labels: map[string]string{ "job": "java-tomcat-fargate-awsvpc", @@ -339,7 +345,8 @@ func TestAddTargets(t *testing.T) { }, }, "10.0.0.129:9406/metrics": &Target{ - Address: "10.0.0.129:9406", + IP: "10.0.0.129", + Port: 9406, MetricsPath: "/metrics", Labels: map[string]string{ "container_name": "bugbash-jar-fargate-awsvpc-with-docker-label", @@ -350,7 +357,8 @@ func TestAddTargets(t *testing.T) { }, }, "10.0.0.129:9404/stats/metrics": &Target{ - Address: "10.0.0.129:9404", + IP: "10.0.0.129", + Port: 9404, MetricsPath: "/stats/metrics", Labels: map[string]string{ "container_name": "bugbash-tomcat-fargate-awsvpc-with-docker-label", @@ -361,7 +369,8 @@ func TestAddTargets(t *testing.T) { }, }, "10.0.0.129:9406/stats/metrics": &Target{ - Address: "10.0.0.129:9406", + IP: "10.0.0.129", + Port: 9406, MetricsPath: "/stats/metrics", Labels: map[string]string{ "container_name": "bugbash-jar-fargate-awsvpc-with-docker-label", @@ -393,7 +402,8 @@ func TestAddTargets(t *testing.T) { }, map[string]*Target{ "10.4.0.205:32774/metrics": &Target{ - Address: "10.4.0.205:32774", + IP: "10.4.0.205", + Port: 32774, MetricsPath: "/metrics", Labels: map[string]string{ "InstanceType": "t3.medium", @@ -407,7 +417,8 @@ func TestAddTargets(t *testing.T) { }, }, "10.4.0.205:9494": &Target{ - Address: "10.4.0.205:9494", + IP: "10.4.0.205", + Port: 9494, MetricsPath: "", Labels: map[string]string{ "job": "bugbash-tomcat-ec2-bridge-mapped-port", @@ -421,7 +432,8 @@ func TestAddTargets(t *testing.T) { }, }, "10.4.0.205:9494/metrics": &Target{ - Address: "10.4.0.205:9494", + IP: "10.4.0.205", + Port: 9494, MetricsPath: "/metrics", Labels: map[string]string{ "InstanceType": "t3.medium", diff --git a/extension/observer/ecsobserver/go.mod b/extension/observer/ecsobserver/go.mod index 69e5479719c5..d189b4903e89 100644 --- a/extension/observer/ecsobserver/go.mod +++ b/extension/observer/ecsobserver/go.mod @@ -3,7 +3,7 @@ module github.com/open-telemetry/opentelemetry-collector-contrib/extension/obser go 1.14 require ( - github.com/aws/aws-sdk-go v1.36.13 + github.com/aws/aws-sdk-go v1.36.17 github.com/hashicorp/golang-lru v0.5.4 github.com/open-telemetry/opentelemetry-collector-contrib/extension/observer v0.0.0-00010101000000-000000000000 github.com/stretchr/testify v1.6.1 diff --git a/extension/observer/ecsobserver/go.sum b/extension/observer/ecsobserver/go.sum index c4c944975e39..c957f6e1e51e 100644 --- a/extension/observer/ecsobserver/go.sum +++ b/extension/observer/ecsobserver/go.sum @@ -114,6 +114,8 @@ github.com/aws/aws-sdk-go v1.35.5 h1:doSEOxC0UkirPcle20Rc+1kAhJ4Ip+GSEeZ3nKl7Qlk github.com/aws/aws-sdk-go v1.35.5/go.mod h1:tlPOdRjfxPBpNIwqDj61rmsnA85v9jc0Ps9+muhnW+k= github.com/aws/aws-sdk-go v1.36.13 h1:RAyssUwg/yM7q874D2PQuIST6uhhyYFFPJtgVG/OujI= github.com/aws/aws-sdk-go v1.36.13/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro= +github.com/aws/aws-sdk-go v1.36.17 h1:8zTvseyGhgs3uQAzkgnFy7dvTo+ZnZLYmrhnopFxYME= +github.com/aws/aws-sdk-go v1.36.17/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro= github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g= 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= diff --git a/extension/observer/ecsobserver/servicediscovery.go b/extension/observer/ecsobserver/servicediscovery.go index b7e84b0d0c4e..095b4aba9e80 100644 --- a/extension/observer/ecsobserver/servicediscovery.go +++ b/extension/observer/ecsobserver/servicediscovery.go @@ -15,6 +15,7 @@ package ecsobserver import ( + "fmt" "os" "github.com/aws/aws-sdk-go/aws" @@ -42,25 +43,29 @@ type PrometheusTarget struct { // Target represents a discovered ECS endpoint. type Target struct { - Address string + IP string + Port int64 MetricsPath string Labels map[string]string } // toPrometheusTarget converts target into seriablizable Prometheus target. func (t *Target) toPrometheusTarget() *PrometheusTarget { + address := fmt.Sprintf("%s:%d", t.IP, t.Port) return &PrometheusTarget{ - Targets: []string{t.Address}, + Targets: []string{address}, Labels: t.Labels, } } // toEndpoint converts target into seriablizable Prometheus target. func (t *Target) toEndpoint() *observer.Endpoint { + id := fmt.Sprintf("%s:%d%s", t.IP, t.Port, t.MetricsPath) return &observer.Endpoint{ - ID: observer.EndpointID(t.Address + t.MetricsPath), - Target: t.Address, + ID: observer.EndpointID(id), + Target: t.IP, Details: observer.Task{ + Port: t.Port, MetricsPath: t.MetricsPath, Labels: t.Labels, }, diff --git a/extension/observer/endpoints.go b/extension/observer/endpoints.go index d26aa7d3bd6b..911575e98f76 100644 --- a/extension/observer/endpoints.go +++ b/extension/observer/endpoints.go @@ -34,7 +34,11 @@ func (e *Endpoint) String() string { // Task is a discovered ECS task. type Task struct { + // Port number of the endpoint. + Port int64 + // MetricsPath is the metrics path of the endpoint. MetricsPath string + // Labels exported by the ECS Observer (mainly for use with Prometheus). Labels map[string]string } @@ -87,14 +91,6 @@ func EndpointToEnv(endpoint Endpoint) (EndpointEnv, error) { } switch o := endpoint.Details.(type) { - case Task: - ruleTypes["task"] = true - return map[string]interface{}{ - "type": ruleTypes, - "endpoint": endpoint.Target, - "metricsPath": o.MetricsPath, - "labels": o.Labels, - }, nil case Pod: ruleTypes["pod"] = true return map[string]interface{}{ @@ -129,6 +125,15 @@ func EndpointToEnv(endpoint Endpoint) (EndpointEnv, error) { "port": o.Port, "transport": o.Transport, }, nil + case Task: + ruleTypes["task"] = true + return map[string]interface{}{ + "type": ruleTypes, + "endpoint": endpoint.Target, + "port": o.Port, + "metricsPath": o.MetricsPath, + "labels": o.Labels, + }, nil default: return nil, fmt.Errorf("unknown endpoint details type %T", endpoint.Details) diff --git a/receiver/receivercreator/README.md b/receiver/receivercreator/README.md index 56df8e8a3136..ccf99d5a0a7a 100644 --- a/receiver/receivercreator/README.md +++ b/receiver/receivercreator/README.md @@ -58,7 +58,7 @@ config: ## Rule Expressions -Each rule must start with `type.(pod|port) &&` such that the rule matches +Each rule must start with `type.(pod|port|task) &&` such that the rule matches only one endpoint type. Depending on the type of endpoint the rule is targeting it will have different variables available. @@ -83,6 +83,15 @@ targeting it will have different variables available. | pod.annotations | map of annotations of the owning pod | | protocol | `TCP` or `UDP` | +### Task + +| Variable | Description | +|-----------------|--------------------------------------| +| type.task | `true` | +| port | port number | +| metricsPath | path for scraping metrics | +| labels | generated labels for scrape target | + ## Example ```yaml @@ -90,6 +99,8 @@ extensions: # Configures the Kubernetes observer to watch for pod start and stop events. k8s_observer: host_observer: + # Configures the ECS observer to watch for task start and stop events. + ecs_observer: receivers: receiver_creator/1: @@ -120,6 +131,15 @@ receivers: rule: type.port && port == 6379 && is_ipv6 == true config: service_name: redis_on_host + receiver_creator/3: + # Name of the extensions to watch for endpoints to start and stop. + watch_observers: [ecs_observer] + receivers: + prometheus_simple: + rule: type.task && metricsPath == '/metrics' + config: + metrics_path: '`metricsPath`' + endpoint: '`endpoint`:`port`' processors: @@ -131,10 +151,10 @@ exporters: service: pipelines: metrics: - receivers: [receiver_creator/1, receiver_creator/2] + receivers: [receiver_creator/1, receiver_creator/2, receiver_creator/3] processors: [exampleprocessor] exporters: [exampleexporter] - extensions: [k8s_observer, host_observer] + extensions: [k8s_observer, host_observer, ecs_observer] ``` The full list of settings exposed for this receiver are documented [here](./config.go) From 62049beb0b35134ea087954fd8ad45f9a9d5adaa Mon Sep 17 00:00:00 2001 From: Raphael Koh Date: Tue, 29 Dec 2020 20:13:25 -0500 Subject: [PATCH 15/19] Add test to endpoints_test.go --- extension/observer/endpoints_test.go | 33 ++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/extension/observer/endpoints_test.go b/extension/observer/endpoints_test.go index 907722eeaed9..ed025630873c 100644 --- a/extension/observer/endpoints_test.go +++ b/extension/observer/endpoints_test.go @@ -45,6 +45,7 @@ func TestEndpointToEnv(t *testing.T) { "type": map[string]interface{}{ "port": false, "pod": true, + "task": false, }, "endpoint": "192.68.73.2", "name": "pod_name", @@ -81,6 +82,7 @@ func TestEndpointToEnv(t *testing.T) { "type": map[string]interface{}{ "port": true, "pod": false, + "task": false, }, "endpoint": "192.68.73.2", "name": "port_name", @@ -115,6 +117,7 @@ func TestEndpointToEnv(t *testing.T) { "type": map[string]interface{}{ "port": true, "pod": false, + "task": false, }, "endpoint": "127.0.0.1", "name": "process_name", @@ -125,6 +128,36 @@ func TestEndpointToEnv(t *testing.T) { }, wantErr: false, }, + { + name: "ECS Task", + endpoint: Endpoint{ + ID: EndpointID("127.0.0.1:9090"), + Target: "127.0.0.1", + Details: Task{ + Port: 2379, + MetricsPath: "/metrics", + Labels: map[string]string{ + "label1": "value1", + "label2": "value2", + }, + }, + }, + want: EndpointEnv{ + "type": map[string]interface{}{ + "port": false, + "pod": false, + "task": true, + }, + "endpoint": "127.0.0.1", + "port": int64(2379), + "metricsPath": "/metrics", + "labels": map[string]string{ + "label1": "value1", + "label2": "value2", + }, + }, + wantErr: false, + }, { name: "Unsupported endpoint", endpoint: Endpoint{ From 8918ab703c783ccb9cbc881dc36975f17fbb6a51 Mon Sep 17 00:00:00 2001 From: Raphael Koh Date: Thu, 7 Jan 2021 16:30:48 -0500 Subject: [PATCH 16/19] Add unit tests --- .../observer/ecsobserver/ecstask_test.go | 90 +++++ .../observer/ecsobserver/extension_test.go | 73 ++++ .../observer/ecsobserver/factory_test.go | 64 +++- .../ecsobserver/servicediscovery_test.go | 187 +++++++++++ .../ecsobserver/taskfilterprocessor_test.go | 314 ++++++++++++++++++ 5 files changed, 719 insertions(+), 9 deletions(-) create mode 100644 extension/observer/ecsobserver/extension_test.go create mode 100644 extension/observer/ecsobserver/servicediscovery_test.go create mode 100644 extension/observer/ecsobserver/taskfilterprocessor_test.go diff --git a/extension/observer/ecsobserver/ecstask_test.go b/extension/observer/ecsobserver/ecstask_test.go index bf26ba7969dd..3f951c2c7b17 100644 --- a/extension/observer/ecsobserver/ecstask_test.go +++ b/extension/observer/ecsobserver/ecstask_test.go @@ -18,6 +18,7 @@ import ( "reflect" "testing" + "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/ecs" "github.com/stretchr/testify/assert" "go.uber.org/zap" @@ -448,6 +449,28 @@ func TestAddTargets(t *testing.T) { }, }, }, + { + "no network mode", + &ECSTask{ + TaskDefinition: &ecs.TaskDefinition{}, + }, + &Config{ + DockerLabel: &DockerLabelConfig{ + JobNameLabel: "EC2_PROMETHEUS_JOB_NAME", + PortLabel: "EC2_PROMETHEUS_EXPORTER_PORT", + MetricsPathLabel: "ECS_PROMETHEUS_METRICS_PATH", + }, + TaskDefinitions: []*TaskDefinitionConfig{ + { + JobName: "", + MetricsPorts: "9404;9406", + TaskDefArnPattern: ".*:task-definition/prometheus-java-tomcat-ec2-awsvpc:[0-9]+", + MetricsPath: "/metrics", + }, + }, + }, + map[string]*Target{}, + }, } for _, tc := range testCases { @@ -464,6 +487,73 @@ func TestAddTargets(t *testing.T) { } } +func TestGetPrivateIp(t *testing.T) { + testCases := []struct { + testName string + task *ECSTask + ip string + }{ + { + "AWS VPC", + buildWorkloadFargateAwsvpc(true, false), + "10.0.0.129", + }, + { + "Bridge", + buildWorkloadEC2BridgeDynamicPort(true, false), + "10.4.0.205", + }, + { + "AWS VPC w/ no valid address", + &ECSTask{ + Task: &ecs.Task{ + Attachments: []*ecs.Attachment{ + { + Type: aws.String("ElasticNetworkInterface"), + Details: []*ecs.KeyValuePair{ + { + Name: aws.String("networkInterfaceId"), + Value: aws.String("eni-03de9d47faaa2e5ec"), + }, + }, + }, + { + Type: aws.String("invalid"), + }, + }, + }, + TaskDefinition: &ecs.TaskDefinition{ + NetworkMode: aws.String(ecs.NetworkModeAwsvpc), + }, + }, + "", + }, + { + "bridge w/ no EC2 Info", + &ECSTask{ + TaskDefinition: &ecs.TaskDefinition{ + NetworkMode: aws.String(ecs.NetworkModeBridge), + }, + }, + "", + }, + { + "no network mode", + &ECSTask{ + TaskDefinition: &ecs.TaskDefinition{}, + }, + "", + }, + } + + for _, tc := range testCases { + t.Run(tc.testName, func(t *testing.T) { + ip := tc.task.getPrivateIp() + assert.Equal(t, tc.ip, ip) + }) + } +} + func TestAddTargetLabel(t *testing.T) { labels := make(map[string]string) var labelValueB string diff --git a/extension/observer/ecsobserver/extension_test.go b/extension/observer/ecsobserver/extension_test.go new file mode 100644 index 000000000000..6f29f65b11fa --- /dev/null +++ b/extension/observer/ecsobserver/extension_test.go @@ -0,0 +1,73 @@ +// Copyright The 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 ecsobserver + +import ( + "context" + "testing" + "time" + + "github.com/stretchr/testify/require" + "go.opentelemetry.io/collector/component/componenttest" + + "github.com/open-telemetry/opentelemetry-collector-contrib/extension/observer" +) + +type mockNotifier struct { + endpointsMap map[observer.EndpointID]observer.Endpoint +} + +func (m mockNotifier) OnAdd(added []observer.Endpoint) { + for _, e := range added { + m.endpointsMap[e.ID] = e + } +} + +func (m mockNotifier) OnRemove(removed []observer.Endpoint) { + for _, e := range removed { + delete(m.endpointsMap, e.ID) + } +} + +func (m mockNotifier) OnChange(changed []observer.Endpoint) { + for _, e := range changed { + m.endpointsMap[e.ID] = e + } +} + +func TestStartAndStopObserver(t *testing.T) { + config := createDefaultConfig() + sd := &serviceDiscovery{config: config.(*Config)} + sd.init() + + obs := &ecsObserver{ + EndpointsWatcher: observer.EndpointsWatcher{ + RefreshInterval: 10 * time.Second, + Endpointslister: endpointsLister{ + sd: sd, + observerName: "test-observer", + }, + }, + } + + notifier := mockNotifier{map[observer.EndpointID]observer.Endpoint{}} + + ctx := context.Background() + require.NoError(t, obs.Start(ctx, componenttest.NewNopHost())) + obs.ListAndWatch(notifier) + + time.Sleep(2 * time.Second) // Wait a bit to sync endpoints once. + require.NoError(t, obs.Shutdown(ctx)) +} diff --git a/extension/observer/ecsobserver/factory_test.go b/extension/observer/ecsobserver/factory_test.go index d860a4d9e4e2..3a5e597afba5 100644 --- a/extension/observer/ecsobserver/factory_test.go +++ b/extension/observer/ecsobserver/factory_test.go @@ -17,11 +17,13 @@ package ecsobserver import ( "context" "testing" + "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.opentelemetry.io/collector/component" "go.opentelemetry.io/collector/config/configcheck" + "go.opentelemetry.io/collector/config/configmodels" "go.uber.org/zap" ) @@ -39,14 +41,58 @@ func TestCreateDefaultConfig(t *testing.T) { func TestCreateExtension(t *testing.T) { f := NewFactory() - cfg := f.CreateDefaultConfig() - assert.NoError(t, configcheck.ValidateConfig(cfg)) + testCases := []struct { + testName string + config *Config + }{ + { + "default config", + f.CreateDefaultConfig().(*Config), + }, + { + "sample config", + &Config{ + ExtensionSettings: configmodels.ExtensionSettings{ + TypeVal: "ecs_observer", + NameVal: "ecs_observer/1", + }, + RefreshInterval: 15 * time.Second, + ClusterName: "EC2-Testing", + ClusterRegion: "us-west-2", + ResultFile: "/opt/aws/amazon-cloudwatch-agent/etc/ecs_sd_targets.yaml", + DockerLabel: &DockerLabelConfig{ + JobNameLabel: "ECS_PROMETHEUS_JOB_NAME", + MetricsPathLabel: "ECS_PROMETHEUS_METRICS_PATH", + PortLabel: "ECS_PROMETHEUS_EXPORTER_PORT_SUBSET_A", + }, + TaskDefinitions: []*TaskDefinitionConfig{ + &TaskDefinitionConfig{ + JobName: "task_def_1", + MetricsPath: "/stats/metrics", + MetricsPorts: "9901;9404;9406", + TaskDefArnPattern: ".*:task-definition/bugbash-java-fargate-awsvpc-task-def-only:[0-9]+", + }, + &TaskDefinitionConfig{ + ContainerNamePattern: "^bugbash-jar.*$", + MetricsPorts: "9902", + TaskDefArnPattern: ".*:task-definition/nginx:[0-9]+", + }, + }, + logger: zap.NewNop(), + }, + }, + } - ext, err := f.CreateExtension( - context.Background(), - component.ExtensionCreateParams{Logger: zap.NewNop()}, - cfg, - ) - require.NoError(t, err) - require.NotNil(t, ext) + for _, tc := range testCases { + t.Run(tc.testName, func(t *testing.T) { + assert.NoError(t, configcheck.ValidateConfig(tc.config)) + ext, err := f.CreateExtension( + context.Background(), + component.ExtensionCreateParams{Logger: zap.NewNop()}, + tc.config, + ) + require.NoError(t, err) + require.NotNil(t, ext) + }) + } } diff --git a/extension/observer/ecsobserver/servicediscovery_test.go b/extension/observer/ecsobserver/servicediscovery_test.go new file mode 100644 index 000000000000..75c35f892a29 --- /dev/null +++ b/extension/observer/ecsobserver/servicediscovery_test.go @@ -0,0 +1,187 @@ +// Copyright The 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 ecsobserver + +import ( + "testing" + "time" + + "github.com/stretchr/testify/assert" + "go.opentelemetry.io/collector/config/configmodels" + "go.uber.org/zap" + + "github.com/open-telemetry/opentelemetry-collector-contrib/extension/observer" +) + +func TestToPrometheusTarget(t *testing.T) { + testCases := []struct { + testName string + target *Target + promTarget *PrometheusTarget + }{ + { + "valid ip and port w/o labels", + &Target{ + IP: "10.0.0.129", + Port: 9090, + }, + &PrometheusTarget{ + Targets: []string{"10.0.0.129:9090"}, + }, + }, + { + "valid ip and port w/ labels", + &Target{ + IP: "10.0.0.129", + Port: 9090, + Labels: map[string]string{ + "label1": "value1", + "label2": "value2", + }, + }, + &PrometheusTarget{ + Targets: []string{"10.0.0.129:9090"}, + Labels: map[string]string{ + "label1": "value1", + "label2": "value2", + }, + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.testName, func(t *testing.T) { + promTarget := tc.target.toPrometheusTarget() + assert.Equal(t, tc.promTarget, promTarget) + }) + } +} + +func TestToEndpoint(t *testing.T) { + testCases := []struct { + testName string + target *Target + endpoint *observer.Endpoint + }{ + { + "valid ip and port w/o metrics path or labels", + &Target{ + IP: "10.0.0.129", + Port: 9090, + }, + &observer.Endpoint{ + ID: observer.EndpointID("10.0.0.129:9090"), + Target: "10.0.0.129", + Details: observer.Task{ + Port: 9090, + }, + }, + }, + { + "valid ip, port and metrics path w/o labels", + &Target{ + IP: "10.0.0.129", + Port: 9090, + MetricsPath: "/metrics", + }, + &observer.Endpoint{ + ID: observer.EndpointID("10.0.0.129:9090/metrics"), + Target: "10.0.0.129", + Details: observer.Task{ + Port: 9090, + MetricsPath: "/metrics", + }, + }, + }, + { + "valid ip, port, metrics path, and labels", + &Target{ + IP: "10.0.0.129", + Port: 9090, + MetricsPath: "/metrics", + Labels: map[string]string{ + "label1": "value1", + "label2": "value2", + }, + }, + &observer.Endpoint{ + ID: observer.EndpointID("10.0.0.129:9090/metrics"), + Target: "10.0.0.129", + Details: observer.Task{ + Port: 9090, + MetricsPath: "/metrics", + Labels: map[string]string{ + "label1": "value1", + "label2": "value2", + }, + }, + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.testName, func(t *testing.T) { + endpoint := tc.target.toEndpoint() + assert.Equal(t, tc.endpoint, endpoint) + }) + } +} + +func TestInitProcessors(t *testing.T) { + sd := serviceDiscovery{ + config: &Config{ + ExtensionSettings: configmodels.ExtensionSettings{ + TypeVal: "ecs_observer", + NameVal: "ecs_observer/1", + }, + RefreshInterval: 15 * time.Second, + ClusterName: "EC2-Testing", + ClusterRegion: "us-west-2", + ResultFile: "/opt/aws/amazon-cloudwatch-agent/etc/ecs_sd_targets.yaml", + DockerLabel: &DockerLabelConfig{ + JobNameLabel: "ECS_PROMETHEUS_JOB_NAME", + MetricsPathLabel: "ECS_PROMETHEUS_METRICS_PATH", + PortLabel: "ECS_PROMETHEUS_EXPORTER_PORT_SUBSET_A", + }, + TaskDefinitions: []*TaskDefinitionConfig{ + &TaskDefinitionConfig{ + JobName: "task_def_1", + MetricsPath: "/stats/metrics", + MetricsPorts: "9901;9404;9406", + TaskDefArnPattern: ".*:task-definition/bugbash-java-fargate-awsvpc-task-def-only:[0-9]+", + }, + &TaskDefinitionConfig{ + ContainerNamePattern: "^bugbash-jar.*$", + MetricsPorts: "9902", + TaskDefArnPattern: ".*:task-definition/nginx:[0-9]+", + }, + }, + logger: zap.NewNop(), + }, + } + + sd.initProcessors() + + expectedProcessorNames := []string{ + "TaskRetrievalProcessor", + "TaskDefinitionProcessor", + "TaskFilterProcessor", + "MetadataProcessor", + } + + for i, p := range sd.processors { + assert.Equal(t, expectedProcessorNames[i], p.ProcessorName()) + } +} diff --git a/extension/observer/ecsobserver/taskfilterprocessor_test.go b/extension/observer/ecsobserver/taskfilterprocessor_test.go new file mode 100644 index 000000000000..dfee820f6a31 --- /dev/null +++ b/extension/observer/ecsobserver/taskfilterprocessor_test.go @@ -0,0 +1,314 @@ +// Copyright The 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 ecsobserver + +import ( + "testing" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/ecs" + "github.com/stretchr/testify/assert" +) + +func TestProcess(t *testing.T) { + taskList := []*ECSTask{ + &ECSTask{ + TaskDefinition: &ecs.TaskDefinition{ + ContainerDefinitions: []*ecs.ContainerDefinition{ + { + DockerLabels: map[string]*string{ + "LABEL1": nil, + "LABEL2": nil, + }, + }, + { + DockerLabels: map[string]*string{ + "PORT_LABEL": nil, + "LABEL1": nil, + }, + }, + }, + }, + }, + &ECSTask{ + TaskDefinition: &ecs.TaskDefinition{ + TaskDefinitionArn: aws.String("arn:aws:ecs:us-east-2:1234567890:task-definition/prometheus-java-tomcat-fargate-awsvpc:1"), + ContainerDefinitions: []*ecs.ContainerDefinition{ + { + Name: aws.String("envoy"), + }, + }, + }, + }, + &ECSTask{ + TaskDefinition: &ecs.TaskDefinition{ + TaskDefinitionArn: aws.String("arn:aws:ecs:us-east-2:1234567890:task-definition/prometheus-java-tomcat-fargate-awsvpc:1"), + ContainerDefinitions: []*ecs.ContainerDefinition{ + { + Name: aws.String("envoy"), + DockerLabels: map[string]*string{ + "LABEL1": nil, + "LABEL2": nil, + }, + }, + { + DockerLabels: map[string]*string{ + "PORT_LABEL": nil, + "LABEL1": nil, + }, + }, + }, + }, + }, + &ECSTask{ + TaskDefinition: &ecs.TaskDefinition{ + TaskDefinitionArn: aws.String("arn:aws:ecs:us-east-2:1234567890:task-definition/prometheus-java-tomcat-fargate-awsvpc:1"), + ContainerDefinitions: []*ecs.ContainerDefinition{ + { + Name: aws.String("envoy_test"), + DockerLabels: map[string]*string{ + "LABEL1": nil, + "LABEL2": nil, + }, + }, + }, + }, + }, + } + + taskDefsConfig := []*TaskDefinitionConfig{ + {TaskDefArnPattern: "^.*prometheus-java-jar-ec2-bridge:2$"}, + { + TaskDefArnPattern: "^.*prometheus-java-tomcat-fargate-awsvpc:[1-9][0-9]*$", + ContainerNamePattern: "^envoy$", + }, + {TaskDefArnPattern: "^.*task:[0-9]+$"}, + } + for _, taskDef := range taskDefsConfig { + taskDef.init() + } + + p := TaskFilterProcessor{ + dockerPortLabel: "PORT_LABEL", + taskDefsConfig: taskDefsConfig, + } + + processedTasks, err := p.Process("", taskList) + assert.Nil(t, err) + + assert.Equal(t, 3, len(processedTasks)) + assert.Equal(t, true, processedTasks[0].DockerLabelBased) + assert.Equal(t, false, processedTasks[0].TaskDefinitionBased) + assert.Equal(t, false, processedTasks[1].DockerLabelBased) + assert.Equal(t, true, processedTasks[1].TaskDefinitionBased) + assert.Equal(t, true, processedTasks[2].DockerLabelBased) + assert.Equal(t, true, processedTasks[2].TaskDefinitionBased) +} + +func TestDiscoverDockerLabelBased(t *testing.T) { + testCases := []struct { + testName string + containerDefs []*ecs.ContainerDefinition + dockerPortLabel string + isDockerLabelBased bool + }{ + { + "has docker label", + []*ecs.ContainerDefinition{ + { + DockerLabels: map[string]*string{ + "LABEL1": nil, + "LABEL2": nil, + }, + }, + { + DockerLabels: map[string]*string{ + "PORT_LABEL": nil, + "LABEL1": nil, + }, + }, + }, + "PORT_LABEL", + true, + }, + { + "no docker label", + []*ecs.ContainerDefinition{ + { + DockerLabels: map[string]*string{ + "LABEL1": nil, + "LABEL2": nil, + }, + }, + { + DockerLabels: map[string]*string{ + "LABEL1": nil, + }, + }, + }, + "PORT_LABEL", + false, + }, + { + "empty port label", + []*ecs.ContainerDefinition{ + { + DockerLabels: map[string]*string{ + "LABEL1": nil, + "LABEL2": nil, + }, + }, + { + DockerLabels: map[string]*string{ + "PORT_LABEL": nil, + "LABEL1": nil, + }, + }, + }, + "", + false, + }, + } + + for _, tc := range testCases { + t.Run(tc.testName, func(t *testing.T) { + p := TaskFilterProcessor{ + dockerPortLabel: tc.dockerPortLabel, + } + + task := &ECSTask{ + TaskDefinition: &ecs.TaskDefinition{ + ContainerDefinitions: tc.containerDefs, + }, + } + + p.discoverDockerLabelBased(task) + + assert.Equal(t, tc.isDockerLabelBased, task.DockerLabelBased) + }) + } +} + +func TestDiscoverTaskDefinitionBased(t *testing.T) { + testCases := []struct { + testName string + taskDefsConfig []*TaskDefinitionConfig + taskDef *ecs.TaskDefinition + isTaskDefBased bool + }{ + { + "matches a task def ARN pattern w/o container name pattern", + []*TaskDefinitionConfig{ + {TaskDefArnPattern: "^.*prometheus-java-jar-ec2-bridge:2$"}, + {TaskDefArnPattern: "^.*prometheus-java-tomcat-fargate-awsvpc:[1-9][0-9]*$"}, + {TaskDefArnPattern: "^.*task:[0-9]+$"}, + }, + &ecs.TaskDefinition{ + TaskDefinitionArn: aws.String("arn:aws:ecs:us-east-2:1234567890:task-definition/prometheus-java-tomcat-fargate-awsvpc:1"), + }, + true, + }, + { + "matches a task def ARN pattern w/ matching container name pattern", + []*TaskDefinitionConfig{ + {TaskDefArnPattern: "^.*prometheus-java-jar-ec2-bridge:2$"}, + { + TaskDefArnPattern: "^.*prometheus-java-tomcat-fargate-awsvpc:[1-9][0-9]*$", + ContainerNamePattern: "^envoy$", + }, + {TaskDefArnPattern: "^.*task:[0-9]+$"}, + }, + &ecs.TaskDefinition{ + TaskDefinitionArn: aws.String("arn:aws:ecs:us-east-2:1234567890:task-definition/prometheus-java-tomcat-fargate-awsvpc:1"), + ContainerDefinitions: []*ecs.ContainerDefinition{ + { + Name: aws.String("envoy"), + }, + }, + }, + true, + }, + { + "matches a task def ARN pattern w/ no matching container name pattern", + []*TaskDefinitionConfig{ + {TaskDefArnPattern: "^.*prometheus-java-jar-ec2-bridge:2$"}, + { + TaskDefArnPattern: "^.*prometheus-java-tomcat-fargate-awsvpc:[1-9][0-9]*$", + ContainerNamePattern: "^envoy$", + }, + {TaskDefArnPattern: "^.*task:[0-9]+$"}, + }, + &ecs.TaskDefinition{ + TaskDefinitionArn: aws.String("arn:aws:ecs:us-east-2:1234567890:task-definition/prometheus-java-tomcat-fargate-awsvpc:1"), + ContainerDefinitions: []*ecs.ContainerDefinition{ + { + Name: aws.String("envoy_test"), + }, + }, + }, + false, + }, + { + "no match", + []*TaskDefinitionConfig{ + {TaskDefArnPattern: "^.*prometheus-java-jar-ec2-bridge:2$"}, + {TaskDefArnPattern: "^.*prometheus-java-tomcat-fargate-awsvpc:[1-9][0-9]*$"}, + {TaskDefArnPattern: "^.*task:[0-9]+$"}, + }, + &ecs.TaskDefinition{ + TaskDefinitionArn: aws.String("arn:aws:ecs:us-east-2:1234567890:task-definition/prometheus-java-jar-ec2-bridge:3"), + }, + false, + }, + { + "no task def ARN", + []*TaskDefinitionConfig{ + {TaskDefArnPattern: "^.*prometheus-java-jar-ec2-bridge:2$"}, + {TaskDefArnPattern: "^.*prometheus-java-tomcat-fargate-awsvpc:[1-9][0-9]*$"}, + {TaskDefArnPattern: "^.*task:[0-9]+$"}, + }, + &ecs.TaskDefinition{ + TaskDefinitionArn: nil, + }, + false, + }, + { + "no task def ARN pattern", + nil, + &ecs.TaskDefinition{ + TaskDefinitionArn: aws.String("arn:aws:ecs:us-east-2:1234567890:task-definition/prometheus-java-tomcat-fargate-awsvpc:1"), + }, + false, + }, + } + + for _, tc := range testCases { + t.Run(tc.testName, func(t *testing.T) { + for _, taskDef := range tc.taskDefsConfig { + taskDef.init() + } + p := TaskFilterProcessor{ + taskDefsConfig: tc.taskDefsConfig, + } + + task := &ECSTask{ + TaskDefinition: tc.taskDef, + } + + p.discoverTaskDefinitionBased(task) + assert.Equal(t, tc.isTaskDefBased, task.TaskDefinitionBased) + }) + } +} From f5a17549089a3105d98e700ea37efcd52063dd65 Mon Sep 17 00:00:00 2001 From: Raphael Koh Date: Fri, 8 Jan 2021 12:35:54 -0500 Subject: [PATCH 17/19] Add Makefile --- extension/observer/ecsobserver/Makefile | 1 + 1 file changed, 1 insertion(+) create mode 100644 extension/observer/ecsobserver/Makefile diff --git a/extension/observer/ecsobserver/Makefile b/extension/observer/ecsobserver/Makefile new file mode 100644 index 000000000000..bdd863a203be --- /dev/null +++ b/extension/observer/ecsobserver/Makefile @@ -0,0 +1 @@ +include ../../../Makefile.Common From 608aef74648ee004fb50e98cc6d743efab324f23 Mon Sep 17 00:00:00 2001 From: Raphael Koh Date: Fri, 8 Jan 2021 19:46:26 -0500 Subject: [PATCH 18/19] Format and fix lint errors --- extension/observer/ecsobserver/config_test.go | 4 +- extension/observer/ecsobserver/ecstask.go | 24 ++++++------ .../observer/ecsobserver/ecstask_test.go | 38 +++++++++---------- extension/observer/ecsobserver/extension.go | 11 +++--- .../observer/ecsobserver/factory_test.go | 4 +- .../observer/ecsobserver/metadataprocessor.go | 18 ++++----- .../observer/ecsobserver/servicediscovery.go | 9 +++-- .../ecsobserver/servicediscovery_test.go | 4 +- .../ecsobserver/taskdefinitionprocessor.go | 2 +- .../ecsobserver/taskfilterprocessor_test.go | 12 +++--- .../ecsobserver/taskretrievalprocessor.go | 4 +- 11 files changed, 66 insertions(+), 64 deletions(-) diff --git a/extension/observer/ecsobserver/config_test.go b/extension/observer/ecsobserver/config_test.go index 9cc546e623d8..68b7bbad3f77 100644 --- a/extension/observer/ecsobserver/config_test.go +++ b/extension/observer/ecsobserver/config_test.go @@ -60,13 +60,13 @@ func TestLoadConfig(t *testing.T) { PortLabel: "ECS_PROMETHEUS_EXPORTER_PORT_SUBSET_A", }, TaskDefinitions: []*TaskDefinitionConfig{ - &TaskDefinitionConfig{ + { JobName: "task_def_1", MetricsPath: "/stats/metrics", MetricsPorts: "9901;9404;9406", TaskDefArnPattern: ".*:task-definition/bugbash-java-fargate-awsvpc-task-def-only:[0-9]+", }, - &TaskDefinitionConfig{ + { ContainerNamePattern: "^bugbash-jar.*$", MetricsPorts: "9902", TaskDefArnPattern: ".*:task-definition/nginx:[0-9]+", diff --git a/extension/observer/ecsobserver/ecstask.go b/extension/observer/ecsobserver/ecstask.go index 22ffe21069a8..f9729b112f47 100644 --- a/extension/observer/ecsobserver/ecstask.go +++ b/extension/observer/ecsobserver/ecstask.go @@ -32,17 +32,17 @@ const ( taskJobNameLabel = "job" taskMetricsPathLabel = "__metrics_path__" ec2InstanceTypeLabel = "InstanceType" - ec2VpcIdLabel = "VpcId" - ec2SubnetIdLabel = "SubnetId" + ec2VpcIDLabel = "VpcId" + ec2SubnetIDLabel = "SubnetId" ) type EC2MetaData struct { - ContainerInstanceId string - ECInstanceId string + ContainerInstanceID string + ECInstanceID string PrivateIP string InstanceType string - VpcId string - SubnetId string + VpcID string + SubnetID string } type ECSTask struct { @@ -55,7 +55,7 @@ type ECSTask struct { } func (t *ECSTask) addTargets(targets map[string]*Target, config *Config) { - ip := t.getPrivateIp() + ip := t.getPrivateIP() if ip == "" { return } @@ -65,8 +65,8 @@ func (t *ECSTask) addTargets(targets map[string]*Target, config *Config) { } } -// getPrivateIp retrieves the private ip of the ECS task. -func (t *ECSTask) getPrivateIp() (ip string) { +// getPrivateIP retrieves the private ip of the ECS task. +func (t *ECSTask) getPrivateIP() (ip string) { if t.TaskDefinition.NetworkMode == nil { return } @@ -226,15 +226,15 @@ func (t *ECSTask) generateTargetLabels(c *ecs.ContainerDefinition, metricsPath s if t.EC2Info != nil { addTargetLabel(labels, ec2InstanceTypeLabel, &t.EC2Info.InstanceType) - addTargetLabel(labels, ec2SubnetIdLabel, &t.EC2Info.SubnetId) - addTargetLabel(labels, ec2VpcIdLabel, &t.EC2Info.VpcId) + addTargetLabel(labels, ec2SubnetIDLabel, &t.EC2Info.SubnetID) + addTargetLabel(labels, ec2VpcIDLabel, &t.EC2Info.VpcID) } for k, v := range c.DockerLabels { addTargetLabel(labels, k, v) } - // handle customized job label last, so the previous job docker label is overriden + // handle customized job label last, so the previous job docker label is overridden addTargetLabel(labels, taskJobNameLabel, &jobName) return labels diff --git a/extension/observer/ecsobserver/ecstask_test.go b/extension/observer/ecsobserver/ecstask_test.go index 3f951c2c7b17..9134bb4fc284 100644 --- a/extension/observer/ecsobserver/ecstask_test.go +++ b/extension/observer/ecsobserver/ecstask_test.go @@ -26,7 +26,7 @@ import ( func buildWorkloadFargateAwsvpc(dockerLabel bool, taskDef bool) *ECSTask { networkMode := ecs.NetworkModeAwsvpc - taskAttachmentId := "775c6c63-b5f7-4a5b-8a60-8f8295a04cda" + taskAttachmentID := "775c6c63-b5f7-4a5b-8a60-8f8295a04cda" taskAttachmentType := "ElasticNetworkInterface" taskAttachmentStatus := "ATTACHING" taskAttachmentDetailsKey1 := "networkInterfaceId" @@ -53,7 +53,7 @@ func buildWorkloadFargateAwsvpc(dockerLabel bool, taskDef bool) *ECSTask { TaskDefinitionArn: &taskDefinitionArn, Attachments: []*ecs.Attachment{ { - Id: &taskAttachmentId, + Id: &taskAttachmentID, Type: &taskAttachmentType, Status: &taskAttachmentStatus, Details: []*ecs.KeyValuePair{ @@ -129,12 +129,12 @@ func buildWorkloadEC2BridgeDynamicPort(dockerLabel bool, taskDef bool) *ECSTask DockerLabelBased: dockerLabel, TaskDefinitionBased: taskDef, EC2Info: &EC2MetaData{ - ContainerInstanceId: "arn:aws:ecs:us-east-2:211220956907:container-instance/7b0a9662-ee0b-4cf6-9391-03f50ca501a5", - ECInstanceId: "i-02aa8e82e91b2c30e", + ContainerInstanceID: "arn:aws:ecs:us-east-2:211220956907:container-instance/7b0a9662-ee0b-4cf6-9391-03f50ca501a5", + ECInstanceID: "i-02aa8e82e91b2c30e", PrivateIP: "10.4.0.205", InstanceType: "t3.medium", - VpcId: "vpc-03e9f55a92516a5e4", - SubnetId: "subnet-0d0b0212d14b70250", + VpcID: "vpc-03e9f55a92516a5e4", + SubnetID: "subnet-0d0b0212d14b70250", }, Task: &ecs.Task{ TaskDefinitionArn: &taskDefinitionArn, @@ -216,7 +216,7 @@ func TestAddTargets(t *testing.T) { }, }, map[string]*Target{ - "10.0.0.129:9404": &Target{ + "10.0.0.129:9404": { IP: "10.0.0.129", Port: 9404, MetricsPath: "", @@ -228,7 +228,7 @@ func TestAddTargets(t *testing.T) { "FARGATE_PROMETHEUS_JOB_NAME": "java-tomcat-fargate-awsvpc", }, }, - "10.0.0.129:9406/metrics": &Target{ + "10.0.0.129:9406/metrics": { IP: "10.0.0.129", Port: 9406, MetricsPath: "/metrics", @@ -256,7 +256,7 @@ func TestAddTargets(t *testing.T) { }, }, map[string]*Target{ - "10.0.0.129:9404/stats/metrics": &Target{ + "10.0.0.129:9404/stats/metrics": { IP: "10.0.0.129", Port: 9404, MetricsPath: "/stats/metrics", @@ -268,7 +268,7 @@ func TestAddTargets(t *testing.T) { "FARGATE_PROMETHEUS_JOB_NAME": "java-tomcat-fargate-awsvpc", }, }, - "10.0.0.129:9406/stats/metrics": &Target{ + "10.0.0.129:9406/stats/metrics": { IP: "10.0.0.129", Port: 9406, MetricsPath: "/stats/metrics", @@ -297,7 +297,7 @@ func TestAddTargets(t *testing.T) { }, }, map[string]*Target{ - "10.4.0.205:9494/metrics": &Target{ + "10.4.0.205:9494/metrics": { IP: "10.4.0.205", Port: 9494, MetricsPath: "/metrics", @@ -333,7 +333,7 @@ func TestAddTargets(t *testing.T) { }, }, map[string]*Target{ - "10.0.0.129:9404": &Target{ + "10.0.0.129:9404": { IP: "10.0.0.129", Port: 9404, MetricsPath: "", @@ -345,7 +345,7 @@ func TestAddTargets(t *testing.T) { "FARGATE_PROMETHEUS_JOB_NAME": "java-tomcat-fargate-awsvpc", }, }, - "10.0.0.129:9406/metrics": &Target{ + "10.0.0.129:9406/metrics": { IP: "10.0.0.129", Port: 9406, MetricsPath: "/metrics", @@ -357,7 +357,7 @@ func TestAddTargets(t *testing.T) { "ECS_PROMETHEUS_METRICS_PATH": "/metrics", }, }, - "10.0.0.129:9404/stats/metrics": &Target{ + "10.0.0.129:9404/stats/metrics": { IP: "10.0.0.129", Port: 9404, MetricsPath: "/stats/metrics", @@ -369,7 +369,7 @@ func TestAddTargets(t *testing.T) { "FARGATE_PROMETHEUS_JOB_NAME": "java-tomcat-fargate-awsvpc", }, }, - "10.0.0.129:9406/stats/metrics": &Target{ + "10.0.0.129:9406/stats/metrics": { IP: "10.0.0.129", Port: 9406, MetricsPath: "/stats/metrics", @@ -402,7 +402,7 @@ func TestAddTargets(t *testing.T) { }, }, map[string]*Target{ - "10.4.0.205:32774/metrics": &Target{ + "10.4.0.205:32774/metrics": { IP: "10.4.0.205", Port: 32774, MetricsPath: "/metrics", @@ -417,7 +417,7 @@ func TestAddTargets(t *testing.T) { "__metrics_path__": "/metrics", }, }, - "10.4.0.205:9494": &Target{ + "10.4.0.205:9494": { IP: "10.4.0.205", Port: 9494, MetricsPath: "", @@ -432,7 +432,7 @@ func TestAddTargets(t *testing.T) { "EC2_PROMETHEUS_EXPORTER_PORT": "9404", }, }, - "10.4.0.205:9494/metrics": &Target{ + "10.4.0.205:9494/metrics": { IP: "10.4.0.205", Port: 9494, MetricsPath: "/metrics", @@ -548,7 +548,7 @@ func TestGetPrivateIp(t *testing.T) { for _, tc := range testCases { t.Run(tc.testName, func(t *testing.T) { - ip := tc.task.getPrivateIp() + ip := tc.task.getPrivateIP() assert.Equal(t, tc.ip, ip) }) } diff --git a/extension/observer/ecsobserver/extension.go b/extension/observer/ecsobserver/extension.go index 3283bd947025..1515cfa07131 100644 --- a/extension/observer/ecsobserver/extension.go +++ b/extension/observer/ecsobserver/extension.go @@ -17,13 +17,14 @@ package ecsobserver import ( "context" "fmt" - "gopkg.in/yaml.v2" "io/ioutil" "os" - "github.com/open-telemetry/opentelemetry-collector-contrib/extension/observer" "go.opentelemetry.io/collector/component" "go.uber.org/zap" + "gopkg.in/yaml.v2" + + "github.com/open-telemetry/opentelemetry-collector-contrib/extension/observer" ) type ecsObserver struct { @@ -104,18 +105,18 @@ func writeTargetsToFile(targets map[string]*Target, filePath string) error { m, err := yaml.Marshal(promTargets) if err != nil { - return fmt.Errorf("Failed to marshal Prometheus targets. Error: %s", err.Error()) + return fmt.Errorf("failed to marshal Prometheus targets. Error: %s", err.Error()) } err = ioutil.WriteFile(tmpFilePath, m, 0644) if err != nil { - return fmt.Errorf("Failed to marshal Prometheus targets into file: %s. Error: %s", tmpFilePath, err.Error()) + return fmt.Errorf("failed to marshal Prometheus targets into file: %s. Error: %s", tmpFilePath, err.Error()) } err = os.Rename(tmpFilePath, filePath) if err != nil { os.Remove(tmpFilePath) - return fmt.Errorf("Failed to rename tmp result file %s to %s. Error: %s", tmpFilePath, filePath, err.Error()) + return fmt.Errorf("failed to rename tmp result file %s to %s. Error: %s", tmpFilePath, filePath, err.Error()) } return nil diff --git a/extension/observer/ecsobserver/factory_test.go b/extension/observer/ecsobserver/factory_test.go index 3a5e597afba5..1716611fcd86 100644 --- a/extension/observer/ecsobserver/factory_test.go +++ b/extension/observer/ecsobserver/factory_test.go @@ -66,13 +66,13 @@ func TestCreateExtension(t *testing.T) { PortLabel: "ECS_PROMETHEUS_EXPORTER_PORT_SUBSET_A", }, TaskDefinitions: []*TaskDefinitionConfig{ - &TaskDefinitionConfig{ + { JobName: "task_def_1", MetricsPath: "/stats/metrics", MetricsPorts: "9901;9404;9406", TaskDefArnPattern: ".*:task-definition/bugbash-java-fargate-awsvpc-task-def-only:[0-9]+", }, - &TaskDefinitionConfig{ + { ContainerNamePattern: "^bugbash-jar.*$", MetricsPorts: "9902", TaskDefArnPattern: ".*:task-definition/nginx:[0-9]+", diff --git a/extension/observer/ecsobserver/metadataprocessor.go b/extension/observer/ecsobserver/metadataprocessor.go index a6c3006b8f62..6630e073f069 100644 --- a/extension/observer/ecsobserver/metadataprocessor.go +++ b/extension/observer/ecsobserver/metadataprocessor.go @@ -15,9 +15,9 @@ package ecsobserver import ( - "github.com/aws/aws-sdk-go/aws" "fmt" + "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/ec2" "github.com/aws/aws-sdk-go/service/ecs" "github.com/hashicorp/golang-lru/simplelru" @@ -52,12 +52,12 @@ func (p *MetadataProcessor) Process(clusterName string, taskList []*ECSTask) ([] if aws.StringValue(task.Task.LaunchType) != ecs.LaunchTypeEc2 { continue } - + ciArn := aws.StringValue(task.Task.ContainerInstanceArn) if ciArn == "" { continue } - + if res, ok := p.ec2Cache.Get(ciArn); ok { // Try retrieving from cache task.EC2Info = res.(*EC2MetaData) @@ -107,7 +107,7 @@ func (p *MetadataProcessor) getEC2Metadata(clusterName string, arns []string, ec } resp, ecsErr := p.svcEcs.DescribeContainerInstances(ecsInput) if ecsErr != nil { - return fmt.Errorf("Failed to DescribeContainerInstances. Error: %s", ecsErr.Error()) + return fmt.Errorf("failed to DescribeContainerInstances. Error: %s", ecsErr.Error()) } for _, f := range resp.Failures { @@ -137,7 +137,7 @@ func (p *MetadataProcessor) getEC2Metadata(clusterName string, arns []string, ec for { ec2resp, ec2err := p.svcEc2.DescribeInstances(ec2input) if ec2err != nil { - return fmt.Errorf("Failed to DescribeInstances. Error: %s", ec2err.Error()) + return fmt.Errorf("failed to DescribeInstances. Error: %s", ec2err.Error()) } for _, rsv := range ec2resp.Reservations { @@ -152,12 +152,12 @@ func (p *MetadataProcessor) getEC2Metadata(clusterName string, arns []string, ec } ciArn := aws.StringValue(ciArnPtr) metadata := &EC2MetaData{ - ContainerInstanceId: ciArn, - ECInstanceId: ec2Id, + ContainerInstanceID: ciArn, + ECInstanceID: ec2Id, PrivateIP: aws.StringValue(instance.PrivateIpAddress), InstanceType: aws.StringValue(instance.InstanceType), - VpcId: aws.StringValue(instance.VpcId), - SubnetId: aws.StringValue(instance.SubnetId), + VpcID: aws.StringValue(instance.VpcId), + SubnetID: aws.StringValue(instance.SubnetId), } ec2MetadataMap[ciArn] = metadata p.ec2Cache.Add(ciArn, metadata) diff --git a/extension/observer/ecsobserver/servicediscovery.go b/extension/observer/ecsobserver/servicediscovery.go index 095b4aba9e80..1389356c4c83 100644 --- a/extension/observer/ecsobserver/servicediscovery.go +++ b/extension/observer/ecsobserver/servicediscovery.go @@ -30,9 +30,6 @@ import ( const ( AwsSdkLevelRetryCount = 3 - - //https://prometheus.io/docs/prometheus/latest/configuration/configuration/#scrape_config - defaultPrometheusMetricsPath = "/metrics" ) // PrometheusTarget represents a discovered Prometheus target. @@ -84,7 +81,11 @@ type serviceDiscovery struct { func (sd *serviceDiscovery) init() { region := getAWSRegion(sd.config) awsConfig := aws.NewConfig().WithRegion(region).WithMaxRetries(AwsSdkLevelRetryCount) - session := session.New(awsConfig) + session, err := session.NewSession(awsConfig) + if err != nil { + sd.config.logger.Fatal("Failed to create AWS session", zap.Error(err)) + panic(err) + } sd.svcEcs = ecs.New(session, awsConfig) sd.svcEc2 = ec2.New(session, awsConfig) sd.initProcessors() diff --git a/extension/observer/ecsobserver/servicediscovery_test.go b/extension/observer/ecsobserver/servicediscovery_test.go index 75c35f892a29..d734cd87d7ae 100644 --- a/extension/observer/ecsobserver/servicediscovery_test.go +++ b/extension/observer/ecsobserver/servicediscovery_test.go @@ -156,13 +156,13 @@ func TestInitProcessors(t *testing.T) { PortLabel: "ECS_PROMETHEUS_EXPORTER_PORT_SUBSET_A", }, TaskDefinitions: []*TaskDefinitionConfig{ - &TaskDefinitionConfig{ + { JobName: "task_def_1", MetricsPath: "/stats/metrics", MetricsPorts: "9901;9404;9406", TaskDefArnPattern: ".*:task-definition/bugbash-java-fargate-awsvpc-task-def-only:[0-9]+", }, - &TaskDefinitionConfig{ + { ContainerNamePattern: "^bugbash-jar.*$", MetricsPorts: "9902", TaskDefArnPattern: ".*:task-definition/nginx:[0-9]+", diff --git a/extension/observer/ecsobserver/taskdefinitionprocessor.go b/extension/observer/ecsobserver/taskdefinitionprocessor.go index be96ce9962e2..0fc991acdc10 100644 --- a/extension/observer/ecsobserver/taskdefinitionprocessor.go +++ b/extension/observer/ecsobserver/taskdefinitionprocessor.go @@ -57,7 +57,7 @@ func (p *TaskDefinitionProcessor) Process(cluster string, taskList []*ECSTask) ( input := &ecs.DescribeTaskDefinitionInput{TaskDefinition: &arn} resp, err := p.svcEcs.DescribeTaskDefinition(input) if err != nil { - return taskList, fmt.Errorf("Failed to describe task definition for %s. Error: %s", arn, err.Error()) + return taskList, fmt.Errorf("failed to describe task definition for %s. Error: %s", arn, err.Error()) } if taskDef := resp.TaskDefinition; taskDef != nil { diff --git a/extension/observer/ecsobserver/taskfilterprocessor_test.go b/extension/observer/ecsobserver/taskfilterprocessor_test.go index dfee820f6a31..a2130dccedfd 100644 --- a/extension/observer/ecsobserver/taskfilterprocessor_test.go +++ b/extension/observer/ecsobserver/taskfilterprocessor_test.go @@ -24,7 +24,7 @@ import ( func TestProcess(t *testing.T) { taskList := []*ECSTask{ - &ECSTask{ + { TaskDefinition: &ecs.TaskDefinition{ ContainerDefinitions: []*ecs.ContainerDefinition{ { @@ -42,7 +42,7 @@ func TestProcess(t *testing.T) { }, }, }, - &ECSTask{ + { TaskDefinition: &ecs.TaskDefinition{ TaskDefinitionArn: aws.String("arn:aws:ecs:us-east-2:1234567890:task-definition/prometheus-java-tomcat-fargate-awsvpc:1"), ContainerDefinitions: []*ecs.ContainerDefinition{ @@ -52,7 +52,7 @@ func TestProcess(t *testing.T) { }, }, }, - &ECSTask{ + { TaskDefinition: &ecs.TaskDefinition{ TaskDefinitionArn: aws.String("arn:aws:ecs:us-east-2:1234567890:task-definition/prometheus-java-tomcat-fargate-awsvpc:1"), ContainerDefinitions: []*ecs.ContainerDefinition{ @@ -72,7 +72,7 @@ func TestProcess(t *testing.T) { }, }, }, - &ECSTask{ + { TaskDefinition: &ecs.TaskDefinition{ TaskDefinitionArn: aws.String("arn:aws:ecs:us-east-2:1234567890:task-definition/prometheus-java-tomcat-fargate-awsvpc:1"), ContainerDefinitions: []*ecs.ContainerDefinition{ @@ -91,7 +91,7 @@ func TestProcess(t *testing.T) { taskDefsConfig := []*TaskDefinitionConfig{ {TaskDefArnPattern: "^.*prometheus-java-jar-ec2-bridge:2$"}, { - TaskDefArnPattern: "^.*prometheus-java-tomcat-fargate-awsvpc:[1-9][0-9]*$", + TaskDefArnPattern: "^.*prometheus-java-tomcat-fargate-awsvpc:[1-9][0-9]*$", ContainerNamePattern: "^envoy$", }, {TaskDefArnPattern: "^.*task:[0-9]+$"}, @@ -102,7 +102,7 @@ func TestProcess(t *testing.T) { p := TaskFilterProcessor{ dockerPortLabel: "PORT_LABEL", - taskDefsConfig: taskDefsConfig, + taskDefsConfig: taskDefsConfig, } processedTasks, err := p.Process("", taskList) diff --git a/extension/observer/ecsobserver/taskretrievalprocessor.go b/extension/observer/ecsobserver/taskretrievalprocessor.go index cd663be6c66d..63d4a1167e63 100644 --- a/extension/observer/ecsobserver/taskretrievalprocessor.go +++ b/extension/observer/ecsobserver/taskretrievalprocessor.go @@ -39,7 +39,7 @@ func (p *TaskRetrievalProcessor) Process(clusterName string, taskList []*ECSTask // List all running task ARNs in the cluster listTasksResp, listTasksErr := p.svcEcs.ListTasks(listTasksInput) if listTasksErr != nil { - return taskList, fmt.Errorf("Failed to list task ARNs for %s. Error: %s", clusterName, listTasksErr.Error()) + return taskList, fmt.Errorf("failed to list task ARNs for %s. Error: %s", clusterName, listTasksErr.Error()) } // Retrieve tasks from task ARNs @@ -49,7 +49,7 @@ func (p *TaskRetrievalProcessor) Process(clusterName string, taskList []*ECSTask } descTasksResp, descTasksErr := p.svcEcs.DescribeTasks(descTasksInput) if descTasksErr != nil { - return taskList, fmt.Errorf("Failed to describe ECS Tasks for %s. Error: %s", clusterName, descTasksErr.Error()) + return taskList, fmt.Errorf("failed to describe ECS Tasks for %s. Error: %s", clusterName, descTasksErr.Error()) } for _, f := range descTasksResp.Failures { From d55f5cfac610ff3d83390d4bb867a720fbbd5b33 Mon Sep 17 00:00:00 2001 From: Raphael Koh Date: Sun, 10 Jan 2021 15:15:57 -0500 Subject: [PATCH 19/19] Format endpoints.go --- extension/observer/endpoints.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/extension/observer/endpoints.go b/extension/observer/endpoints.go index 911575e98f76..13d9c0947224 100644 --- a/extension/observer/endpoints.go +++ b/extension/observer/endpoints.go @@ -35,11 +35,11 @@ func (e *Endpoint) String() string { // Task is a discovered ECS task. type Task struct { // Port number of the endpoint. - Port int64 + Port int64 // MetricsPath is the metrics path of the endpoint. MetricsPath string // Labels exported by the ECS Observer (mainly for use with Prometheus). - Labels map[string]string + Labels map[string]string } // Pod is a discovered k8s pod.