diff --git a/docs/INTEGRATION_TESTS.md b/docs/INTEGRATION_TESTS.md index ef76332951ca9..51ae42ee887d7 100644 --- a/docs/INTEGRATION_TESTS.md +++ b/docs/INTEGRATION_TESTS.md @@ -52,6 +52,7 @@ Current areas we have integration tests: | Outputs: MQTT | | | Outputs: Nats | | | Outputs: NSQ | | +| Outputs: Postgresql | | Areas we would benefit most from new integration tests: diff --git a/docs/LICENSE_OF_DEPENDENCIES.md b/docs/LICENSE_OF_DEPENDENCIES.md index 31d9bc7d30380..8e0cc396ff96e 100644 --- a/docs/LICENSE_OF_DEPENDENCIES.md +++ b/docs/LICENSE_OF_DEPENDENCIES.md @@ -17,6 +17,9 @@ following works: - github.com/Azure/go-autorest [Apache License 2.0](https://github.com/Azure/go-autorest/blob/master/LICENSE) - github.com/Azure/go-ntlmssp [MIT License](https://github.com/Azure/go-ntlmssp/blob/master/LICENSE) - github.com/ClickHouse/clickhouse-go [MIT License](https://github.com/ClickHouse/clickhouse-go/blob/master/LICENSE) +- github.com/Masterminds/goutils [Apache License 2.0](https://github.com/Masterminds/goutils/blob/master/LICENSE.txt) +- github.com/Masterminds/semver [MIT License](https://github.com/Masterminds/semver/blob/master/LICENSE.txt) +- github.com/Masterminds/sprig [MIT License](https://github.com/Masterminds/sprig/blob/master/LICENSE.txt) - github.com/Mellanox/rdmamap [Apache License 2.0](https://github.com/Mellanox/rdmamap/blob/master/LICENSE) - github.com/Microsoft/go-winio [MIT License](https://github.com/Microsoft/go-winio/blob/master/LICENSE) - github.com/Shopify/sarama [MIT License](https://github.com/Shopify/sarama/blob/master/LICENSE) @@ -67,6 +70,7 @@ following works: - github.com/cespare/xxhash [MIT License](https://github.com/cespare/xxhash/blob/master/LICENSE.txt) - github.com/cisco-ie/nx-telemetry-proto [Apache License 2.0](https://github.com/cisco-ie/nx-telemetry-proto/blob/master/LICENSE) - github.com/containerd/containerd [Apache License 2.0](https://github.com/containerd/containerd/blob/master/LICENSE) +- github.com/coocood/freecache [MIT License](https://github.com/coocood/freecache/blob/master/LICENSE) - github.com/coreos/go-semver [Apache License 2.0](https://github.com/coreos/go-semver/blob/main/LICENSE) - github.com/coreos/go-systemd [Apache License 2.0](https://github.com/coreos/go-systemd/blob/main/LICENSE) - github.com/couchbase/go-couchbase [MIT License](https://github.com/couchbase/go-couchbase/blob/master/LICENSE) @@ -135,6 +139,8 @@ following works: - github.com/hashicorp/go-uuid [Mozilla Public License 2.0](https://github.com/hashicorp/go-uuid/blob/master/LICENSE) - github.com/hashicorp/golang-lru [Mozilla Public License 2.0](https://github.com/hashicorp/golang-lru/blob/master/LICENSE) - github.com/hashicorp/serf [Mozilla Public License 2.0](https://github.com/hashicorp/serf/blob/master/LICENSE) +- github.com/huandu/xstrings [MIT License](https://github.com/huandu/xstrings/blob/master/LICENSE) +- github.com/imdario/mergo [BSD 3-Clause "New" or "Revised" License](https://github.com/imdario/mergo/blob/master/LICENSE) - github.com/influxdata/go-syslog [MIT License](https://github.com/influxdata/go-syslog/blob/develop/LICENSE) - github.com/influxdata/influxdb-observability/common [MIT License](https://github.com/influxdata/influxdb-observability/blob/main/LICENSE) - github.com/influxdata/influxdb-observability/influx2otel [MIT License](https://github.com/influxdata/influxdb-observability/blob/main/LICENSE) @@ -151,6 +157,7 @@ following works: - github.com/jackc/pgservicefile [MIT License](https://github.com/jackc/pgservicefile/blob/master/LICENSE) - github.com/jackc/pgtype [MIT License](https://github.com/jackc/pgtype/blob/master/LICENSE) - github.com/jackc/pgx [MIT License](https://github.com/jackc/pgx/blob/master/LICENSE) +- github.com/jackc/puddle [MIT License](https://github.com/jackc/puddle/blob/master/LICENSE) - github.com/jaegertracing/jaeger [Apache License 2.0](https://github.com/jaegertracing/jaeger/blob/master/LICENSE) - github.com/james4k/rcon [MIT License](https://github.com/james4k/rcon/blob/master/LICENSE) - github.com/jcmturner/aescts [Apache License 2.0](https://github.com/jcmturner/aescts/blob/master/LICENSE) @@ -182,8 +189,10 @@ following works: - github.com/microsoft/ApplicationInsights-Go [MIT License](https://github.com/microsoft/ApplicationInsights-Go/blob/master/LICENSE) - github.com/miekg/dns [BSD 3-Clause Clear License](https://github.com/miekg/dns/blob/master/LICENSE) - github.com/minio/highwayhash [Apache License 2.0](https://github.com/minio/highwayhash/blob/master/LICENSE) +- github.com/mitchellh/copystructure [MIT License](https://github.com/mitchellh/copystructure/blob/master/LICENSE) - github.com/mitchellh/go-homedir [MIT License](https://github.com/mitchellh/go-homedir/blob/master/LICENSE) - github.com/mitchellh/mapstructure [MIT License](https://github.com/mitchellh/mapstructure/blob/master/LICENSE) +- github.com/mitchellh/reflectwalk [MIT License](https://github.com/mitchellh/reflectwalk/blob/master/LICENSE) - github.com/moby/ipvs [Apache License 2.0](https://github.com/moby/ipvs/blob/master/LICENSE) - github.com/modern-go/concurrent [Apache License 2.0](https://github.com/modern-go/concurrent/blob/master/LICENSE) - github.com/modern-go/reflect2 [Apache License 2.0](https://github.com/modern-go/reflect2/blob/master/LICENSE) diff --git a/go.mod b/go.mod index 0ca27da671f6f..bd0157adb5fdf 100644 --- a/go.mod +++ b/go.mod @@ -15,6 +15,7 @@ require ( github.com/Azure/go-autorest/autorest/azure/auth v0.5.11 github.com/BurntSushi/toml v0.4.1 github.com/ClickHouse/clickhouse-go v1.5.1 + github.com/Masterminds/sprig v2.22.0+incompatible github.com/Mellanox/rdmamap v0.0.0-20191106181932-7c3c4763a6ee github.com/Shopify/sarama v1.29.1 github.com/aerospike/aerospike-client-go v1.27.0 @@ -43,6 +44,7 @@ require ( github.com/bmatcuk/doublestar/v3 v3.0.0 github.com/caio/go-tdigest v3.1.0+incompatible github.com/cisco-ie/nx-telemetry-proto v0.0.0-20190531143454-82441e232cf6 + github.com/coocood/freecache v1.2.0 github.com/coreos/go-semver v0.3.0 github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f github.com/couchbase/go-couchbase v0.1.1 @@ -85,6 +87,9 @@ require ( github.com/influxdata/toml v0.0.0-20190415235208-270119a8ce65 github.com/influxdata/wlog v0.0.0-20160411224016-7c63b0a71ef8 github.com/intel/iaevents v1.0.0 + github.com/jackc/pgconn v1.10.1 + github.com/jackc/pgio v1.0.0 + github.com/jackc/pgtype v1.9.1 github.com/jackc/pgx/v4 v4.14.1 github.com/james4k/rcon v0.0.0-20120923215419-8fbb8268b60a github.com/jhump/protoreflect v1.8.3-0.20210616212123-6cc1efa697ca @@ -181,6 +186,8 @@ require ( github.com/Azure/go-autorest/logger v0.2.1 // indirect github.com/Azure/go-autorest/tracing v0.6.0 // indirect github.com/Azure/go-ntlmssp v0.0.0-20200615164410-66371956d46c // indirect + github.com/Masterminds/goutils v1.1.1 // indirect + github.com/Masterminds/semver v1.5.0 // indirect github.com/Microsoft/go-winio v0.4.17 // indirect github.com/Microsoft/hcsshim v0.8.23 // indirect github.com/alecthomas/participle v0.4.1 // indirect @@ -205,6 +212,7 @@ require ( github.com/bitly/go-hostpool v0.1.0 // indirect github.com/cenkalti/backoff v2.2.1+incompatible // indirect github.com/cenkalti/backoff/v4 v4.1.2 // indirect + github.com/cespare/xxhash v1.1.0 // indirect github.com/cespare/xxhash/v2 v2.1.2 // indirect github.com/cloudflare/golz4 v0.0.0-20150217214814-ef862a3cdc58 // indirect github.com/containerd/cgroups v1.0.1 // indirect @@ -249,13 +257,13 @@ require ( github.com/hashicorp/go-rootcerts v1.0.2 // indirect github.com/hashicorp/golang-lru v0.5.4 // indirect github.com/hashicorp/serf v0.9.6 // indirect + github.com/huandu/xstrings v1.3.2 // indirect + github.com/imdario/mergo v0.3.12 // indirect github.com/jackc/chunkreader/v2 v2.0.1 // indirect - github.com/jackc/pgconn v1.10.1 // indirect - github.com/jackc/pgio v1.0.0 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgproto3/v2 v2.2.0 // indirect github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b // indirect - github.com/jackc/pgtype v1.9.1 // indirect + github.com/jackc/puddle v1.2.0 // indirect github.com/jaegertracing/jaeger v1.26.0 // indirect github.com/jcmturner/aescts/v2 v2.0.0 // indirect github.com/jcmturner/dnsutils/v2 v2.0.0 // indirect @@ -278,8 +286,10 @@ require ( github.com/mdlayher/netlink v1.4.2 // indirect github.com/mdlayher/socket v0.0.0-20211102153432-57e3fa563ecb // indirect github.com/minio/highwayhash v1.0.1 // indirect + github.com/mitchellh/copystructure v1.2.0 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/mitchellh/mapstructure v1.4.1 // indirect + github.com/mitchellh/reflectwalk v1.0.2 // indirect github.com/moby/sys/mount v0.2.0 // indirect github.com/moby/sys/mountinfo v0.4.1 // indirect github.com/moby/term v0.0.0-20201216013528-df9cb8a40635 // indirect diff --git a/go.sum b/go.sum index 507623799c561..e25151a3969be 100644 --- a/go.sum +++ b/go.sum @@ -161,6 +161,7 @@ github.com/ClickHouse/clickhouse-go v1.5.1/go.mod h1:EaI/sW7Azgz9UATzd5ZdZHRUhHg github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= github.com/DATA-DOG/go-sqlmock v1.4.1/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= +github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= github.com/DataDog/sketches-go v0.0.0-20190923095040-43f19ad77ff7/go.mod h1:Q5DbzQ+3AkgGwymQO7aZFNP7ns2lZKGtvRBzRXfdi60= 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= @@ -168,10 +169,20 @@ github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24/go.mod h1:4UJr5H github.com/HdrHistogram/hdrhistogram-go v0.9.0/go.mod h1:nxrse8/Tzg2tg3DZcZjm6qEclQKK70g0KxO61gFFZD4= github.com/HdrHistogram/hdrhistogram-go v1.0.1/go.mod h1:BWJ+nMSHY3L41Zj7CA3uXnloDp7xxV0YvstAE7nKTaM= github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= +github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= +github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= +github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= +github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= +github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= +github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= +github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc= github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc= github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= +github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= +github.com/Masterminds/sprig v2.22.0+incompatible h1:z4yfnGrZ7netVz+0EDJ0Wi+5VZCSYp4Z0m2dk6cEM60= +github.com/Masterminds/sprig v2.22.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o= github.com/Mellanox/rdmamap v0.0.0-20191106181932-7c3c4763a6ee h1:atI/FFjXh6hIVlPE1Jup9m8N4B9q/OSbMUe2EBahs+w= github.com/Mellanox/rdmamap v0.0.0-20191106181932-7c3c4763a6ee/go.mod h1:jDA6v0TUYrFEIAE5uGJ29LQOeONIgMdP4Rkqb8HUnPM= github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA= @@ -210,6 +221,8 @@ github.com/Microsoft/hcsshim/test v0.0.0-20210227013316-43a75bb4edd3/go.mod h1:m github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= +github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= +github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/OpenPeeDeeP/depguard v1.0.1/go.mod h1:xsIw86fROiiwelg+jB2uM9PiKihMMmUx/1V+TNhjQvM= github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= @@ -227,10 +240,14 @@ github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d/go.mod h1:3eOhrU github.com/StackExchange/wmi v0.0.0-20210224194228-fe8f1750fd46/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= github.com/StackExchange/wmi v1.2.1/go.mod h1:rcmrprowKIVzvc+NUiLncP2uuArMWLCbu9SBzvHz7e8= github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= +github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= github.com/a8m/tree v0.0.0-20210115125333-10a5fd5b637d/go.mod h1:FSdwKX97koS5efgm8WevNf7XS3PqtyFkKDDXrz778cg= github.com/aerospike/aerospike-client-go v1.27.0 h1:VC6/Wqqm3Qlp4/utM7Zts3cv4A2HPn8rVFp/XZKTWgE= +github.com/aerospike/aerospike-client-go v1.27.0 h1:VC6/Wqqm3Qlp4/utM7Zts3cv4A2HPn8rVFp/XZKTWgE= +github.com/aerospike/aerospike-client-go v1.27.0/go.mod h1:zj8LBEnWBDOVEIJt8LvaRvDG5ARAoa5dBeHaB472NRc= github.com/aerospike/aerospike-client-go v1.27.0/go.mod h1:zj8LBEnWBDOVEIJt8LvaRvDG5ARAoa5dBeHaB472NRc= github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c= +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/go-thrift v0.0.0-20170109061633-7914173639b2/go.mod h1:CxCgO+NdpMdi9SsTlGbc0W+/UNxO3I0AabOEJZ3w61w= @@ -285,15 +302,19 @@ github.com/aristanetworks/glog v0.0.0-20191112221043-67e8567f59f3/go.mod h1:KASm github.com/aristanetworks/goarista v0.0.0-20190325233358-a123909ec740 h1:FD4/ikKOFxwP8muWDypbmBWc634+YcAs3eBrYAmRdZY= github.com/aristanetworks/goarista v0.0.0-20190325233358-a123909ec740/go.mod h1:D/tb0zPVXnP7fmsLZjtdUhSsumbK/ij54UXjjVgMGxQ= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= +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.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= github.com/armon/go-metrics v0.3.3 h1:a9F4rlj7EWWrbj7BYw8J8+x+ZZkJeqzNyRk8hdPF+ro= 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 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/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A= +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= @@ -303,14 +324,17 @@ github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d/go.mod h1:W github.com/ashanbrown/forbidigo v1.1.0/go.mod h1:vVW7PEdqEFqapJe95xHkTfB1+XvZXBFg8t0sG2FIxmI= github.com/ashanbrown/makezero v0.0.0-20201205152432-7b7cdbb3025a/go.mod h1:oG9Dnez7/ESBqc4EdrdNlryeo7d0KcW1ftXHm7nU/UU= github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU= +github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU= github.com/aws/aws-sdk-go v1.15.11/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZoCYDt7FT0= github.com/aws/aws-sdk-go v1.19.48/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go v1.20.6/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go v1.29.11/go.mod h1:1KvfttTE3SPKMpo8g2c6jL3ZKfXtFvKscTgahTma5Xg= github.com/aws/aws-sdk-go v1.34.28/go.mod h1:H7NKnBqNVzoTJpGfLrQkkD+ytBA93eiDYi/+8rV9s48= github.com/aws/aws-sdk-go v1.38.3/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro= github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g= +github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g= github.com/aws/aws-sdk-go-v2 v1.1.0/go.mod h1:smfAbmpW+tcRVuNUjo3MOArSZmW72t62rkCzc2i0TWM= github.com/aws/aws-sdk-go-v2 v1.8.0/go.mod h1:xEFuWz+3TYdlPRuo+CqATbeDWIWyaT5uAPwPaWtgse0= github.com/aws/aws-sdk-go-v2 v1.8.0/go.mod h1:xEFuWz+3TYdlPRuo+CqATbeDWIWyaT5uAPwPaWtgse0= @@ -476,6 +500,7 @@ github.com/c-bata/go-prompt v0.2.2/go.mod h1:VzqtzE2ksDBcdln8G7mk2RX9QyGjH+OVqOC github.com/caio/go-tdigest v3.1.0+incompatible h1:uoVMJ3Q5lXmVLCCqaMGHLBWnbGoN6Lpu7OAUPR60cds= github.com/caio/go-tdigest v3.1.0+incompatible/go.mod h1:sHQM/ubZStBUmF1WbB8FAm8q9GjDajLC5T7ydxE3JHI= github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= +github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4= github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4= github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= @@ -507,6 +532,7 @@ github.com/cilium/ebpf v0.7.0/go.mod h1:/oI2+1shJiTGAMgl6/RgJr36Eo1jzrRcAWbcXO2u 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/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/cloudflare/golz4 v0.0.0-20150217214814-ef862a3cdc58 h1:F1EaeKL/ta07PY/k9Os/UFtwERei2/XzGemhpGnBKNg= github.com/cloudflare/golz4 v0.0.0-20150217214814-ef862a3cdc58/go.mod h1:EOBUe0h4xcZ5GoxqC5SDxFQ8gwyZPKQoEzownBlhI80= @@ -521,6 +547,8 @@ github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWH github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I= github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= +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/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= github.com/containerd/aufs v0.0.0-20200908144142-dab0cbea06f4/go.mod h1:nukgQABAEopAHvB6j7cnP5zJ+/3aVcE7hCYqvIwAHyE= github.com/containerd/aufs v0.0.0-20201003224125-76a6863f2989/go.mod h1:AkGGQs9NM2vtYHaUen+NljV0/baGCAPELGm2q9ZXpWU= @@ -551,6 +579,8 @@ github.com/containerd/containerd v1.3.1-0.20191213020239-082f7e3aed57/go.mod h1: github.com/containerd/containerd v1.3.2/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= github.com/containerd/containerd v1.4.0-beta.2.0.20200729163537-40b22ef07410/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= github.com/containerd/containerd v1.4.1/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= +github.com/containerd/containerd v1.4.1/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= +github.com/containerd/containerd v1.4.1/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= github.com/containerd/containerd v1.4.3/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= github.com/containerd/containerd v1.4.9/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= github.com/containerd/containerd v1.5.0-beta.1/go.mod h1:5HfvG1V2FsKesEGQ17k5/T7V960Tmcumvqn8Mc+pCYQ= @@ -615,6 +645,8 @@ github.com/containernetworking/plugins v0.9.1/go.mod h1:xP/idU2ldlzN6m4p5LmGiwRD github.com/containers/ocicrypt v1.0.1/go.mod h1:MeJDzk1RJHv89LjsH0Sp5KTY3ZYkjXO/C+bKAeWFIrc= github.com/containers/ocicrypt v1.1.0/go.mod h1:b8AOe0YR67uU8OqfVNcznfFpAzu3rdgUV4GP9qXPfu4= github.com/containers/ocicrypt v1.1.1/go.mod h1:Dm55fwWm1YZAjYRaJ94z2mfZikIyIN4B0oB3dj3jFxY= +github.com/coocood/freecache v1.2.0 h1:p8RhjN6Y4DRBIMzdRlm1y+M7h7YJxye3lGW8/VvzCz0= +github.com/coocood/freecache v1.2.0/go.mod h1:OKrEjkGVoxZhyWAJoeFi5BMLUJm2Tit0kpGkIr7NGYY= 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= @@ -624,16 +656,22 @@ github.com/coreos/go-iptables v0.5.0/go.mod h1:/mVI274lEDI2ns62jHCDnCyBF9Iwsmeka github.com/coreos/go-oidc v2.1.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc= 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.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-semver v0.3.0 h1:wkHLiw0WNATZnSG7epLsujiMCgPAc9xhjJ4tgnAxmfM= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd v0.0.0-20161114122254-48702e0da86b/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +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/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f h1:JOrtw2xFKzlg+cbHpyrpLDmnN1HqhBfnX7WDiW7eG2c= github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd/v22 v22.0.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk= github.com/coreos/go-systemd/v22 v22.1.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk= github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= @@ -727,6 +765,7 @@ github.com/dropbox/godropbox v0.0.0-20180512210157-31879d3884b9/go.mod h1:glr97h github.com/dstrand1/riemann-go-client v0.5.1-0.20211028194734-b5eb11fb5754 h1:aDtw0/++yjOoiXB9sldaFYW61mK3m6ia/wYWxPLrwYY= github.com/dstrand1/riemann-go-client v0.5.1-0.20211028194734-b5eb11fb5754/go.mod h1:4rS0vfmzOMwfFPhi6Zve4k/59TsBepqd6WESNULE0ho= github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= @@ -747,6 +786,7 @@ github.com/eclipse/paho.mqtt.golang v1.2.0/go.mod h1:H9keYFcgq3Qr5OUJm/JZI/i6U7j github.com/eclipse/paho.mqtt.golang v1.3.5 h1:sWtmgNxYM9P2sP+xEItMozsR3w0cqZFlqnNN1bdl41Y= github.com/eclipse/paho.mqtt.golang v1.3.5/go.mod h1:eTzb4gxwwyWpqBUHGQZ4ABAV7+Jgm1PklsYT/eo8Hcc= github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= +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/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= @@ -768,6 +808,7 @@ github.com/facebookgo/stack v0.0.0-20160209184415-751773369052/go.mod h1:UbMTZqL github.com/facebookgo/stackerr v0.0.0-20150612192056-c2fcf88613f4 h1:fP04zlkPjAGpsduG7xN3rRkxjAqkJaIQnnkNYYw/pAk= github.com/facebookgo/stackerr v0.0.0-20150612192056-c2fcf88613f4/go.mod h1:SBHk9aNQtiw4R4bEuzHjVmZikkUKCnO1v3lPQ21HZGk= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +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/fatih/color v1.10.0 h1:s36xzo75JdqLaaWoiEHk767eHiwo0598uUxyfiPkDsg= github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= @@ -782,6 +823,7 @@ github.com/form3tech-oss/jwt-go v3.2.5+incompatible/go.mod h1:pbq4aXjuKjdthFRnoD github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= 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/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.11.3 h1:8sXhOn0uLys67V8EsXLc6eszDs8VXWxL3iRvebPhedY= @@ -830,6 +872,7 @@ github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V github.com/go-logfmt/logfmt v0.5.0 h1:TrB8swr/68K7m9CcGut2g3UOihhbcbiMAYiuTXdEih4= 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.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-logr/logr v0.4.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= @@ -1001,12 +1044,17 @@ github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRx github.com/gofrs/uuid v4.2.0+incompatible h1:yyYWMnhkhrKwwr8gAOcOCYxOOscHgDS9yZgBrnJfGa0= github.com/gofrs/uuid v4.2.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.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= github.com/gogo/googleapis v1.2.0/go.mod h1:Njal3psf3qN6dwBtQfUmBZh2ybovJ0tlu3o/AC7HYjU= github.com/gogo/googleapis v1.4.0/go.mod h1:5YRNX2z1oM5gXdAkurHa942MDgEJyk02w4OecKY87+c= github.com/gogo/googleapis v1.4.1/go.mod h1:2lpHqI5OcWCtVElxXnPt+s8oJvMpySlOyM6xDCrzib4= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +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.0/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.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= github.com/gogo/protobuf v1.3.0/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= @@ -1129,6 +1177,9 @@ github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLe github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +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.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= @@ -1150,6 +1201,8 @@ github.com/gopcua/opcua v0.3.1/go.mod h1:rdqS1oF5s/+Ko4SnhZA+3tgK4MQuXDzH3KgnnLD github.com/gophercloud/gophercloud v0.16.0 h1:sWjPfypuzxRxjVbk3/MsU4H8jS0NNlyauZtIUl78BPU= github.com/gophercloud/gophercloud v0.16.0/go.mod h1:wRtmUelyIIv3CSSDI47aUwbs075O6i+LY+pXsKCBsb4= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gordonklaus/ineffassign v0.0.0-20200309095847-7953dde2c7bf/go.mod h1:cuNKsD1zp2v6XfE/orVX2QE1LC+i254ceGcVeDT3pTU= github.com/gordonklaus/ineffassign v0.0.0-20210225214923-2e10b2664254/go.mod h1:M9mZEtGIsR1oDaZagNPNG9iq9n2HrhZ17dsXk73V3Lw= @@ -1165,6 +1218,7 @@ github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB7 github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +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 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= @@ -1186,11 +1240,14 @@ github.com/grid-x/serial v0.0.0-20211107191517-583c7356b3aa h1:Rsn6ARgNkXrsXJIzh github.com/grid-x/serial v0.0.0-20211107191517-583c7356b3aa/go.mod h1:kdOd86/VGFWRrtkNwf1MPk0u1gIjc4Y7R2j7nhwc7Rk= 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.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-middleware v1.2.2/go.mod h1:EaizFBKfUKtMIF5iaDEhniwNedqGo9FuLFzppDr3uwI= github.com/grpc-ecosystem/go-grpc-middleware v1.3.0/go.mod h1:z0ButlSOZa5vEBq9m2m2hlwIgKw+rp3sdCBRoJY+30Y= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= +github.com/grpc-ecosystem/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.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.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= @@ -1203,11 +1260,13 @@ github.com/harlow/kinesis-consumer v0.3.6-0.20210911031324-5a873d6e9fec h1:ya+kv github.com/harlow/kinesis-consumer v0.3.6-0.20210911031324-5a873d6e9fec/go.mod h1:FIT1uhdVv2iXO0l6aACPZSVHxdth7RdmoT34jk9MEm0= 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.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE= github.com/hashicorp/consul/api v1.8.1/go.mod h1:sDjTOq0yUyv5G4h+BqSea7Fn6BU+XbolEz1952UB+mk= github.com/hashicorp/consul/api v1.12.0 h1:k3y1FYv6nuKyNTqj6w9gXOx5r5CfLj/k/euUeBXj1OY= github.com/hashicorp/consul/api v1.12.0/go.mod h1:6pVBMo0ebnYdt2S3H87XhekM/HHrUoTD2XXb/VrZVy0= 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.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= github.com/hashicorp/consul/sdk v0.7.0/go.mod h1:fY08Y9z5SvJqevyZNy6WWPXiG3KwBPAvlcdx16zZ0fM= github.com/hashicorp/consul/sdk v0.8.0 h1:OJtKBtEjboEZvG6AOUdh4Z1Zbyu0WcxQ0qatRrZHTVU= github.com/hashicorp/consul/sdk v0.8.0/go.mod h1:GBvyrGALthsZObzUGsfgHZQDXjg4lOjagTIwIR1vPms= @@ -1236,10 +1295,13 @@ github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+ github.com/hashicorp/go-plugin v1.4.0/go.mod h1:5fGEH17QVwTTcR0zV7yhDPLLmFX9YSZ38b18Udy6vYQ= github.com/hashicorp/go-plugin v1.4.2/go.mod h1:5fGEH17QVwTTcR0zV7yhDPLLmFX9YSZ38b18Udy6vYQ= github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= +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.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc= 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.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= github.com/hashicorp/go-sockaddr v1.0.2 h1:ztczhD1jLxIRjVejw8gFomI1BQZOe2WoVOu0SyteCQc= 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= @@ -1248,6 +1310,7 @@ github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/b github.com/hashicorp/go-uuid v1.0.2 h1:cfejS+Tpcp13yd5nYHWDI6qVCny6wyX2Mt5SGur2IGE= 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-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= @@ -1255,14 +1318,18 @@ github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+l github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= 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/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.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= github.com/hashicorp/mdns v1.0.1/go.mod h1:4gW7WsVCke5TE7EPeYliwHlRUyBtfCwuFwuMg2DmyNY= github.com/hashicorp/mdns v1.0.4/go.mod h1:mtBihi+LeNXGtG8L9dX59gAEa12BDtBQSp4v/YAJqrc= github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= +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/memberlist v0.3.0 h1:8+567mCcFDnS5ADl7lrpxPMWiFCElyUEeW0gtj34fMA= github.com/hashicorp/memberlist v0.3.0/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE= github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= +github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= github.com/hashicorp/serf v0.9.5/go.mod h1:UWDWwZeL5cuWDJdl0C6wrvrUwEqtQ4ZKBKKENpqIUyk= github.com/hashicorp/serf v0.9.6 h1:uuEX1kLR6aoda1TBttmJQKDLZE1Ob7KN0NPdE7EtCDc= github.com/hashicorp/serf v0.9.6/go.mod h1:TXZNMjZQijwlDvp+r0b63xZ45H7JmCmgg4gpTwn9UV4= @@ -1270,15 +1337,22 @@ github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKe github.com/hashicorp/yamux v0.0.0-20190923154419-df201c70410d/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= github.com/hetznercloud/hcloud-go v1.24.0/go.mod h1:3YmyK8yaZZ48syie6xpm3dt26rtB6s65AisBHylXYFA= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/huandu/xstrings v1.3.2 h1:L18LIDzqlW6xN2rEkpdV8+oL/IXWJ1APd+vsdYy4Wdw= +github.com/huandu/xstrings v1.3.2/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= +github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg= 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/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/imdario/mergo v0.3.10/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= +github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= +github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU= github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/influxdata/apcupsd v0.0.0-20210427145308-694d5caead0e h1:3J1OB4RDKwXs5l8uEV6BP/tucOJOPDQysiT7/9cuXzA= github.com/influxdata/apcupsd v0.0.0-20210427145308-694d5caead0e/go.mod h1:WYK/Z/aXq9cbMFIL5ihcA4sX/r/3/WCas/Qvs/2fXcA= github.com/influxdata/flux v0.65.1/go.mod h1:J754/zds0vvpfwuq7Gc2wRdVwEodfpCFM7mYlOw2LqY= @@ -1292,6 +1366,7 @@ github.com/influxdata/influxdb-observability/influx2otel v0.2.10/go.mod h1:y/9uu github.com/influxdata/influxdb-observability/otel2influx v0.2.10 h1:sNZCYUExwCWsNHWpNlu2gZZZav6H0rjK/DcaXEVN29E= github.com/influxdata/influxdb-observability/otel2influx v0.2.10/go.mod h1:UOa19v6sU7EpL1dPK79Yt+mZ+1/iOwvMqcFu9yVXenw= github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= +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= @@ -1313,7 +1388,10 @@ github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgO github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8= github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA= +github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA= github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE= +github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE= +github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s= github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s= github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o= github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY= @@ -1335,26 +1413,41 @@ github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= +github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= github.com/jackc/pgproto3/v2 v2.2.0 h1:r7JypeP2D3onoQTCxWdTpCtJ4D+qpKr0TxvoyMhZ5ns= github.com/jackc/pgproto3/v2 v2.2.0/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b h1:C8S2+VttkHFdOOCXJe+YGfa4vHYwlt4Zx+IVXQ97jYg= +github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b h1:C8S2+VttkHFdOOCXJe+YGfa4vHYwlt4Zx+IVXQ97jYg= +github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E= github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E= github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg= +github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg= github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc= +github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc= +github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw= github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw= github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM= github.com/jackc/pgtype v1.9.1 h1:MJc2s0MFS8C3ok1wQTdQxWuXQcB6+HwAm5x1CzW7mf0= github.com/jackc/pgtype v1.9.1/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4= github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y= +github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y= github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM= +github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM= +github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc= github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc= github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs= github.com/jackc/pgx/v4 v4.14.1 h1:71oo1KAGI6mXhLiTMn6iDFcp3e7+zon/capWjl2OEFU= github.com/jackc/pgx/v4 v4.14.1/go.mod h1:RgDuE4Z34o7XE92RpLsvFiOEfrAUT0Xt2KxvX73W06M= github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jackc/puddle v1.2.0 h1:DNDKdn/pDrWvDWyT2FYvpZVE81OAhWrjCv19I9n108Q= github.com/jackc/puddle v1.2.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jaegertracing/jaeger v1.22.0/go.mod h1:WnwW68MjJEViSLRQhe0nkIsBDaF3CzfFd8wJcpJv24k= github.com/jaegertracing/jaeger v1.23.0/go.mod h1:gB6Qc+Kjd/IX1G82oGTArbHI3ZRO//iUkaMW+gzL9uw= @@ -1368,6 +1461,8 @@ github.com/jcmturner/dnsutils/v2 v2.0.0 h1:lltnkeZGL0wILNvrNiVCR6Ro5PGU/SeBvVO/8 github.com/jcmturner/dnsutils/v2 v2.0.0/go.mod h1:b0TnjGOvI/n42bZa+hmXL+kFJZsFT7G4t3HTlQ184QM= github.com/jcmturner/gofork v0.0.0-20190328161633-dc7c13fece03/go.mod h1:MK8+TM0La+2rjBD4jE12Kj1pCCxK7d2LK/UM3ncEo0o= github.com/jcmturner/gofork v1.0.0 h1:J7uCkflzTEhUZ64xqKnkDxq3kzc96ajM1Gli5ktUem8= +github.com/jcmturner/gofork v1.0.0 h1:J7uCkflzTEhUZ64xqKnkDxq3kzc96ajM1Gli5ktUem8= +github.com/jcmturner/gofork v1.0.0/go.mod h1:MK8+TM0La+2rjBD4jE12Kj1pCCxK7d2LK/UM3ncEo0o= github.com/jcmturner/gofork v1.0.0/go.mod h1:MK8+TM0La+2rjBD4jE12Kj1pCCxK7d2LK/UM3ncEo0o= github.com/jcmturner/goidentity/v6 v6.0.1 h1:VKnZd2oEIMorCTsFBnJWbExfNN7yZr3EhJAxwOkZg6o= github.com/jcmturner/goidentity/v6 v6.0.1/go.mod h1:X1YW3bgtvwAXju7V3LCIMpY0Gbxyjn/mY9zx4tFonSg= @@ -1386,6 +1481,7 @@ github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht github.com/jmespath/go-jmespath v0.0.0-20160803190731-bd40a432e4c7/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= +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 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= @@ -1394,6 +1490,7 @@ github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhB github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc= 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/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/josharian/native v0.0.0-20200817173448-b6b71def0850 h1:uhL5Gw7BINiiPAo24A2sxkcDI0Jt/sqp1v5xQCniEFA= @@ -1424,12 +1521,15 @@ github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1 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 h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= +github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/juju/errors v0.0.0-20181012004132-a4583d0a56ea h1:g2k+8WR7cHch4g0tBDhfiEvAp7fXxTNBiD1oC1Oxj3E= github.com/juju/errors v0.0.0-20181012004132-a4583d0a56ea/go.mod h1:W54LbzXuIE0boCoNJfwqpmkKJ1O4TCTZMetAt6jGk7Q= github.com/juju/loggo v0.0.0-20190526231331-6e530bcce5d8/go.mod h1:vgyd7OREkbtVEN/8IXZe5Ooef3LQePvuBm9UWj6ZL8U= github.com/juju/testing v0.0.0-20191001232224-ce9dec17d28b/go.mod h1:63prj8cnj0tU0S9OHjGJn+b1h0ZghCndfnbQolrYTwA= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +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/julz/importas v0.0.0-20210226073942-60b4fa260dd0/go.mod h1:oSFU2R4XK/P7kNBrnL/FEQlDGN1/6WoxXEjSSXO0DV0= github.com/jung-kurt/gofpdf v1.0.0/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= @@ -1466,6 +1566,7 @@ github.com/klauspost/crc32 v0.0.0-20161016154125-cb6bfca970f6/go.mod h1:+ZoRqAPR 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.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/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= @@ -1499,6 +1600,8 @@ github.com/lib/pq v1.9.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lib/pq v1.10.2 h1:AqzbZs4ZoCBp+GtejcpCpcxM3zlSMx29dXbUSeVtJb8= github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM= +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/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= github.com/logrusorgru/aurora v0.0.0-20181002194514-a7b3b318ed4e/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4= github.com/lucasb-eyer/go-colorful v1.0.2/go.mod h1:0MS4r+7BZKSJ5mw4/S5MPN+qHFF1fYclkSPilDOKW0s= @@ -1506,6 +1609,7 @@ github.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4= github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= +github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= github.com/magefile/mage v1.10.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= @@ -1529,6 +1633,7 @@ github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcncea github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= 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.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8= github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-ieproxy v0.0.1 h1:qiyop7gCflfhwCzGyeT0gro3sF9AIg9HU98JORTkqfI= @@ -1538,12 +1643,14 @@ github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNx github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= 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 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= 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.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.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= @@ -1599,7 +1706,10 @@ github.com/minio/highwayhash v1.0.1 h1:dZ6IIu8Z14VlC0VpfKofAhCy74wu/Qb5gcn52yWoz github.com/minio/highwayhash v1.0.1/go.mod h1:BQskDq+xkJ12lmlUUi7U0M5Swg3EWR+dLTk+kldvVxY= github.com/mistifyio/go-zfs v2.1.2-0.20190413222219-f784269be439+incompatible/go.mod h1:8AuVvqP/mXw1px98n46wfvcGfQ4ci2FwoAjKYxuo3Z4= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= +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/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= +github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= @@ -1609,8 +1719,11 @@ github.com/mitchellh/go-testing-interface v1.0.0 h1:fzU/JVNcaqHQEcVFAKeR41fkiLdI 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/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= +github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= 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 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/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= @@ -1619,6 +1732,8 @@ github.com/mitchellh/mapstructure v1.4.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RR github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag= github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/osext v0.0.0-20151018003038-5e2d6d41470f/go.mod h1:OkQIRizQZAeMln+1tSwduZz7+Af5oFlKirV/MSYes2A= +github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= +github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/mjibson/esc v0.2.0/go.mod h1:9Hw9gxxfHulMF5OJKCyhYD7PzlSdhzXyaGEBRPH1OPs= github.com/moby/ipvs v1.0.1 h1:aoZ7fhLTXgDbzVrAnvV+XbKOU8kOET7B3+xULDF/1o0= github.com/moby/ipvs v1.0.1/go.mod h1:2pngiyseZbIKXNv7hsKj3O9UEz30c53MT9005gt2hxQ= @@ -1696,6 +1811,8 @@ github.com/nsqio/go-nsq v1.1.0/go.mod h1:vKq36oyeVXgsS5Q8YEO7WghqidAVXQlcFxzQbQT github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78= 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/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.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= @@ -1778,16 +1895,24 @@ github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go 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/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.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.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/opentracing/opentracing-go v1.1.1-0.20190913142402-a7454ce5950e/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs= github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5 h1:ZCnq+JUrvXcDVhX/xRolRBZifmabN1HcS1wrPSvxhrU= github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5/go.mod h1:/wsWhb9smxSfWAKL3wpBW7V8scJMt8N8gnaMCS9E/cA= +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.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.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.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= github.com/openzipkin/zipkin-go v0.2.5 h1:UwtQQx2pyPIgWYHRg+epgdx1/HnBQTgN3/oIYEJTQzU= github.com/openzipkin/zipkin-go v0.2.5/go.mod h1:KpXfKdgRDnnhsxw4pNIH9Md5lyFqKUa4YDFlwRYAMyE= @@ -1800,6 +1925,7 @@ github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144T github.com/paulbellamy/ratecounter v0.2.0/go.mod h1:Hfx1hDpSGoqxkVVpBi/IlYD7kChlfo5C6hzIHwPqfFE= github.com/pavius/impi v0.0.3/go.mod h1:x/hU0bfdWIhuOT1SKwiJg++yvkk6EuOtJk8WtDZqgr8= github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= +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= @@ -1808,6 +1934,7 @@ github.com/pelletier/go-toml v1.8.0/go.mod h1:D6yutnOGMveHEPV7VQOuvI/gXY61bv+9bA github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc= github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac= +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/phayes/checkstyle v0.0.0-20170904204023-bfd46e6a821d/go.mod h1:3OzsM7FXDQlpCiw2j81fOmAwQLnZnLGXVKUzeKQXIAw= @@ -1845,13 +1972,17 @@ github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 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/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA= github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= 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 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/polyfloyd/go-errorlint v0.0.0-20201127212506-19bd8db6546f/go.mod h1:wi9BfjxjF/bwiZ701TzmfKu6UKC357IOAtNr0Td0Lvw= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= +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/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw= github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= @@ -1891,6 +2022,7 @@ github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y8 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.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.15.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16Clt/msog/s= @@ -1943,11 +2075,14 @@ github.com/robertkrimen/otto v0.0.0-20191219234010-c382bd3c16ff/go.mod h1:xvqspo github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= +github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/fastuuid v1.1.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= +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/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.6.2 h1:aIihoIOHCiLZHxyoNQ+ABL4NKhFTgKLBdMLyEAh98m0= github.com/rogpeppe/go-internal v1.6.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= @@ -1958,15 +2093,20 @@ github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OK github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc= 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/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ruudk/golang-pdf417 v0.0.0-20181029194003-1af4ab5afa58/go.mod h1:6lfFZQK844Gfx8o5WFuvpxWRwnSoipWe/p622j1v06w= github.com/ryancurrah/gomodguard v1.2.0/go.mod h1:rNqbC4TOIdUDcVMSIpNNAzTbzXAZa6W5lnUepvuMMgQ= github.com/ryanrolds/sqlclosecheck v0.3.0/go.mod h1:1gREqxyTGR3lVtpngyFo3hZAgk0KCtEdgEkHwDbigdA= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +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/safchain/ethtool v0.0.0-20190326074333-42ed695e3de8/go.mod h1:Z0q5wiBQGYcxhMZ6gUqHn6pYNLypFAvaL3UvgZLR0U4= github.com/safchain/ethtool v0.0.0-20200218184317-f459e2d13664 h1:gvolwzuDhul9qK6/oHqxCHD5TEYfsWNBGidOeG6kvpk= +github.com/safchain/ethtool v0.0.0-20200218184317-f459e2d13664 h1:gvolwzuDhul9qK6/oHqxCHD5TEYfsWNBGidOeG6kvpk= +github.com/safchain/ethtool v0.0.0-20200218184317-f459e2d13664/go.mod h1:Z0q5wiBQGYcxhMZ6gUqHn6pYNLypFAvaL3UvgZLR0U4= github.com/safchain/ethtool v0.0.0-20200218184317-f459e2d13664/go.mod h1:Z0q5wiBQGYcxhMZ6gUqHn6pYNLypFAvaL3UvgZLR0U4= github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E= +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 h1:CGjiMQ0wMH4wtNWrlj6kiTbkPt2F3rbYnhGX6TWLfco= 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= @@ -1975,6 +2115,8 @@ github.com/sbezverk/nx-telemetry-proto v0.0.0-20210629125746-3c19a51b1abc h1:9RA github.com/sbezverk/nx-telemetry-proto v0.0.0-20210629125746-3c19a51b1abc/go.mod h1:rJDd05J5hqWVU9MjJ+5jw1CuLn/jRhvU0xtFEzzqjwM= github.com/scaleway/scaleway-sdk-go v1.0.0-beta.7.0.20210223165440-c65ae3540d44/go.mod h1:CJJ5VAbozOl0yEw7nHB9+7BXTJbIn6h7W+f6Gau5IP8= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUtVbo7ada43DJhG55ua/hjS5I= +github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUtVbo7ada43DJhG55ua/hjS5I= +github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/seccomp/libseccomp-golang v0.9.1/go.mod h1:GbW5+tmTXfcxTToHLXlScSlAvWlF4P2Ca7zGrPiEpWo= github.com/securego/gosec v0.0.0-20200203094520-d13bb6d2420c/go.mod h1:gp0gaHj0WlmPh9BdsTmo1aq6C27yIPWdxCKGFGdVKBE= @@ -2000,6 +2142,7 @@ github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxr github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ= 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/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/signalfx/com_signalfx_metrics_protobuf v0.0.2 h1:X886QgwZH5qr9HIQkk3mWcNEhUxx6D8rUZumzLV4Wiw= @@ -2015,8 +2158,10 @@ github.com/sirupsen/logrus v1.0.4-0.20170822132746-89742aefa4b2/go.mod h1:pMByvH github.com/sirupsen/logrus v1.0.6/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +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.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/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= @@ -2028,23 +2173,31 @@ github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic github.com/sleepinggenius2/gosmi v0.4.3 h1:99Zwzy1Cvgsh396sw07oR2G4ab88ILGZFMxSlGWnR6o= github.com/sleepinggenius2/gosmi v0.4.3/go.mod h1:l8OniPmd3bJzw0MXP2/qh7AhP/e+bTY2CNivIhsnDT0= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/assertions v1.0.0/go.mod h1:kHHU4qYBaI3q23Pp3VPrmWhuIUrLW/7eUrw0BU5VaoM= github.com/smartystreets/assertions v1.0.1 h1:voD4ITNjPL5jjBfgR/r8fPIIBrliWrWHeiJApdr3r4w= github.com/smartystreets/assertions v1.0.1/go.mod h1:kHHU4qYBaI3q23Pp3VPrmWhuIUrLW/7eUrw0BU5VaoM= github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9/go.mod h1:SnhjPscd9TpLiy1LpzGSKh3bXCfxxXuqd9xmQJy3slM= github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= +github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= +github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/smartystreets/gunit v1.0.0/go.mod h1:qwPWnhz6pn0NnRBP++URONOVyNkPyr4SauJk4cUOwJs= github.com/smartystreets/gunit v1.1.3/go.mod h1:EH5qMBab2UclzXUcpR8b93eHsIlp9u+pDQIRp5DZNzQ= github.com/snowflakedb/gosnowflake v1.6.2 h1:drZkX7Ve3qr3lLD/f0vxwesgJZfNerivknAvPRAMy88= github.com/snowflakedb/gosnowflake v1.6.2/go.mod h1:k1Wq+O8dRD/jmFBLyStEv2OrgHoMFQpqHCRSy70P0dI= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= +github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= +github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= github.com/soheilhy/cmux v0.1.5/go.mod h1:T7TcVDs9LWfQgPlPsdngu6I6QIoyIFZDDC6sNE1GqG0= github.com/sonatard/noctx v0.0.1/go.mod h1:9D2D/EoULe8Yy2joDHJj7bv3sZoq9AaSb8B4lqBjiZI= github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY= +github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY= +github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY= github.com/sourcegraph/go-diff v0.6.1/go.mod h1:iBszgVvyxdc8SFZ7gm69go2KDdt3ag071iBaWPF6cjs= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= 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/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= @@ -2053,6 +2206,7 @@ github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkU github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cobra v0.0.2-0.20171109065643-2da4a54c5cee/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= +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 v0.0.7/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= @@ -2061,11 +2215,16 @@ github.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t6 github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= 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 v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.1-0.20171106142849-4c012f6dcd95/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.1/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.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 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 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.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= @@ -2077,8 +2236,11 @@ github.com/ssgreg/nlreturn/v2 v2.1.0/go.mod h1:E/iiPB78hV7Szg2YfRgyIrk1AD6JVMTRk github.com/stefanberger/go-pkcs11uri v0.0.0-20201008174630-78d3cae3a980/go.mod h1:AO3tvPzVZ/ayst6UlUKUv6rcPQInYe3IknH3jYhAKu8= github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= +github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= +github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271 h1:WhxRHzgeVGETMlmVfqhRn8RIeeNoPr2Czh33I4Zdccw= github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271 h1:WhxRHzgeVGETMlmVfqhRn8RIeeNoPr2Czh33I4Zdccw= github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/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.0.0-20180129172003-8a3f7159479f/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -2133,10 +2295,12 @@ github.com/tklauser/numcpus v0.2.2/go.mod h1:x3qojaO3uyYt0i56EW/VUYs7uBvdl2fkfZF github.com/tklauser/numcpus v0.3.0 h1:ILuRUQBtssgnxw0XXIjKUC56fgnOrFoQQ/4+DeU2biQ= github.com/tklauser/numcpus v0.3.0/go.mod h1:yFGUr7TUHQRAhyqBcEg0Ge34zDBAsIvJJcyE6boqnA8= 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-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/tomarrell/wrapcheck v0.0.0-20201130113247-1683564d9756/go.mod h1:yiFB6fFoV7saXirUGfuK+cPtUh4NX/Hf5y2WC2lehu0= github.com/tommy-muehle/go-mnd/v2 v2.3.1/go.mod h1:WsUAkMJMYww6l/ufffCD3m+P7LEvr8TnZn9lwVDlgzw= github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= +github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= github.com/uber/jaeger-client-go v2.25.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk= github.com/uber/jaeger-client-go v2.29.1+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk= github.com/uber/jaeger-lib v2.4.0+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U= @@ -2149,6 +2313,8 @@ github.com/ultraware/funlen v0.0.3/go.mod h1:Dp4UiAus7Wdb9KUZsYWZEWiRzGuM2kXM1lP github.com/ultraware/whitespace v0.0.4/go.mod h1:aVMh/gQve5Maj9hQ/hg+F75lr/X5A89uZnzAmWSineA= github.com/urfave/cli v0.0.0-20171014202726-7bc6a0acffa5/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= +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/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/uudashr/gocognit v1.0.1/go.mod h1:j44Ayx2KW4+oB6SWMv8KsmHzZrOInQav7D3cQMJ5JUM= @@ -2201,6 +2367,7 @@ github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2 github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= github.com/xeipuuv/gojsonschema v0.0.0-20180618132009-1d523034197f/go.mod h1:5yf86TLmAcydyeJq5YvxkGPE2fm/u4myDekKRoLuqhs= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= +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= @@ -2222,10 +2389,14 @@ github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43/go.mod h1:aX github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50/go.mod h1:NUSPSUX/bi6SeDMUh6brw0nXpxHnc96TguQh0+r/ssA= github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f/go.mod h1:GlGEuHIJweS1mbCqG+7vt2nvWLzLLnRHbXz5JKd/Qbg= github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= +github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= 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/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= +go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg= +go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg= go.etcd.io/etcd v0.5.0-alpha.5.0.20200910180754-dd1b699fc489 h1:1JFLBqwIgdyHN1ZtgjTBwO+blA6gVOmZurpiMEsETKo= go.etcd.io/etcd v0.5.0-alpha.5.0.20200910180754-dd1b699fc489/go.mod h1:yVHk9ub3CSBatqGNg7GRmsnfLWtoW60w4eDYfh7vHDg= go.etcd.io/etcd/api/v3 v3.5.0 h1:GsV3S+OfZEOCNXdtNkBSR7kgLobAa/SO6tCxRa0GAYw= @@ -2286,22 +2457,30 @@ go.opentelemetry.io/proto/otlp v0.12.0/go.mod h1:TsIjwGWIx5VFYv9KGVlOpxoBl5Dy+63 go.starlark.net v0.0.0-20210406145628-7a1108eaa012 h1:4RGobP/iq7S22H0Bb92OEt+M8/cfBQnW+T+a2MC0sQo= go.starlark.net v0.0.0-20210406145628-7a1108eaa012/go.mod h1:t3mmBBPzAVvK0L0n1drDmrQsJ8FoIx4INCqVMTr/Zo0= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +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.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/automaxprocs v1.4.0/go.mod h1:/mTEdr7LvHhs0v7mjdxDreTz1OG5zdZGqgOnhWiR/+Q= 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.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= +go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= +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.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.10.0/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/go.mod h1:MA8QOfq0BHJwdXa996Y4dYkAqRKB8/1K1QMMZVaNZjQ= go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= @@ -2333,16 +2512,19 @@ golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201112155050-0c6587e931a9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20201208171446-5f87f3452ae9/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20201216223049-8b5274cf687f/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= +golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20210314154223-e6e6c4f2bb5b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210314154223-e6e6c4f2bb5b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= @@ -2478,6 +2660,7 @@ golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20210224082022-3d97a244fca7/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= golang.org/x/net v0.0.0-20210324051636-2c4c8ecb7826/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= @@ -2634,6 +2817,7 @@ golang.org/x/sys v0.0.0-20201112073958-5cba982894dd/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20201117170446-d9b008d0a637/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201118182958-a01c418693c7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201126233918-771906719818/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -2704,6 +2888,7 @@ golang.org/x/sys v0.0.0-20220111092808-5a964db01320/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220114195835-da31bd327af9 h1:XfKQ4OlFl8okEOr5UvAqFRVj8pY/4yfcXrddB8qAbU0= golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= +golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b h1:9zKuko04nR4gjZ4+DNjHqRlAJqbJETHwiNKDqTfOjfE= @@ -2716,9 +2901,11 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +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= @@ -2761,6 +2948,7 @@ golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgw golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/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-20190910044552-dd2b5c81c578/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= @@ -3066,6 +3254,7 @@ gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= +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/fatih/pool.v2 v2.0.0 h1:xIFeWtxifuQJGk/IEPKsTduEKcKvPmhoiVDGpC40nKg= gopkg.in/fatih/pool.v2 v2.0.0/go.mod h1:8xVGeu1/2jr2wm5V9SPuMht2H5AEmf5aFMGSQixtjTY= @@ -3098,6 +3287,7 @@ gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24 gopkg.in/olivere/elastic.v5 v5.0.86 h1:xFy6qRCGAmo5Wjx96srho9BitLhZl2fcnpuidPwduXM= gopkg.in/olivere/elastic.v5 v5.0.86/go.mod h1:M3WNlsF+WhYn7api4D87NIflwTV/c0iVs8cqfWhK+68= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= +gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/sourcemap.v1 v1.0.5 h1:inv58fC9f9J3TK2Y2R1NPntXEn3/wjWHkonhIUODNTI= gopkg.in/sourcemap.v1 v1.0.5/go.mod h1:2RlvNNSMglmRrcvhfuzp4hQHwOtjxlbjX7UPY/GXb78= gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= @@ -3240,6 +3430,7 @@ sigs.k8s.io/structured-merge-diff/v4 v4.1.0/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK sigs.k8s.io/structured-merge-diff/v4 v4.2.1 h1:bKCqE9GvQ5tiVHn5rfn1r+yao3aLQEaLzkkmAkf+A6Y= sigs.k8s.io/structured-merge-diff/v4 v4.2.1/go.mod h1:j/nl6xW8vLS49O8YvXW1ocPhZawJtm+Yrr7PPRQ0Vg4= sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= +sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q= 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/plugins/outputs/all/all.go b/plugins/outputs/all/all.go index d6cad6524340f..53dfd2e013c56 100644 --- a/plugins/outputs/all/all.go +++ b/plugins/outputs/all/all.go @@ -40,6 +40,7 @@ import ( _ "github.com/influxdata/telegraf/plugins/outputs/nsq" _ "github.com/influxdata/telegraf/plugins/outputs/opentelemetry" _ "github.com/influxdata/telegraf/plugins/outputs/opentsdb" + _ "github.com/influxdata/telegraf/plugins/outputs/postgresql" _ "github.com/influxdata/telegraf/plugins/outputs/prometheus_client" _ "github.com/influxdata/telegraf/plugins/outputs/riemann" _ "github.com/influxdata/telegraf/plugins/outputs/riemann_legacy" diff --git a/plugins/outputs/postgresql/README.md b/plugins/outputs/postgresql/README.md new file mode 100644 index 0000000000000..4a7fbb672f02e --- /dev/null +++ b/plugins/outputs/postgresql/README.md @@ -0,0 +1,201 @@ +# PostgreSQL Output Plugin + +This output plugin writes metrics to PostgreSQL (or compatible database). +The plugin manages the schema, automatically updating missing columns. + +## Configuration + +```toml +[[outputs.postgresql]] + ## Specify connection address via the standard libpq connection string: + ## host=... user=... password=... sslmode=... dbname=... + ## Or a URL: + ## postgres://[user[:password]]@localhost[/dbname]\ + ## ?sslmode=[disable|verify-ca|verify-full] + ## + ## All connection parameters are optional. Environment vars are also supported. + ## e.g. PGPASSWORD, PGHOST, PGUSER, PGDATABASE + ## All supported vars can be found here: + ## https://www.postgresql.org/docs/current/libpq-envars.html + ## + ## Non-standard parameters: + ## pool_max_conns (default: 1) - Maximum size of connection pool for parallel (per-batch per-table) inserts. + ## pool_min_conns (default: 0) - Minimum size of connection pool. + ## pool_max_conn_lifetime (default: 0s) - Maximum age of a connection before closing. + ## pool_max_conn_idle_time (default: 0s) - Maximum idle time of a connection before closing. + ## pool_health_check_period (default: 0s) - Duration between health checks on idle connections. + # connection = "" + + ## Postgres schema to use. + # schema = "public" + + ## Store tags as foreign keys in the metrics table. Default is false. + # tags_as_foreign_keys = false + + ## Suffix to append to table name (measurement name) for the foreign tag table. + # tag_table_suffix = "_tag" + + ## Deny inserting metrics if the foreign tag can't be inserted. + # foreign_tag_constraint = false + + ## Store all tags as a JSONB object in a single 'tags' column. + # tags_as_jsonb = false + + ## Store all fields as a JSONB object in a single 'fields' column. + # fields_as_jsonb = false + + ## Templated statements to execute when creating a new table. + # create_templates = [ + # '''CREATE TABLE {{.table}} ({{.columns}})''', + # ] + + ## Templated statements to execute when adding columns to a table. + ## Set to an empty list to disable. Points containing tags for which there is no column will be skipped. Points + ## containing fields for which there is no column will have the field omitted. + # add_column_templates = [ + # '''ALTER TABLE {{.table}} ADD COLUMN IF NOT EXISTS {{.columns|join ", ADD COLUMN IF NOT EXISTS "}}''', + # ] + + ## Templated statements to execute when creating a new tag table. + # tag_table_create_templates = [ + # '''CREATE TABLE {{.table}} ({{.columns}}, PRIMARY KEY (tag_id))''', + # ] + + ## Templated statements to execute when adding columns to a tag table. + ## Set to an empty list to disable. Points containing tags for which there is no column will be skipped. + # tag_table_add_column_templates = [ + # '''ALTER TABLE {{.table}} ADD COLUMN IF NOT EXISTS {{.columns|join ", ADD COLUMN IF NOT EXISTS "}}''', + # ] + + ## Controls whether to use the uint8 data type provided by the pguint extension. + # use_uint8 = false + + ## When using pool_max_conns>1, and a temporary error occurs, the query is retried with an incremental backoff. This + ## controls the maximum backoff duration. + # retry_max_backoff = "15s" + + ## Approximate number of tag IDs to store in in-memory cache (when using tags_as_foreign_keys). + ## This is an optimization to skip inserting known tag IDs. + ## Each entry consumes approximately 34 bytes of memory. + # tag_cache_size = 100000 + + ## Enable & set the log level for the Postgres driver. + # log_level = "warn" # trace, debug, info, warn, error, none +``` + +### Concurrency + +By default the postgresql plugin does not utilize any concurrency. However it can for increased throughput. When concurrency is off, telegraf core handles things like retrying on failure, buffering, etc. When concurrency is used, these aspects have to be handled by the plugin. + +To enable concurrent writes to the database, set the `pool_max_conns` connection parameter to a value >1. When enabled, incoming batches will be split by measurement/table name. In addition, if a batch comes in and the previous batch has not completed, concurrency will be used for the new batch as well. + +If all connections are utilized and the pool is exhausted, further incoming batches will be buffered within telegraf core. + +### Foreign tags + +When using `tags_as_foreign_keys`, tags will be written to a separate table with a `tag_id` column used for joins. Each series (unique combination of tag values) gets its own entry in the tags table, and a unique `tag_id`. + +## Data types + +By default the postgresql plugin maps Influx data types to the following PostgreSQL types: + +| Influx | PostgreSQL | +|--------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------| +| [float](https://docs.influxdata.com/influxdb/latest/reference/syntax/line-protocol/#float) | [double precision](https://www.postgresql.org/docs/current/datatype-numeric.html#DATATYPE-FLOAT) | +| [integer](https://docs.influxdata.com/influxdb/latest/reference/syntax/line-protocol/#integer) | [bigint](https://www.postgresql.org/docs/current/datatype-numeric.html#DATATYPE-INT) | +| [uinteger](https://docs.influxdata.com/influxdb/latest/reference/syntax/line-protocol/#uinteger) | [numeric](https://www.postgresql.org/docs/current/datatype-numeric.html#DATATYPE-NUMERIC-DECIMAL)* | +| [string](https://docs.influxdata.com/influxdb/latest/reference/syntax/line-protocol/#string) | [text](https://www.postgresql.org/docs/current/datatype-character.html) | +| [boolean](https://docs.influxdata.com/influxdb/latest/reference/syntax/line-protocol/#boolean) | [boolean](https://www.postgresql.org/docs/current/datatype-boolean.html) | +| [unix timestamp](https://docs.influxdata.com/influxdb/latest/reference/syntax/line-protocol/#unix-timestamp) | [timestamp](https://www.postgresql.org/docs/current/datatype-datetime.html) | + +It is important to note that `uinteger` (unsigned 64-bit integer) is mapped to the `numeric` PostgreSQL data type. The `numeric` data type is an arbitrary precision decimal data type that is less efficient than `bigint`. This is necessary as the range of values for the Influx `uinteger` data type can exceed `bigint`, and thus cause errors when inserting data. + +### pguint + +As a solution to the `uinteger`/`numeric` data type problem, there is a PostgreSQL extension that offers unsigned 64-bit integer support: [https://github.com/petere/pguint](https://github.com/petere/pguint). + +If this extension is installed, you can enable the `unsigned_integers` config parameter which will cause the plugin to use the `uint8` datatype instead of `numeric`. + +## Templating + +The postgresql plugin uses templates for the schema modification SQL statements. This allows for complete control of the schema by the user. + +Documentation on how to write templates can be found here: +[https://pkg.go.dev/github.com/influxdb/telegraf/plugins/outputs/postgresql/sqltemplate](https://pkg.go.dev/github.com/influxdb/telegraf/plugins/outputs/postgresql/sqltemplate) + +### Samples + +#### TimescaleDB + +```toml +tags_as_foreign_keys = true +create_templates = [ + '''CREATE TABLE {{.table}} ({{.columns}})''', + '''SELECT create_distributed_hypertable({{ .table|quoteLiteral }}, 'time', partitioning_column => 'tag_id', number_partitions => (SELECT count(*) FROM timescaledb_information.data_nodes)::integer, replication_factor => 2, chunk_time_interval => INTERVAL '1h')''', + '''ALTER TABLE {{ .table }} SET (timescaledb.compress, timescaledb.compress_segmentby = 'tag_id')''', +] +``` + +#### Tag table with view + +This example enables `tags_as_foreign_keys`, but creates a postgres view to automatically join the metric & tag tables. The metric & tag tables are stored in a "telegraf" schema, with the view in the "public" schema. + +```toml +tags_as_foreign_keys = true +schema = "telegraf" +create_templates = [ + '''CREATE TABLE {{ .table }} ({{ .columns }})''', + '''CREATE VIEW {{ .table.WithSchema "public" }} AS SELECT time, {{ (.tagTable.Columns.Tags.Concat .allColumns.Fields).Identifiers | join "," }} FROM {{ .table }} t, {{ .tagTable }} tt WHERE t.tag_id = tt.tag_id''', +] +add_column_templates = [ + '''ALTER TABLE {{.table}} ADD COLUMN IF NOT EXISTS {{.columns|join ", ADD COLUMN IF NOT EXISTS "}}''', + '''DROP VIEW IF EXISTS {{ .table.WithSchema "public" }}''', + '''CREATE VIEW {{ .table.WithSchema "public" }} AS SELECT time, {{ (.tagTable.Columns.Tags.Concat .allColumns.Fields).Identifiers | join "," }} FROM {{ .table }} t, {{ .tagTable }} tt WHERE t.tag_id = tt.tag_id''', +] +tag_table_add_column_templates = [ + '''ALTER TABLE {{.table}} ADD COLUMN IF NOT EXISTS {{.columns|join ", ADD COLUMN IF NOT EXISTS "}}''', + '''DROP VIEW IF EXISTS {{ .metricTable.WithSchema "public" }}''', + '''CREATE VIEW {{ .metricTable.WithSchema "public" }} AS SELECT time, {{ (.allColumns.Tags.Concat .metricTable.Columns.Fields).Identifiers | join "," }} FROM {{ .metricTable }} t, {{ .tagTable }} tt WHERE t.tag_id = tt.tag_id''', +] +``` + +#### Immutable data table + +Some PostgreSQL-compatible databases don't allow modification of table schema after initial creation. This example works around the limitation by creating a new table and then using a view to join them together. + +```toml +tags_as_foreign_keys = true +schema = 'telegraf' +create_templates = [ + '''CREATE TABLE {{ .table }} ({{ .allColumns }})''', + '''SELECT create_hypertable({{ .table|quoteLiteral }}, 'time', chunk_time_interval => INTERVAL '1h')''', + '''ALTER TABLE {{ .table }} SET (timescaledb.compress, timescaledb.compress_segmentby = 'tag_id')''', + '''SELECT add_compression_policy({{ .table|quoteLiteral }}, INTERVAL '2h')''', + '''CREATE VIEW {{ .table.WithSuffix "_data" }} AS SELECT {{ .allColumns.Selectors | join "," }} FROM {{ .table }}''', + '''CREATE VIEW {{ .table.WithSchema "public" }} AS SELECT time, {{ (.tagTable.Columns.Tags.Concat .allColumns.Fields).Identifiers | join "," }} FROM {{ .table.WithSuffix "_data" }} t, {{ .tagTable }} tt WHERE t.tag_id = tt.tag_id''', +] +add_column_templates = [ + '''ALTER TABLE {{ .table }} RENAME TO {{ (.table.WithSuffix "_" .table.Columns.Hash).WithSchema "" }}''', + '''ALTER VIEW {{ .table.WithSuffix "_data" }} RENAME TO {{ (.table.WithSuffix "_" .table.Columns.Hash "_data").WithSchema "" }}''', + '''DROP VIEW {{ .table.WithSchema "public" }}''', + + '''CREATE TABLE {{ .table }} ({{ .allColumns }})''', + '''SELECT create_hypertable({{ .table|quoteLiteral }}, 'time', chunk_time_interval => INTERVAL '1h')''', + '''ALTER TABLE {{ .table }} SET (timescaledb.compress, timescaledb.compress_segmentby = 'tag_id')''', + '''SELECT add_compression_policy({{ .table|quoteLiteral }}, INTERVAL '2h')''', + '''CREATE VIEW {{ .table.WithSuffix "_data" }} AS SELECT {{ .allColumns.Selectors | join "," }} FROM {{ .table }} UNION ALL SELECT {{ (.allColumns.Union .table.Columns).Selectors | join "," }} FROM {{ .table.WithSuffix "_" .table.Columns.Hash "_data" }}''', + '''CREATE VIEW {{ .table.WithSchema "public" }} AS SELECT time, {{ (.tagTable.Columns.Tags.Concat .allColumns.Fields).Identifiers | join "," }} FROM {{ .table.WithSuffix "_data" }} t, {{ .tagTable }} tt WHERE t.tag_id = tt.tag_id''', +] +tag_table_add_column_templates = [ + '''ALTER TABLE {{.table}} ADD COLUMN IF NOT EXISTS {{.columns|join ", ADD COLUMN IF NOT EXISTS "}}''', + '''DROP VIEW {{ .metricTable.WithSchema "public" }}''', + '''CREATE VIEW {{ .metricTable.WithSchema "public" }} AS SELECT time, {{ (.allColumns.Tags.Concat .metricTable.Columns.Fields).Identifiers | join "," }} FROM {{ .metricTable.WithSuffix "_data" }} t, {{ .table }} tt WHERE t.tag_id = tt.tag_id''', +] +``` + +## Error handling + +When the plugin encounters an error writing to the database, it attempts to determine whether the error is temporary or permanent. An error is considered temporary if it's possible that retrying the write will succeed. Some examples of temporary errors are things like connection interruption, deadlocks, etc. Permanent errors are things like invalid data type, insufficient permissions, etc. + +When an error is determined to be temporary, the plugin will retry the write with an incremental backoff. +When an error is determined to be permanent, the plugin will discard the sub-batch. The "sub-batch" is the portion of the input batch that is being written to the same table. diff --git a/plugins/outputs/postgresql/columns.go b/plugins/outputs/postgresql/columns.go new file mode 100644 index 0000000000000..ca08fdfd5c615 --- /dev/null +++ b/plugins/outputs/postgresql/columns.go @@ -0,0 +1,26 @@ +package postgresql + +import "github.com/influxdata/telegraf/plugins/outputs/postgresql/utils" + +// Column names and data types for standard fields (time, tag_id, tags, and fields) +const ( + timeColumnName = "time" + timeColumnDataType = PgTimestampWithoutTimeZone + tagIDColumnName = "tag_id" + tagIDColumnDataType = PgBigInt + tagsJSONColumnName = "tags" + fieldsJSONColumnName = "fields" + jsonColumnDataType = PgJSONb +) + +var timeColumn = utils.Column{Name: timeColumnName, Type: timeColumnDataType, Role: utils.TimeColType} +var tagIDColumn = utils.Column{Name: tagIDColumnName, Type: tagIDColumnDataType, Role: utils.TagsIDColType} +var fieldsJSONColumn = utils.Column{Name: fieldsJSONColumnName, Type: jsonColumnDataType, Role: utils.FieldColType} +var tagsJSONColumn = utils.Column{Name: tagsJSONColumnName, Type: jsonColumnDataType, Role: utils.TagColType} + +func (p *Postgresql) columnFromTag(key string, value interface{}) utils.Column { + return utils.Column{Name: key, Type: p.derivePgDatatype(value), Role: utils.TagColType} +} +func (p *Postgresql) columnFromField(key string, value interface{}) utils.Column { + return utils.Column{Name: key, Type: p.derivePgDatatype(value), Role: utils.FieldColType} +} diff --git a/plugins/outputs/postgresql/datatype_uint8.go b/plugins/outputs/postgresql/datatype_uint8.go new file mode 100644 index 0000000000000..3e771f1ec951d --- /dev/null +++ b/plugins/outputs/postgresql/datatype_uint8.go @@ -0,0 +1,356 @@ +//nolint +package postgresql + +// Copied from github.com/jackc/pgtype/int8.go and tweaked for uint64 +/* +Copyright (c) 2013-2021 Jack Christensen + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +import ( + "database/sql" + "database/sql/driver" + "encoding/binary" + "encoding/json" + "errors" + "fmt" + . "github.com/jackc/pgtype" + "math" + "strconv" + + "github.com/jackc/pgio" +) + +var errUndefined = errors.New("cannot encode status undefined") +var errBadStatus = errors.New("invalid status") + +type Uint8 struct { + Int uint64 + Status Status +} + +func (dst *Uint8) Set(src interface{}) error { + if src == nil { + *dst = Uint8{Status: Null} + return nil + } + + if value, ok := src.(interface{ Get() interface{} }); ok { + value2 := value.Get() + if value2 != value { + return dst.Set(value2) + } + } + + switch value := src.(type) { + case int8: + *dst = Uint8{Int: uint64(value), Status: Present} + case uint8: + *dst = Uint8{Int: uint64(value), Status: Present} + case int16: + *dst = Uint8{Int: uint64(value), Status: Present} + case uint16: + *dst = Uint8{Int: uint64(value), Status: Present} + case int32: + *dst = Uint8{Int: uint64(value), Status: Present} + case uint32: + *dst = Uint8{Int: uint64(value), Status: Present} + case int64: + *dst = Uint8{Int: uint64(value), Status: Present} + case uint64: + *dst = Uint8{Int: value, Status: Present} + case int: + if value < 0 { + return fmt.Errorf("%d is less than maximum value for Uint8", value) + } + *dst = Uint8{Int: uint64(value), Status: Present} + case uint: + if uint64(value) > math.MaxInt64 { + return fmt.Errorf("%d is greater than maximum value for Uint8", value) + } + *dst = Uint8{Int: uint64(value), Status: Present} + case string: + num, err := strconv.ParseUint(value, 10, 64) + if err != nil { + return err + } + *dst = Uint8{Int: num, Status: Present} + case float32: + if value > math.MaxInt64 { + return fmt.Errorf("%f is greater than maximum value for Uint8", value) + } + *dst = Uint8{Int: uint64(value), Status: Present} + case float64: + if value > math.MaxInt64 { + return fmt.Errorf("%f is greater than maximum value for Uint8", value) + } + *dst = Uint8{Int: uint64(value), Status: Present} + case *int8: + if value == nil { + *dst = Uint8{Status: Null} + } else { + return dst.Set(*value) + } + case *uint8: + if value == nil { + *dst = Uint8{Status: Null} + } else { + return dst.Set(*value) + } + case *int16: + if value == nil { + *dst = Uint8{Status: Null} + } else { + return dst.Set(*value) + } + case *uint16: + if value == nil { + *dst = Uint8{Status: Null} + } else { + return dst.Set(*value) + } + case *int32: + if value == nil { + *dst = Uint8{Status: Null} + } else { + return dst.Set(*value) + } + case *uint32: + if value == nil { + *dst = Uint8{Status: Null} + } else { + return dst.Set(*value) + } + case *int64: + if value == nil { + *dst = Uint8{Status: Null} + } else { + return dst.Set(*value) + } + case *uint64: + if value == nil { + *dst = Uint8{Status: Null} + } else { + return dst.Set(*value) + } + case *int: + if value == nil { + *dst = Uint8{Status: Null} + } else { + return dst.Set(*value) + } + case *uint: + if value == nil { + *dst = Uint8{Status: Null} + } else { + return dst.Set(*value) + } + case *string: + if value == nil { + *dst = Uint8{Status: Null} + } else { + return dst.Set(*value) + } + case *float32: + if value == nil { + *dst = Uint8{Status: Null} + } else { + return dst.Set(*value) + } + case *float64: + if value == nil { + *dst = Uint8{Status: Null} + } else { + return dst.Set(*value) + } + default: + return fmt.Errorf("cannot convert %v to Uint8", value) + } + + return nil +} + +func (dst Uint8) Get() interface{} { + switch dst.Status { + case Present: + return dst.Int + case Null: + return nil + default: + return dst.Status + } +} + +func (src *Uint8) AssignTo(dst interface{}) error { + switch v := dst.(type) { + case *int: + *v = int(src.Int) + case *int8: + *v = int8(src.Int) + case *int16: + *v = int16(src.Int) + case *int32: + *v = int32(src.Int) + case *int64: + *v = int64(src.Int) + case *uint: + *v = uint(src.Int) + case *uint8: + *v = uint8(src.Int) + case *uint16: + *v = uint16(src.Int) + case *uint32: + *v = uint32(src.Int) + case *uint64: + *v = src.Int + case *float32: + *v = float32(src.Int) + case *float64: + *v = float64(src.Int) + case *string: + *v = strconv.FormatUint(src.Int, 10) + case sql.Scanner: + return v.Scan(src.Int) + case interface{ Set(interface{}) error }: + return v.Set(src.Int) + default: + return fmt.Errorf("cannot assign %v into %T", src.Int, dst) + } + return nil +} + +func (dst *Uint8) DecodeText(ci *ConnInfo, src []byte) error { + if src == nil { + *dst = Uint8{Status: Null} + return nil + } + + n, err := strconv.ParseUint(string(src), 10, 64) + if err != nil { + return err + } + + *dst = Uint8{Int: n, Status: Present} + return nil +} + +func (dst *Uint8) DecodeBinary(ci *ConnInfo, src []byte) error { + if src == nil { + *dst = Uint8{Status: Null} + return nil + } + + if len(src) != 8 { + return fmt.Errorf("invalid length for int8: %v", len(src)) + } + + n := binary.BigEndian.Uint64(src) + + *dst = Uint8{Int: n, Status: Present} + return nil +} + +func (src Uint8) EncodeText(ci *ConnInfo, buf []byte) ([]byte, error) { + switch src.Status { + case Null: + return nil, nil + case Undefined: + return nil, errUndefined + } + + return append(buf, strconv.FormatUint(src.Int, 10)...), nil +} + +func (src Uint8) EncodeBinary(ci *ConnInfo, buf []byte) ([]byte, error) { + switch src.Status { + case Null: + return nil, nil + case Undefined: + return nil, errUndefined + } + + return pgio.AppendUint64(buf, src.Int), nil +} + +// Scan implements the database/sql Scanner interface. +func (dst *Uint8) Scan(src interface{}) error { + if src == nil { + *dst = Uint8{Status: Null} + return nil + } + + switch src := src.(type) { + case uint64: + *dst = Uint8{Int: src, Status: Present} + return nil + case string: + return dst.DecodeText(nil, []byte(src)) + case []byte: + srcCopy := make([]byte, len(src)) + copy(srcCopy, src) + return dst.DecodeText(nil, srcCopy) + } + + return fmt.Errorf("cannot scan %T", src) +} + +// Value implements the database/sql/driver Valuer interface. +func (src Uint8) Value() (driver.Value, error) { + switch src.Status { + case Present: + return int64(src.Int), nil + case Null: + return nil, nil + default: + return nil, errUndefined + } +} + +func (src Uint8) MarshalJSON() ([]byte, error) { + switch src.Status { + case Present: + return []byte(strconv.FormatUint(src.Int, 10)), nil + case Null: + return []byte("null"), nil + case Undefined: + return nil, errUndefined + } + + return nil, errBadStatus +} + +func (dst *Uint8) UnmarshalJSON(b []byte) error { + var n *uint64 + err := json.Unmarshal(b, &n) + if err != nil { + return err + } + + if n == nil { + *dst = Uint8{Status: Null} + } else { + *dst = Uint8{Int: *n, Status: Present} + } + + return nil +} diff --git a/plugins/outputs/postgresql/datatypes.go b/plugins/outputs/postgresql/datatypes.go new file mode 100644 index 0000000000000..eed36c1c9dea1 --- /dev/null +++ b/plugins/outputs/postgresql/datatypes.go @@ -0,0 +1,60 @@ +package postgresql + +import ( + "time" +) + +// Constants for naming PostgreSQL data types both in +// their short and long versions. +const ( + PgBool = "boolean" + PgSmallInt = "smallint" + PgInteger = "integer" + PgBigInt = "bigint" + PgReal = "real" + PgDoublePrecision = "double precision" + PgNumeric = "numeric" + PgText = "text" + PgTimestampWithTimeZone = "timestamp with time zone" + PgTimestampWithoutTimeZone = "timestamp without time zone" + PgSerial = "serial" + PgJSONb = "jsonb" +) + +// Types from pguint +const ( + PgUint8 = "uint8" +) + +// DerivePgDatatype returns the appropriate PostgreSQL data type +// that could hold the value. +func (p *Postgresql) derivePgDatatype(value interface{}) string { + if p.UseUint8 { + if _, ok := value.(uint64); ok { + return PgUint8 + } + } + + switch value.(type) { + case bool: + return PgBool + case uint64: + return PgNumeric + case int64, int, uint, uint32: + return PgBigInt + case int32: + return PgInteger + case int16, int8: + return PgSmallInt + case float64: + return PgDoublePrecision + case float32: + return PgReal + case string: + return PgText + case time.Time: + return PgTimestampWithoutTimeZone + default: + return PgText + } +} diff --git a/plugins/outputs/postgresql/postgresql.go b/plugins/outputs/postgresql/postgresql.go new file mode 100644 index 0000000000000..743e3e80b0ab7 --- /dev/null +++ b/plugins/outputs/postgresql/postgresql.go @@ -0,0 +1,557 @@ +package postgresql + +import ( + "context" + "errors" + "fmt" + "strings" + "time" + + "github.com/coocood/freecache" + "github.com/jackc/pgconn" + "github.com/jackc/pgtype" + "github.com/jackc/pgx/v4" + "github.com/jackc/pgx/v4/pgxpool" + + "github.com/influxdata/telegraf" + "github.com/influxdata/telegraf/config" + "github.com/influxdata/telegraf/models" + "github.com/influxdata/telegraf/plugins/outputs" + "github.com/influxdata/telegraf/plugins/outputs/postgresql/sqltemplate" + "github.com/influxdata/telegraf/plugins/outputs/postgresql/utils" +) + +type dbh interface { + Begin(ctx context.Context) (pgx.Tx, error) + CopyFrom(ctx context.Context, tableName pgx.Identifier, columnNames []string, rowSrc pgx.CopyFromSource) (int64, error) + Exec(ctx context.Context, sql string, arguments ...interface{}) (commandTag pgconn.CommandTag, err error) + Query(ctx context.Context, sql string, args ...interface{}) (pgx.Rows, error) +} + +var sampleConfig = ` + ## Specify connection address via the standard libpq connection string: + ## host=... user=... password=... sslmode=... dbname=... + ## Or a URL: + ## postgres://[user[:password]]@localhost[/dbname]\ + ## ?sslmode=[disable|verify-ca|verify-full] + ## + ## All connection parameters are optional. Environment vars are also supported. + ## e.g. PGPASSWORD, PGHOST, PGUSER, PGDATABASE + ## All supported vars can be found here: + ## https://www.postgresql.org/docs/current/libpq-envars.html + ## + ## Non-standard parameters: + ## pool_max_conns (default: 1) - Maximum size of connection pool for parallel (per-batch per-table) inserts. + ## pool_min_conns (default: 0) - Minimum size of connection pool. + ## pool_max_conn_lifetime (default: 0s) - Maximum age of a connection before closing. + ## pool_max_conn_idle_time (default: 0s) - Maximum idle time of a connection before closing. + ## pool_health_check_period (default: 0s) - Duration between health checks on idle connections. + # connection = "" + + ## Postgres schema to use. + # schema = "public" + + ## Store tags as foreign keys in the metrics table. Default is false. + # tags_as_foreign_keys = false + + ## Suffix to append to table name (measurement name) for the foreign tag table. + # tag_table_suffix = "_tag" + + ## Deny inserting metrics if the foreign tag can't be inserted. + # foreign_tag_constraint = false + + ## Store all tags as a JSONB object in a single 'tags' column. + # tags_as_jsonb = false + + ## Store all fields as a JSONB object in a single 'fields' column. + # fields_as_jsonb = false + + ## Templated statements to execute when creating a new table. + # create_templates = [ + # '''CREATE TABLE {{.table}} ({{.columns}})''', + # ] + + ## Templated statements to execute when adding columns to a table. + ## Set to an empty list to disable. Points containing tags for which there is no column will be skipped. Points + ## containing fields for which there is no column will have the field omitted. + # add_column_templates = [ + # '''ALTER TABLE {{.table}} ADD COLUMN IF NOT EXISTS {{.columns|join ", ADD COLUMN IF NOT EXISTS "}}''', + # ] + + ## Templated statements to execute when creating a new tag table. + # tag_table_create_templates = [ + # '''CREATE TABLE {{.table}} ({{.columns}}, PRIMARY KEY (tag_id))''', + # ] + + ## Templated statements to execute when adding columns to a tag table. + ## Set to an empty list to disable. Points containing tags for which there is no column will be skipped. + # tag_table_add_column_templates = [ + # '''ALTER TABLE {{.table}} ADD COLUMN IF NOT EXISTS {{.columns|join ", ADD COLUMN IF NOT EXISTS "}}''', + # ] + + ## Controls whether to use the uint8 data type provided by the pguint extension. + # use_uint8 = false + + ## When using pool_max_conns>1, and a temporary error occurs, the query is retried with an incremental backoff. This + ## controls the maximum backoff duration. + # retry_max_backoff = "15s" + + ## Approximate number of tag IDs to store in in-memory cache (when using tags_as_foreign_keys). + ## This is an optimization to skip inserting known tag IDs. + ## Each entry consumes approximately 34 bytes of memory. + # tag_cache_size = 100000 + + ## Enable & set the log level for the Postgres driver. + # log_level = "warn" # trace, debug, info, warn, error, none +` + +type Postgresql struct { + Connection string `toml:"connection"` + Schema string `toml:"schema"` + TagsAsForeignKeys bool `toml:"tags_as_foreign_keys"` + TagTableSuffix string `toml:"tag_table_suffix"` + ForeignTagConstraint bool `toml:"foreign_tag_constraint"` + TagsAsJsonb bool `toml:"tags_as_jsonb"` + FieldsAsJsonb bool `toml:"fields_as_jsonb"` + CreateTemplates []*sqltemplate.Template `toml:"create_templates"` + AddColumnTemplates []*sqltemplate.Template `toml:"add_column_templates"` + TagTableCreateTemplates []*sqltemplate.Template `toml:"tag_table_create_templates"` + TagTableAddColumnTemplates []*sqltemplate.Template `toml:"tag_table_add_column_templates"` + UseUint8 bool `toml:"use_uint8"` + RetryMaxBackoff config.Duration `toml:"retry_max_backoff"` + TagCacheSize int `toml:"tag_cache_size"` + LogLevel string `toml:"log_level"` + + dbContext context.Context + dbContextCancel func() + dbConfig *pgxpool.Config + db *pgxpool.Pool + tableManager *TableManager + tagsCache *freecache.Cache + + pguint8 *pgtype.DataType + + writeChan chan *TableSource + writeWaitGroup *utils.WaitGroup + + Logger telegraf.Logger `toml:"-"` +} + +func init() { + outputs.Add("postgresql", func() telegraf.Output { return newPostgresql() }) +} + +func newPostgresql() *Postgresql { + return &Postgresql{} +} + +func (p *Postgresql) Init() error { + if p.Schema == "" { + p.Schema = "public" + } + + if p.TagTableSuffix == "" { + p.TagTableSuffix = "_tag" + } + + if p.CreateTemplates == nil { + t := &sqltemplate.Template{} + _ = t.UnmarshalText([]byte(`CREATE TABLE {{.table}} ({{.columns}})`)) + p.CreateTemplates = []*sqltemplate.Template{t} + } + + if p.AddColumnTemplates == nil { + t := &sqltemplate.Template{} + _ = t.UnmarshalText([]byte(`ALTER TABLE {{.table}} ADD COLUMN IF NOT EXISTS {{.columns|join ", ADD COLUMN IF NOT EXISTS "}}`)) + p.AddColumnTemplates = []*sqltemplate.Template{t} + } + + if p.TagTableCreateTemplates == nil { + t := &sqltemplate.Template{} + _ = t.UnmarshalText([]byte(`CREATE TABLE {{.table}} ({{.columns}}, PRIMARY KEY (tag_id))`)) + p.TagTableCreateTemplates = []*sqltemplate.Template{t} + } + + if p.TagTableAddColumnTemplates == nil { + t := &sqltemplate.Template{} + _ = t.UnmarshalText([]byte(`ALTER TABLE {{.table}} ADD COLUMN IF NOT EXISTS {{.columns|join ", ADD COLUMN IF NOT EXISTS "}}`)) + p.TagTableAddColumnTemplates = []*sqltemplate.Template{t} + } + + if p.RetryMaxBackoff == 0 { + p.RetryMaxBackoff = config.Duration(time.Second * 15) + } + + if p.TagCacheSize == 0 { + p.TagCacheSize = 100000 + } else if p.TagCacheSize < 0 { + return fmt.Errorf("invalid tag_cache_size") + } + + if p.LogLevel == "" { + p.LogLevel = "warn" + } + + if p.TagTableAddColumnTemplates == nil { + t := &sqltemplate.Template{} + _ = t.UnmarshalText([]byte(`ALTER TABLE {{.table}} ADD COLUMN IF NOT EXISTS {{.columns|join ", ADD COLUMN IF NOT EXISTS "}}`)) + } + + if p.Logger == nil { + p.Logger = models.NewLogger("outputs", "postgresql", "") + } + + var err error + if p.dbConfig, err = pgxpool.ParseConfig(p.Connection); err != nil { + return err + } + parsedConfig, _ := pgx.ParseConfig(p.Connection) + if _, ok := parsedConfig.Config.RuntimeParams["pool_max_conns"]; !ok { + // The pgx default for pool_max_conns is 4. However we want to default to 1. + p.dbConfig.MaxConns = 1 + } + + if _, ok := p.dbConfig.ConnConfig.RuntimeParams["application_name"]; !ok { + p.dbConfig.ConnConfig.RuntimeParams["application_name"] = "telegraf" + } + + if p.LogLevel != "" { + p.dbConfig.ConnConfig.Logger = utils.PGXLogger{Logger: p.Logger} + p.dbConfig.ConnConfig.LogLevel, err = pgx.LogLevelFromString(p.LogLevel) + if err != nil { + return fmt.Errorf("invalid log level") + } + } + + if p.UseUint8 { + p.dbConfig.AfterConnect = p.registerUint8 + } + + return nil +} + +func (p *Postgresql) SampleConfig() string { return sampleConfig } +func (p *Postgresql) Description() string { return "Send metrics to PostgreSQL" } + +// Connect establishes a connection to the target database and prepares the cache +func (p *Postgresql) Connect() error { + // Yes, we're not supposed to store the context. However since we don't receive a context, we have to. + p.dbContext, p.dbContextCancel = context.WithCancel(context.Background()) + var err error + p.db, err = pgxpool.ConnectConfig(p.dbContext, p.dbConfig) + if err != nil { + p.Logger.Errorf("Couldn't connect to server\n%v", err) + return err + } + p.tableManager = NewTableManager(p) + + if p.TagsAsForeignKeys { + p.tagsCache = freecache.NewCache(p.TagCacheSize * 34) // from testing, each entry consumes approx 34 bytes + } + + maxConns := int(p.db.Stat().MaxConns()) + if maxConns > 1 { + p.writeChan = make(chan *TableSource) + p.writeWaitGroup = utils.NewWaitGroup() + for i := 0; i < maxConns; i++ { + p.writeWaitGroup.Add(1) + go p.writeWorker(p.dbContext) + } + } + + return nil +} + +func (p *Postgresql) registerUint8(ctx context.Context, conn *pgx.Conn) error { + if p.pguint8 == nil { + dt := pgtype.DataType{ + // Use 'numeric' type for encoding/decoding across the wire + // It might be more efficient to create a native pgtype.Type, but would involve a lot of code. So this is + // probably good enough. + Value: &Uint8{}, + Name: "uint8", + } + row := conn.QueryRow(p.dbContext, "SELECT oid FROM pg_type WHERE typname=$1", dt.Name) + if err := row.Scan(&dt.OID); err != nil { + return fmt.Errorf("retreiving OID for uint8 data type: %w", err) + } + p.pguint8 = &dt + } + + conn.ConnInfo().RegisterDataType(*p.pguint8) + return nil +} + +// Close closes the connection(s) to the database. +func (p *Postgresql) Close() error { + if p.writeChan != nil { + // We're using async mode. Gracefully close with timeout. + close(p.writeChan) + select { + case <-p.writeWaitGroup.C(): + case <-time.NewTimer(time.Second * 5).C: + p.Logger.Warnf("Shutdown timeout expired while waiting for metrics to flush. Some metrics may not be written to database.") + } + } + + // Die! + p.dbContextCancel() + p.db.Close() + p.tableManager = nil + return nil +} + +func (p *Postgresql) Write(metrics []telegraf.Metric) error { + if p.tagsCache != nil { + // gather at the start of write so there's less chance of any async operations ongoing + p.Logger.Debugf("cache: size=%d hit=%d miss=%d full=%d\n", + p.tagsCache.EntryCount(), + p.tagsCache.HitCount(), + p.tagsCache.MissCount(), + p.tagsCache.EvacuateCount(), + ) + p.tagsCache.ResetStatistics() + } + + tableSources := NewTableSources(p, metrics) + + var err error + if p.db.Stat().MaxConns() > 1 { + err = p.writeConcurrent(tableSources) + } else { + err = p.writeSequential(tableSources) + } + if err != nil { + var pgErr *pgconn.PgError + if errors.As(err, &pgErr) { + // PgError doesn't include .Detail in Error(), so we concat it onto .Message. + if pgErr.Detail != "" { + pgErr.Message += "; " + pgErr.Detail + } + } + } + + return err +} + +func (p *Postgresql) writeSequential(tableSources map[string]*TableSource) error { + tx, err := p.db.Begin(p.dbContext) + if err != nil { + return fmt.Errorf("starting transaction: %w", err) + } + defer tx.Rollback(p.dbContext) //nolint:errcheck + + for _, tableSource := range tableSources { + sp := tx + if len(tableSources) > 1 { + // wrap each sub-batch in a savepoint so that if a permanent error is received, we can drop just that one sub-batch, and insert everything else. + sp, err = tx.Begin(p.dbContext) + if err != nil { + return fmt.Errorf("starting savepoint: %w", err) + } + } + + err := p.writeMetricsFromMeasure(p.dbContext, sp, tableSource) + if err != nil { + if isTempError(err) { + // return so that telegraf will retry the whole batch + return err + } + p.Logger.Errorf("write error (permanent, dropping sub-batch): %v", err) + if len(tableSources) > 1 { + if err := sp.Rollback(p.dbContext); err != nil { + return err + } + } + } + // savepoints do not need to be committed (released), so save the round trip and skip it + } + + if err := tx.Commit(p.dbContext); err != nil { + return fmt.Errorf("committing transaction: %w", err) + } + return nil +} + +func (p *Postgresql) writeConcurrent(tableSources map[string]*TableSource) error { + for _, tableSource := range tableSources { + select { + case p.writeChan <- tableSource: + case <-p.dbContext.Done(): + return nil + } + } + return nil +} + +func (p *Postgresql) writeWorker(ctx context.Context) { + defer p.writeWaitGroup.Done() + for { + select { + case tableSource, ok := <-p.writeChan: + if !ok { + return + } + if err := p.writeRetry(ctx, tableSource); err != nil { + p.Logger.Errorf("write error (permanent, dropping sub-batch): %v", err) + } + case <-p.dbContext.Done(): + return + } + } +} + +// isTempError reports whether the error received during a metric write operation is temporary or permanent. +// A temporary error is one that if the write were retried at a later time, that it might succeed. +// Note however that this applies to the transaction as a whole, not the individual operation. Meaning for example a +// write might come in that needs a new table created, but another worker already created the table in between when we +// checked for it, and tried to create it. In this case, the operation error is permanent, as we can try `CREATE TABLE` +// again and it will still fail. But if we retry the transaction from scratch, when we perform the table check we'll see +// it exists, so we consider the error temporary. +func isTempError(err error) bool { + var pgErr *pgconn.PgError + if errors.As(err, &pgErr); pgErr != nil { + // https://www.postgresql.org/docs/12/errcodes-appendix.html + errClass := pgErr.Code[:2] + switch errClass { + case "23": // Integrity Constraint Violation + switch pgErr.Code { //nolint:revive + case "23505": // unique_violation + if strings.Contains(err.Error(), "pg_type_typname_nsp_index") { + // Happens when you try to create 2 tables simultaneously. + return true + } + } + case "25": // Invalid Transaction State + // If we're here, this is a bug, but recoverable + return true + case "40": // Transaction Rollback + switch pgErr.Code { //nolint:revive + case "40P01": // deadlock_detected + return true + } + case "42": // Syntax Error or Access Rule Violation + switch pgErr.Code { + case "42701": // duplicate_column + return true + case "42P07": // duplicate_table + return true + } + case "53": // Insufficient Resources + return true + case "57": // Operator Intervention + switch pgErr.Code { //nolint:revive + case "57014": // query_cancelled + // This one is a bit of a mess. This code comes back when PGX cancels the query. Such as when PGX can't + // convert to the column's type. So even though the error was originally generated by PGX, we get the + // error from Postgres. + return false + case "57P04": // database_dropped + return false + } + return true + } + // Assume that any other error that comes from postgres is a permanent error + return false + } + + if err, ok := err.(interface{ Temporary() bool }); ok { + return err.Temporary() + } + + // Assume that any other error is permanent. + // This may mean that we incorrectly discard data that could have been retried, but the alternative is that we get + // stuck retrying data that will never succeed, causing good data to be dropped because the buffer fills up. + return false +} + +func (p *Postgresql) writeRetry(ctx context.Context, tableSource *TableSource) error { + backoff := time.Duration(0) + for { + err := p.writeMetricsFromMeasure(ctx, p.db, tableSource) + if err == nil { + return nil + } + + if !isTempError(err) { + return err + } + p.Logger.Errorf("write error (retry in %s): %v", backoff, err) + tableSource.Reset() + time.Sleep(backoff) + + if backoff == 0 { + backoff = time.Millisecond * 250 + } else { + backoff *= 2 + if backoff > time.Duration(p.RetryMaxBackoff) { + backoff = time.Duration(p.RetryMaxBackoff) + } + } + } +} + +// Writes the metrics from a specified measure. All the provided metrics must belong to the same measurement. +func (p *Postgresql) writeMetricsFromMeasure(ctx context.Context, db dbh, tableSource *TableSource) error { + err := p.tableManager.MatchSource(ctx, db, tableSource) + if err != nil { + return err + } + + if p.TagsAsForeignKeys { + if err := p.writeTagTable(ctx, db, tableSource); err != nil { + if p.ForeignTagConstraint { + return fmt.Errorf("writing to tag table '%s': %s", tableSource.Name()+p.TagTableSuffix, err) + } + // log and continue. As the admin can correct the issue, and tags don't change over time, they can be + // added from future metrics after issue is corrected. + p.Logger.Errorf("writing to tag table '%s': %s", tableSource.Name()+p.TagTableSuffix, err) + } + } + + fullTableName := utils.FullTableName(p.Schema, tableSource.Name()) + if _, err := db.CopyFrom(ctx, fullTableName, tableSource.ColumnNames(), tableSource); err != nil { + return err + } + + return nil +} + +func (p *Postgresql) writeTagTable(ctx context.Context, db dbh, tableSource *TableSource) error { + ttsrc := NewTagTableSource(tableSource) + + // Check whether we have any tags to insert + if !ttsrc.Next() { + return nil + } + ttsrc.Reset() + + // need a transaction so that if it errors, we don't roll back the parent transaction, just the tags + tx, err := db.Begin(ctx) + if err != nil { + return err + } + defer tx.Rollback(ctx) //nolint:errcheck + + ident := pgx.Identifier{ttsrc.postgresql.Schema, ttsrc.Name()} + identTemp := pgx.Identifier{ttsrc.Name() + "_temp"} + sql := fmt.Sprintf("CREATE TEMP TABLE %s (LIKE %s) ON COMMIT DROP", identTemp.Sanitize(), ident.Sanitize()) + if _, err := tx.Exec(ctx, sql); err != nil { + return fmt.Errorf("creating tags temp table: %w", err) + } + + if _, err := tx.CopyFrom(ctx, identTemp, ttsrc.ColumnNames(), ttsrc); err != nil { + return fmt.Errorf("copying into tags temp table: %w", err) + } + + if _, err := tx.Exec(ctx, fmt.Sprintf("INSERT INTO %s SELECT * FROM %s ORDER BY tag_id ON CONFLICT (tag_id) DO NOTHING", ident.Sanitize(), identTemp.Sanitize())); err != nil { + return fmt.Errorf("inserting into tags table: %w", err) + } + + if err := tx.Commit(ctx); err != nil { + return err + } + + ttsrc.UpdateCache() + return nil +} diff --git a/plugins/outputs/postgresql/postgresql_bench_test.go b/plugins/outputs/postgresql/postgresql_bench_test.go new file mode 100644 index 0000000000000..8780385feffa4 --- /dev/null +++ b/plugins/outputs/postgresql/postgresql_bench_test.go @@ -0,0 +1,103 @@ +package postgresql + +import ( + "context" + "fmt" + "math/rand" + "strconv" + "testing" + "time" + + "github.com/influxdata/telegraf" + "github.com/influxdata/telegraf/metric" +) + +func BenchmarkPostgresql_sequential(b *testing.B) { + gen := batchGenerator(batchGeneratorArgs{ctx, b, 1000, 3, 8, 12, 100, 2}) + benchmarkPostgresql(b, gen, 1, true) +} +func BenchmarkPostgresql_concurrent(b *testing.B) { + gen := batchGenerator(batchGeneratorArgs{ctx, b, 1000, 3, 8, 12, 100, 2}) + benchmarkPostgresql(b, gen, 10, true) +} + +func benchmarkPostgresql(b *testing.B, gen <-chan []telegraf.Metric, concurrency int, foreignTags bool) { + p := newPostgresqlTest(b) + p.Connection += fmt.Sprintf(" pool_max_conns=%d", concurrency) + p.TagsAsForeignKeys = foreignTags + p.LogLevel = "" + _ = p.Init() + if err := p.Connect(); err != nil { + b.Fatalf("Error: %s", err) + } + + metricCount := 0 + + b.ResetTimer() + tStart := time.Now() + for i := 0; i < b.N; i++ { + batch := <-gen + if err := p.Write(batch); err != nil { + b.Fatalf("Error: %s", err) + } + metricCount += len(batch) + } + p.Close() + b.StopTimer() + tStop := time.Now() + b.ReportMetric(float64(metricCount)/tStop.Sub(tStart).Seconds(), "metrics/s") +} + +type batchGeneratorArgs struct { + ctx context.Context + b *testing.B + batchSize int + numTables int + numTags int + numFields int + tagCardinality int + fieldCardinality int +} + +// tagCardinality counts all the tag keys & values as one element. fieldCardinality counts all the field keys (not values) as one element. +func batchGenerator(args batchGeneratorArgs) <-chan []telegraf.Metric { + tagSets := make([]MSS, args.tagCardinality) + for i := 0; i < args.tagCardinality; i++ { + tags := MSS{} + for j := 0; j < args.numTags; j++ { + tags[fmt.Sprintf("tag_%d", j)] = fmt.Sprintf("%d", rand.Int()) + } + tagSets[i] = tags + } + + metricChan := make(chan []telegraf.Metric, 32) + go func() { + for { + batch := make([]telegraf.Metric, args.batchSize) + for i := 0; i < args.batchSize; i++ { + tableName := args.b.Name() + "_" + strconv.Itoa(rand.Intn(args.numTables)) + + tags := tagSets[rand.Intn(len(tagSets))] + + m := metric.New(tableName, tags, nil, time.Now()) + m.AddTag("tableName", tableName) // ensure the tag set is unique to this table. Just in case... + + // We do field cardinality by randomizing the name of the final field to an integer < cardinality. + for j := 0; j < args.numFields-1; j++ { // use -1 to reserve the last field for cardinality + m.AddField("f"+strconv.Itoa(j), rand.Int()) + } + m.AddField("f"+strconv.Itoa(rand.Intn(args.fieldCardinality)), rand.Int()) + + batch[i] = m + } + + select { + case metricChan <- batch: + case <-ctx.Done(): + return + } + } + }() + + return metricChan +} diff --git a/plugins/outputs/postgresql/postgresql_test.go b/plugins/outputs/postgresql/postgresql_test.go new file mode 100644 index 0000000000000..af5bfd388bbb3 --- /dev/null +++ b/plugins/outputs/postgresql/postgresql_test.go @@ -0,0 +1,768 @@ +package postgresql + +import ( + "bytes" + "context" + "encoding/json" + "flag" + "fmt" + "math" + "math/rand" + "os" + "regexp" + "strings" + "sync" + "testing" + "time" + + "github.com/influxdata/toml" + + "github.com/influxdata/telegraf/testutil" + + "github.com/jackc/pgx/v4" + "github.com/jackc/pgx/v4/pgxpool" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/influxdata/telegraf" + "github.com/influxdata/telegraf/plugins/outputs/postgresql/utils" +) + +type Log struct { + level pgx.LogLevel + format string + args []interface{} +} + +func (l Log) String() string { + // We have to use Errorf() as Sprintf() doesn't allow usage of %w. + return fmt.Errorf("%s: "+l.format, append([]interface{}{l.level}, l.args...)...).Error() +} + +// LogAccumulator is a log collector that satisfies telegraf.Logger. +type LogAccumulator struct { + logs []Log + cond *sync.Cond + tb testing.TB + emitLevel pgx.LogLevel +} + +func NewLogAccumulator(tb testing.TB) *LogAccumulator { + return &LogAccumulator{ + cond: sync.NewCond(&sync.Mutex{}), + tb: tb, + } +} + +func (la *LogAccumulator) append(level pgx.LogLevel, format string, args []interface{}) { + la.tb.Helper() + + la.cond.L.Lock() + log := Log{level, format, args} + la.logs = append(la.logs, log) + + if la.emitLevel == 0 || log.level <= la.emitLevel { + la.tb.Log(log.String()) + } + + la.cond.Broadcast() + la.cond.L.Unlock() +} + +func (la *LogAccumulator) HasLevel(level pgx.LogLevel) bool { + la.cond.L.Lock() + defer la.cond.L.Unlock() + for _, log := range la.logs { + if log.level > 0 && log.level <= level { + return true + } + } + return false +} + +func (la *LogAccumulator) WaitLen(n int) []Log { + la.cond.L.Lock() + defer la.cond.L.Unlock() + for len(la.logs) < n { + la.cond.Wait() + } + return la.logs[:] +} + +// Waits for a specific query log from pgx to show up. +func (la *LogAccumulator) WaitFor(f func(l Log) bool, waitCommit bool) { + la.cond.L.Lock() + defer la.cond.L.Unlock() + i := 0 + var commitPid uint32 + for { + for ; i < len(la.logs); i++ { + log := la.logs[i] + if commitPid == 0 { + if f(log) { + if !waitCommit { + return + } + commitPid = log.args[1].(MSI)["pid"].(uint32) + } + } else { + if len(log.args) < 2 { + continue + } + data, ok := log.args[1].(MSI) + if !ok || data["pid"] != commitPid { + continue + } + if log.args[0] == "Exec" && data["sql"] == "commit" { + return + } else if log.args[0] == "Exec" && data["sql"] == "rollback" { + // transaction aborted, start looking for another match + commitPid = 0 + } else if log.level == pgx.LogLevelError { + commitPid = 0 + } + } + } + la.cond.Wait() + } +} + +func (la *LogAccumulator) WaitForQuery(str string, waitCommit bool) { + la.WaitFor(func(log Log) bool { + return log.format == "PG %s - %+v" && + (log.args[0].(string) == "Query" || log.args[0].(string) == "Exec") && + strings.Contains(log.args[1].(MSI)["sql"].(string), str) + }, waitCommit) +} + +func (la *LogAccumulator) WaitForCopy(tableName string, waitCommit bool) { + la.WaitFor(func(log Log) bool { + return log.format == "PG %s - %+v" && + log.args[0].(string) == "CopyFrom" && + log.args[1].(MSI)["tableName"].(pgx.Identifier)[1] == tableName + }, waitCommit) +} + +// Clear any stored logs. +// Do not run this while any WaitFor* operations are in progress. +func (la *LogAccumulator) Clear() { + la.cond.L.Lock() + if len(la.logs) > 0 { + la.logs = nil + } + la.cond.L.Unlock() +} + +func (la *LogAccumulator) Logs() []Log { + la.cond.L.Lock() + defer la.cond.L.Unlock() + return la.logs[:] +} + +func (la *LogAccumulator) Errorf(format string, args ...interface{}) { + la.tb.Helper() + la.append(pgx.LogLevelError, format, args) +} + +func (la *LogAccumulator) Error(args ...interface{}) { + la.tb.Helper() + la.append(pgx.LogLevelError, "%v", args) +} + +func (la *LogAccumulator) Debugf(format string, args ...interface{}) { + la.tb.Helper() + la.append(pgx.LogLevelDebug, format, args) +} + +func (la *LogAccumulator) Debug(args ...interface{}) { + la.tb.Helper() + la.append(pgx.LogLevelDebug, "%v", args) +} + +func (la *LogAccumulator) Warnf(format string, args ...interface{}) { + la.tb.Helper() + la.append(pgx.LogLevelWarn, format, args) +} + +func (la *LogAccumulator) Warn(args ...interface{}) { + la.tb.Helper() + la.append(pgx.LogLevelWarn, "%v", args) +} + +func (la *LogAccumulator) Infof(format string, args ...interface{}) { + la.tb.Helper() + la.append(pgx.LogLevelInfo, format, args) +} + +func (la *LogAccumulator) Info(args ...interface{}) { + la.tb.Helper() + la.append(pgx.LogLevelInfo, "%v", args) +} + +var ctx = context.Background() + +func TestMain(m *testing.M) { + flag.Parse() + if testing.Short() { + os.Exit(m.Run()) + } + + // Use the integration server if no other PG addr env vars specified. + pguri := "postgresql://localhost:5432" + for _, varname := range []string{"PGURI", "PGHOST", "PGHOSTADDR", "PGPORT"} { + if os.Getenv(varname) != "" { + pguri = "" + break + } + } + if pguri != "" { + _ = os.Setenv("PGURI", pguri) + } + + if err := prepareDatabase("telegraf"); err != nil { + _, _ = fmt.Fprintf(os.Stderr, "Error preparing database: %s\n", err) + os.Exit(1) + } + os.Exit(m.Run()) +} + +func prepareDatabase(name string) error { + db, err := pgx.Connect(ctx, os.Getenv("PGURI")) + if err != nil { + return err + } + _, err = db.Exec(ctx, "DROP DATABASE IF EXISTS "+name) + if err != nil { + return err + } + _, err = db.Exec(ctx, "CREATE DATABASE "+name) + return err +} + +type PostgresqlTest struct { + *Postgresql + Logger *LogAccumulator +} + +func newPostgresqlTest(tb testing.TB) *PostgresqlTest { + if testing.Short() { + tb.Skipf("skipping integration test in short mode") + tb.SkipNow() + } + + p := newPostgresql() + p.Connection = "database=telegraf" + logger := NewLogAccumulator(tb) + p.Logger = logger + p.LogLevel = "debug" + require.NoError(tb, p.Init()) + pt := &PostgresqlTest{Postgresql: p} + pt.Logger = logger + + tb.Cleanup(func() { + if pt.db != nil { + // This will block forever (timeout the test) if not all connections were released (committed/rollbacked). + // We can't use pt.db.Stats() because in some cases, pgx releases the connection asynchronously. + pt.db.Close() + } + }) + + return pt +} + +// Verify that the documented defaults match the actual defaults. +// +// Sample config must be in the format documented in `docs/developers/SAMPLE_CONFIG.md`. +func TestPostgresqlSampleConfig(t *testing.T) { + p1 := newPostgresql() + require.NoError(t, p1.Init()) + + p2 := newPostgresql() + re := regexp.MustCompile(`(?m)^\s*#`) + conf := re.ReplaceAllLiteralString(p1.SampleConfig(), "") + require.NoError(t, toml.Unmarshal([]byte(conf), p2)) + require.NoError(t, p2.Init()) + + // Can't use assert.Equal() because it dives into unexported fields that contain unequal values. + // Serializing to JSON is effective as any differences will be visible in exported fields. + p1json, _ := json.Marshal(p1) + p2json, _ := json.Marshal(p2) + assert.JSONEq(t, string(p1json), string(p2json), "Sample config does not match default config") +} + +func TestPostgresqlConnect(t *testing.T) { + p := newPostgresqlTest(t) + require.NoError(t, p.Connect()) + assert.EqualValues(t, 1, p.db.Stat().MaxConns()) + + p = newPostgresqlTest(t) + p.Connection += " pool_max_conns=2" + _ = p.Init() + require.NoError(t, p.Connect()) + assert.EqualValues(t, 2, p.db.Stat().MaxConns()) +} + +func newMetric( + t *testing.T, + suffix string, + tags map[string]string, + fields map[string]interface{}, +) telegraf.Metric { + return testutil.MustMetric(t.Name()+suffix, tags, fields, time.Now()) +} + +type MSS = map[string]string +type MSI = map[string]interface{} + +func dbTableDump(t *testing.T, db *pgxpool.Pool, suffix string) []MSI { + rows, err := db.Query(ctx, "SELECT * FROM "+pgx.Identifier{t.Name() + suffix}.Sanitize()) + require.NoError(t, err) + defer rows.Close() + + var dump []MSI + for rows.Next() { + msi := MSI{} + vals, err := rows.Values() + require.NoError(t, err) + for i, fd := range rows.FieldDescriptions() { + msi[string(fd.Name)] = vals[i] + } + dump = append(dump, msi) + } + require.NoError(t, rows.Err()) + return dump +} + +func TestWrite_sequential(t *testing.T) { + p := newPostgresqlTest(t) + require.NoError(t, p.Connect()) + + metrics := []telegraf.Metric{ + newMetric(t, "_a", MSS{}, MSI{"v": 1}), + newMetric(t, "_b", MSS{}, MSI{"v": 2}), + newMetric(t, "_a", MSS{}, MSI{"v": 3}), + } + require.NoError(t, p.Write(metrics)) + + dumpA := dbTableDump(t, p.db, "_a") + dumpB := dbTableDump(t, p.db, "_b") + + if assert.Len(t, dumpA, 2) { + assert.EqualValues(t, 1, dumpA[0]["v"]) + assert.EqualValues(t, 3, dumpA[1]["v"]) + } + if assert.Len(t, dumpB, 1) { + assert.EqualValues(t, 2, dumpB[0]["v"]) + } + + p.Logger.Clear() + require.NoError(t, p.Write(metrics)) + + stmtCount := 0 + for _, log := range p.Logger.Logs() { + if strings.Contains(log.String(), "info: PG ") { + stmtCount++ + } + } + assert.Equal(t, 6, stmtCount) // BEGIN, SAVEPOINT, COPY table _a, SAVEPOINT, COPY table _b, COMMIT +} + +func TestWrite_concurrent(t *testing.T) { + p := newPostgresqlTest(t) + p.dbConfig.MaxConns = 3 + require.NoError(t, p.Connect()) + + // Write a metric so it creates a table we can lock. + metrics := []telegraf.Metric{ + newMetric(t, "_a", MSS{}, MSI{"v": 1}), + } + require.NoError(t, p.Write(metrics)) + p.Logger.WaitForCopy(t.Name()+"_a", false) + // clear so that the WaitForCopy calls below don't pick up this one + p.Logger.Clear() + + // Lock the table so that we ensure the writes hangs and the plugin has to open another connection. + tx, err := p.db.Begin(ctx) + require.NoError(t, err) + defer tx.Rollback(ctx) //nolint:errcheck + _, err = tx.Exec(ctx, "LOCK TABLE "+utils.QuoteIdentifier(t.Name()+"_a")) + require.NoError(t, err) + + metrics = []telegraf.Metric{ + newMetric(t, "_a", MSS{}, MSI{"v": 2}), + } + require.NoError(t, p.Write(metrics)) + + // Note, there is technically a possible race here, where it doesn't try to insert into _a until after _b. However + // this should be practically impossible, and trying to engineer a solution to account for it would be even more + // complex than we already are. + + metrics = []telegraf.Metric{ + newMetric(t, "_b", MSS{}, MSI{"v": 3}), + } + require.NoError(t, p.Write(metrics)) + + p.Logger.WaitForCopy(t.Name()+"_b", false) + // release the lock on table _a + _ = tx.Rollback(ctx) + p.Logger.WaitForCopy(t.Name()+"_a", false) + + dumpA := dbTableDump(t, p.db, "_a") + dumpB := dbTableDump(t, p.db, "_b") + + if assert.Len(t, dumpA, 2) { + assert.EqualValues(t, 1, dumpA[0]["v"]) + assert.EqualValues(t, 2, dumpA[1]["v"]) + } + if assert.Len(t, dumpB, 1) { + assert.EqualValues(t, 3, dumpB[0]["v"]) + } + + // We should have had 3 connections. One for the lock, and one for each table. + assert.EqualValues(t, 3, p.db.Stat().TotalConns()) +} + +// Test that the bad metric is dropped, and the rest of the batch succeeds. +func TestWrite_sequentialPermError(t *testing.T) { + p := newPostgresqlTest(t) + require.NoError(t, p.Connect()) + + metrics := []telegraf.Metric{ + newMetric(t, "_a", MSS{}, MSI{"v": 1}), + newMetric(t, "_b", MSS{}, MSI{"v": 2}), + } + require.NoError(t, p.Write(metrics)) + + metrics = []telegraf.Metric{ + newMetric(t, "_a", MSS{}, MSI{"v": "a"}), + newMetric(t, "_b", MSS{}, MSI{"v": 3}), + } + require.NoError(t, p.Write(metrics)) + + dumpA := dbTableDump(t, p.db, "_a") + dumpB := dbTableDump(t, p.db, "_b") + assert.Len(t, dumpA, 1) + assert.Len(t, dumpB, 2) + + haveError := false + for _, l := range p.Logger.Logs() { + if strings.Contains(l.String(), "write error") { + haveError = true + break + } + } + assert.True(t, haveError, "write error not found in log") +} + +// Test that the bad metric is dropped, and the rest of the batch succeeds. +func TestWrite_concurrentPermError(t *testing.T) { + p := newPostgresqlTest(t) + p.dbConfig.MaxConns = 2 + require.NoError(t, p.Connect()) + + metrics := []telegraf.Metric{ + newMetric(t, "_a", MSS{}, MSI{"v": 1}), + } + require.NoError(t, p.Write(metrics)) + p.Logger.WaitForCopy(t.Name()+"_a", false) + + metrics = []telegraf.Metric{ + newMetric(t, "_a", MSS{}, MSI{"v": "a"}), + newMetric(t, "_b", MSS{}, MSI{"v": 2}), + } + require.NoError(t, p.Write(metrics)) + p.Logger.WaitFor(func(l Log) bool { + return strings.Contains(l.String(), "write error") + }, false) + p.Logger.WaitForCopy(t.Name()+"_b", false) + + dumpA := dbTableDump(t, p.db, "_a") + dumpB := dbTableDump(t, p.db, "_b") + assert.Len(t, dumpA, 1) + assert.Len(t, dumpB, 1) +} + +// Verify that in sequential mode, errors are returned allowing telegraf agent to handle & retry +func TestWrite_sequentialTempError(t *testing.T) { + p := newPostgresqlTest(t) + require.NoError(t, p.Connect()) + + // To avoid a race condition, we need to know when our goroutine has started listening to the log. + wg := sync.WaitGroup{} + wg.Add(1) + + go func() { + // Wait for the CREATE TABLE, and then kill the connection. + // The WaitFor callback holds a lock on the log. Meaning it will block logging of the next action. So we trigger + // on CREATE TABLE so that there's a few statements to go before the COMMIT. + p.Logger.WaitFor(func(log Log) bool { + if strings.Contains(log.String(), "release wg") { + wg.Done() + } + + if !strings.Contains(log.String(), "CREATE TABLE") { + return false + } + pid := log.args[1].(MSI)["pid"].(uint32) + + conf := p.db.Config().ConnConfig + conf.Logger = nil + c, err := pgx.ConnectConfig(context.Background(), conf) + if !assert.NoError(t, err) { + return true + } + _, err = c.Exec(context.Background(), "SELECT pg_terminate_backend($1)", pid) + assert.NoError(t, err) + return true + }, false) + }() + + p.Logger.Infof("release wg") + wg.Wait() + + metrics := []telegraf.Metric{ + newMetric(t, "_a", MSS{}, MSI{"v": 1}), + } + require.Error(t, p.Write(metrics)) +} + +// Verify that when using concurrency, errors are not returned, but instead logged and automatically retried +func TestWrite_concurrentTempError(t *testing.T) { + p := newPostgresqlTest(t) + p.dbConfig.MaxConns = 2 + require.NoError(t, p.Connect()) + + // To avoid a race condition, we need to know when our goroutine has started listening to the log. + wg := sync.WaitGroup{} + wg.Add(1) + + go func() { + // Wait for the CREATE TABLE, and then kill the connection. + // The WaitFor callback holds a lock on the log. Meaning it will block logging of the next action. So we trigger + // on CREATE TABLE so that there's a few statements to go before the COMMIT. + p.Logger.WaitFor(func(log Log) bool { + if strings.Contains(log.String(), "release wg") { + wg.Done() + } + + if !strings.Contains(log.String(), "CREATE TABLE") { + return false + } + pid := log.args[1].(MSI)["pid"].(uint32) + + conf := p.db.Config().ConnConfig + conf.Logger = nil + c, err := pgx.ConnectConfig(context.Background(), conf) + if !assert.NoError(t, err) { + return true + } + _, err = c.Exec(context.Background(), "SELECT pg_terminate_backend($1)", pid) + assert.NoError(t, err) + return true + }, false) + }() + p.Logger.Infof("release wg") + wg.Wait() + + metrics := []telegraf.Metric{ + newMetric(t, "_a", MSS{}, MSI{"v": 1}), + } + require.NoError(t, p.Write(metrics)) + + p.Logger.WaitForCopy(t.Name()+"_a", false) + dumpA := dbTableDump(t, p.db, "_a") + assert.Len(t, dumpA, 1) + + haveError := false + for _, l := range p.Logger.Logs() { + if strings.Contains(l.String(), "write error") { + haveError = true + break + } + } + assert.True(t, haveError, "write error not found in log") +} + +func TestWriteTagTable(t *testing.T) { + p := newPostgresqlTest(t) + p.TagsAsForeignKeys = true + require.NoError(t, p.Connect()) + + metrics := []telegraf.Metric{ + newMetric(t, "", MSS{"tag": "foo"}, MSI{"v": 1}), + } + require.NoError(t, p.Write(metrics)) + + dump := dbTableDump(t, p.db, "") + require.Len(t, dump, 1) + assert.EqualValues(t, 1, dump[0]["v"]) + + dumpTags := dbTableDump(t, p.db, p.TagTableSuffix) + require.Len(t, dumpTags, 1) + assert.EqualValues(t, dump[0]["tag_id"], dumpTags[0]["tag_id"]) + assert.EqualValues(t, "foo", dumpTags[0]["tag"]) + + p.Logger.Clear() + require.NoError(t, p.Write(metrics)) + + stmtCount := 0 + for _, log := range p.Logger.Logs() { + if strings.Contains(log.String(), "info: PG ") { + stmtCount++ + } + } + assert.Equal(t, 3, stmtCount) // BEGIN, COPY metrics table, COMMIT +} + +// Verify that when using TagsAsForeignKeys and a tag can't be written, that we still add the metrics. +func TestWrite_tagError(t *testing.T) { + p := newPostgresqlTest(t) + p.TagsAsForeignKeys = true + require.NoError(t, p.Connect()) + + metrics := []telegraf.Metric{ + newMetric(t, "", MSS{"tag": "foo"}, MSI{"v": 1}), + } + require.NoError(t, p.Write(metrics)) + + // It'll have the table cached, so won't know we dropped it, will try insert, and get error. + _, err := p.db.Exec(ctx, "DROP TABLE \""+t.Name()+"_tag\"") + require.NoError(t, err) + + metrics = []telegraf.Metric{ + newMetric(t, "", MSS{"tag": "foo"}, MSI{"v": 2}), + } + require.NoError(t, p.Write(metrics)) + + dump := dbTableDump(t, p.db, "") + require.Len(t, dump, 2) + assert.EqualValues(t, 1, dump[0]["v"]) + assert.EqualValues(t, 2, dump[1]["v"]) +} + +// Verify that when using TagsAsForeignKeys and ForeignTagConstraing and a tag can't be written, that we drop the metrics. +func TestWrite_tagError_foreignConstraint(t *testing.T) { + p := newPostgresqlTest(t) + p.TagsAsForeignKeys = true + p.ForeignTagConstraint = true + require.NoError(t, p.Connect()) + + metrics := []telegraf.Metric{ + newMetric(t, "", MSS{"tag": "foo"}, MSI{"v": 1}), + } + require.NoError(t, p.Write(metrics)) + + // It'll have the table cached, so won't know we dropped it, will try insert, and get error. + _, err := p.db.Exec(ctx, "DROP TABLE \""+t.Name()+"_tag\"") + require.NoError(t, err) + + metrics = []telegraf.Metric{ + newMetric(t, "", MSS{"tag": "bar"}, MSI{"v": 2}), + } + assert.NoError(t, p.Write(metrics)) + haveError := false + for _, l := range p.Logger.Logs() { + if strings.Contains(l.String(), "write error") { + haveError = true + break + } + } + assert.True(t, haveError, "write error not found in log") + + dump := dbTableDump(t, p.db, "") + require.Len(t, dump, 1) + assert.EqualValues(t, 1, dump[0]["v"]) +} + +func TestWrite_UnsignedIntegers(t *testing.T) { + p := newPostgresqlTest(t) + p.UseUint8 = true + _ = p.Init() + if err := p.Connect(); err != nil { + if strings.Contains(err.Error(), "retreiving OID for uint8 data type") { + t.Skipf("pguint extension is not installed") + t.SkipNow() + } + require.NoError(t, err) + } + + metrics := []telegraf.Metric{ + newMetric(t, "", MSS{}, MSI{"v": uint64(math.MaxUint64)}), + } + require.NoError(t, p.Write(metrics)) + + dump := dbTableDump(t, p.db, "") + + if assert.Len(t, dump, 1) { + assert.EqualValues(t, uint64(math.MaxUint64), dump[0]["v"]) + } +} + +// Last ditch effort to find any concurrency issues. +func TestStressConcurrency(t *testing.T) { + metrics := []telegraf.Metric{ + newMetric(t, "", MSS{"foo": "bar"}, MSI{"a": 1}), + newMetric(t, "", MSS{"pop": "tart"}, MSI{"b": 1}), + newMetric(t, "", MSS{"foo": "bar", "pop": "tart"}, MSI{"a": 2, "b": 2}), + newMetric(t, "_b", MSS{"foo": "bar"}, MSI{"a": 1}), + } + + concurrency := 4 + loops := 100 + + pctl := newPostgresqlTest(t) + pctl.Logger.emitLevel = pgx.LogLevelWarn + require.NoError(t, pctl.Connect()) + + for i := 0; i < loops; i++ { + var wgStart, wgDone sync.WaitGroup + wgStart.Add(concurrency) + wgDone.Add(concurrency) + for j := 0; j < concurrency; j++ { + go func() { + mShuf := make([]telegraf.Metric, len(metrics)) + copy(mShuf, metrics) + rand.Shuffle(len(mShuf), func(a, b int) { mShuf[a], mShuf[b] = mShuf[b], mShuf[a] }) + + p := newPostgresqlTest(t) + p.TagsAsForeignKeys = true + p.Logger.emitLevel = pgx.LogLevelWarn + p.dbConfig.MaxConns = int32(rand.Intn(3) + 1) + require.NoError(t, p.Connect()) + wgStart.Done() + wgStart.Wait() + + err := p.Write(mShuf) + assert.NoError(t, err) + assert.NoError(t, p.Close()) + assert.False(t, p.Logger.HasLevel(pgx.LogLevelWarn)) + wgDone.Done() + }() + } + wgDone.Wait() + + if t.Failed() { + break + } + + for _, stmt := range []string{ + "DROP TABLE \"" + t.Name() + "_tag\"", + "DROP TABLE \"" + t.Name() + "\"", + "DROP TABLE \"" + t.Name() + "_b_tag\"", + "DROP TABLE \"" + t.Name() + "_b\"", + } { + _, err := pctl.db.Exec(ctx, stmt) + require.NoError(t, err) + } + } +} + +func TestReadme(t *testing.T) { + f, err := os.Open("README.md") + require.NoError(t, err) + buf := bytes.NewBuffer(nil) + _, _ = buf.ReadFrom(f) + _ = f.Close() + txt := strings.ReplaceAll(buf.String(), "\r", "") // windows files contain CR + assert.Contains(t, txt, (&Postgresql{}).SampleConfig(), "Readme is out of date with sample config") +} diff --git a/plugins/outputs/postgresql/sqltemplate/template.go b/plugins/outputs/postgresql/sqltemplate/template.go new file mode 100644 index 0000000000000..b4a8a699b72c4 --- /dev/null +++ b/plugins/outputs/postgresql/sqltemplate/template.go @@ -0,0 +1,416 @@ +/* + +Templates are used for creation of the SQL used when creating and modifying tables. These templates are specified within +the configuration as the parameters 'create_templates', 'add_column_templates, 'tag_table_create_templates', and +'tag_table_add_column_templates'. + +The templating functionality behaves the same in all cases. However the variables will differ. + + +Variables + +The following variables are available within all template executions: + + * table - A Table object referring to the current table being + created/modified. + + * columns - A Columns object of the new columns being added to the + table (all columns in the case of a new table, and new columns in the case + of existing table). + + * allColumns - A Columns object of all the columns (both old and new) + of the table. In the case of a new table, this is the same as `columns`. + + * metricTable - A Table object referring to the table containing the + fields. In the case of TagsAsForeignKeys and `table` is the tag table, then + `metricTable` is the table using this one for its tags. + + * tagTable - A Table object referring to the table containing the + tags. In the case of TagsAsForeignKeys and `table` is the metrics table, + then `tagTable` is the table containing the tags for it. + +Each object has helper methods that may be used within the template. See the documentation for the appropriate type. + +When the object is interpolated without a helper, it is automatically converted to a string through its String() method. + + +Functions + +All the functions provided by the Sprig library (http://masterminds.github.io/sprig/) are available within template executions. + +In addition, the following functions are also available: + + * quoteIdentifier - Quotes the input string as a Postgres identifier. + + * quoteLiteral - Quotes the input string as a Postgres literal. + + +Examples + +The default templates show basic usage. When left unconfigured, it is the equivalent of: + [outputs.postgresql] + create_templates = [ + '''CREATE TABLE {{.table}} ({{.columns}})''', + ] + add_column_templates = [ + '''ALTER TABLE {{.table}} ADD COLUMN IF NOT EXISTS {{.columns|join ", ADD COLUMN IF NOT EXISTS "}}''', + ] + tag_table_create_templates = [ + '''CREATE TABLE {{.table}} ({{.columns}}, PRIMARY KEY (tag_id))''' + ] + tag_table_add_column_templates = [ + '''ALTER TABLE {{.table}} ADD COLUMN IF NOT EXISTS {{.columns|join ", ADD COLUMN IF NOT EXISTS "}}''', + ] + +A simple example for usage with TimescaleDB would be: + [outputs.postgresql] + create_templates = [ + '''CREATE TABLE {{ .table }} ({{ .allColumns }})''', + '''SELECT create_hypertable({{ .table|quoteLiteral }}, 'time', chunk_time_interval => INTERVAL '1d')''', + '''ALTER TABLE {{ .table }} SET (timescaledb.compress, timescaledb.compress_segmentby = 'tag_id')''', + '''SELECT add_compression_policy({{ .table|quoteLiteral }}, INTERVAL '2h')''', + ] +...where the defaults for the other templates would be automatically applied. + +A very complex example for versions of TimescaleDB which don't support adding columns to compressed hypertables (v<2.1.0), using views and unions to emulate the functionality, would be: + [outputs.postgresql] + schema = "telegraf" + create_templates = [ + '''CREATE TABLE {{ .table }} ({{ .allColumns }})''', + '''SELECT create_hypertable({{ .table|quoteLiteral }}, 'time', chunk_time_interval => INTERVAL '1d')''', + '''ALTER TABLE {{ .table }} SET (timescaledb.compress, timescaledb.compress_segmentby = 'tag_id')''', + '''SELECT add_compression_policy({{ .table|quoteLiteral }}, INTERVAL '2d')''', + '''CREATE VIEW {{ .table.WithSuffix "_data" }} AS + SELECT {{ .allColumns.Selectors | join "," }} FROM {{ .table }}''', + '''CREATE VIEW {{ .table.WithSchema "public" }} AS + SELECT time, {{ (.tagTable.Columns.Tags.Concat .allColumns.Fields).Identifiers | join "," }} + FROM {{ .table.WithSuffix "_data" }} t, {{ .tagTable }} tt + WHERE t.tag_id = tt.tag_id''', + ] + add_column_templates = [ + '''ALTER TABLE {{ .table }} RENAME TO {{ (.table.WithSuffix "_" .table.Columns.Hash).WithSchema "" }}''', + '''ALTER VIEW {{ .table.WithSuffix "_data" }} RENAME TO {{ (.table.WithSuffix "_" .table.Columns.Hash "_data").WithSchema "" }}''', + '''DROP VIEW {{ .table.WithSchema "public" }}''', + + '''CREATE TABLE {{ .table }} ({{ .allColumns }})''', + '''SELECT create_hypertable({{ .table|quoteLiteral }}, 'time', chunk_time_interval => INTERVAL '1d')''', + '''ALTER TABLE {{ .table }} SET (timescaledb.compress, timescaledb.compress_segmentby = 'tag_id')''', + '''SELECT add_compression_policy({{ .table|quoteLiteral }}, INTERVAL '2d')''', + '''CREATE VIEW {{ .table.WithSuffix "_data" }} AS + SELECT {{ .allColumns.Selectors | join "," }} + FROM {{ .table }} + UNION ALL + SELECT {{ (.allColumns.Union .table.Columns).Selectors | join "," }} + FROM {{ .table.WithSuffix "_" .table.Columns.Hash "_data" }}''', + '''CREATE VIEW {{ .table.WithSchema "public" }} + AS SELECT time, {{ (.tagTable.Columns.Tags.Concat .allColumns.Fields).Identifiers | join "," }} + FROM {{ .table.WithSuffix "_data" }} t, {{ .tagTable }} tt + WHERE t.tag_id = tt.tag_id''', + ] +*/ +package sqltemplate + +import ( + "bytes" + "encoding/base32" + "fmt" + "hash/fnv" + "strings" + "text/template" + "unsafe" + + "github.com/influxdata/telegraf/plugins/outputs/postgresql/utils" + + "github.com/Masterminds/sprig" +) + +var templateFuncs = map[string]interface{}{ + "quoteIdentifier": QuoteIdentifier, + "quoteLiteral": QuoteLiteral, +} + +func asString(obj interface{}) string { + switch obj := obj.(type) { + case string: + return obj + case []byte: + return string(obj) + case fmt.Stringer: + return obj.String() + default: + return fmt.Sprintf("%v", obj) + } +} + +// QuoteIdentifier quotes the given string as a Postgres identifier (double-quotes the value). +// +// QuoteIdentifier is accessible within templates as 'quoteIdentifier'. +func QuoteIdentifier(name interface{}) string { + return utils.QuoteIdentifier(asString(name)) +} + +// QuoteLiteral quotes the given string as a Postgres literal (single-quotes the value). +// +// QuoteLiteral is accessible within templates as 'quoteLiteral'. +func QuoteLiteral(str interface{}) string { + return utils.QuoteLiteral(asString(str)) +} + +// Table is an object which represents a Postgres table. +type Table struct { + Schema string + Name string + Columns Columns +} + +func NewTable(schemaName, tableName string, columns []utils.Column) *Table { + if tableName == "" { + return nil + } + return &Table{ + Schema: schemaName, + Name: tableName, + Columns: NewColumns(columns), + } +} + +// String returns the table's fully qualified & quoted identifier (schema+table). +func (tbl *Table) String() string { + return tbl.Identifier() +} + +// Identifier returns the table's fully qualified & quoted identifier (schema+table). +// +// If schema is empty, it is omitted from the result. +func (tbl *Table) Identifier() string { + if tbl.Schema == "" { + return QuoteIdentifier(tbl.Name) + } + return QuoteIdentifier(tbl.Schema) + "." + QuoteIdentifier(tbl.Name) +} + +// WithSchema returns a copy of the Table object, but with the schema replaced by the given value. +func (tbl *Table) WithSchema(name string) *Table { + tblNew := &Table{} + *tblNew = *tbl + tblNew.Schema = name + return tblNew +} + +// WithName returns a copy of the Table object, but with the name replaced by the given value. +func (tbl *Table) WithName(name string) *Table { + tblNew := &Table{} + *tblNew = *tbl + tblNew.Name = name + return tblNew +} + +// WithSuffix returns a copy of the Table object, but with the name suffixed with the given value. +func (tbl *Table) WithSuffix(suffixes ...string) *Table { + tblNew := &Table{} + *tblNew = *tbl + tblNew.Name += strings.Join(suffixes, "") + return tblNew +} + +// A Column is an object which represents a Postgres column. +type Column utils.Column + +// String returns the column's definition (as used in a CREATE TABLE statement). E.G: +// "my_column" bigint +func (tc Column) String() string { + return tc.Definition() +} + +// Definition returns the column's definition (as used in a CREATE TABLE statement). E.G: +// "my_column" bigint +func (tc Column) Definition() string { + return tc.Identifier() + " " + tc.Type +} + +// Identifier returns the column's quoted identifier. +func (tc Column) Identifier() string { + return QuoteIdentifier(tc.Name) +} + +// Selector returns the selector for the column. For most cases this is the same as Identifier. However in some cases, such as a UNION, this may return a statement such as `NULL AS "foo"`. +func (tc Column) Selector() string { + if tc.Type != "" { + return tc.Identifier() + } + return "NULL AS " + tc.Identifier() +} + +// IsTag returns true if the column is a tag column. Otherwise false. +func (tc Column) IsTag() bool { + return tc.Role == utils.TagColType +} + +// IsField returns true if the column is a field column. Otherwise false. +func (tc Column) IsField() bool { + return tc.Role == utils.FieldColType +} + +// Columns represents an ordered list of Column objects, with convenience methods for operating on the +// list. +type Columns []Column + +func NewColumns(cols []utils.Column) Columns { + tcols := make(Columns, len(cols)) + for i, col := range cols { + tcols[i] = Column(col) + } + return tcols +} + +// List returns the Columns object as a slice of Column. +func (cols Columns) List() []Column { + return cols +} + +// Definitions returns the list of column definitions. +func (cols Columns) Definitions() []string { + defs := make([]string, len(cols)) + for i, tc := range cols { + defs[i] = tc.Definition() + } + return defs +} + +// Identifiers returns the list of quoted column identifiers. +func (cols Columns) Identifiers() []string { + idents := make([]string, len(cols)) + for i, tc := range cols { + idents[i] = tc.Identifier() + } + return idents +} + +// Selectors returns the list of column selectors. +func (cols Columns) Selectors() []string { + selectors := make([]string, len(cols)) + for i, tc := range cols { + selectors[i] = tc.Selector() + } + return selectors +} + +// String returns the comma delimited list of column identifiers. +func (cols Columns) String() string { + colStrs := make([]string, len(cols)) + for i, tc := range cols { + colStrs[i] = tc.String() + } + return strings.Join(colStrs, ", ") +} + +// Keys returns a Columns list of the columns which are not fields (e.g. time, tag_id, & tags). +func (cols Columns) Keys() Columns { + var newCols []Column + for _, tc := range cols { + if tc.Role != utils.FieldColType { + newCols = append(newCols, tc) + } + } + return newCols +} + +// Sorted returns a sorted copy of Columns. +// +// Columns are sorted so that they are in order as: [Time, Tags, Fields], with the columns within each group sorted +// alphabetically. +func (cols Columns) Sorted() Columns { + newCols := append([]Column{}, cols...) + (*utils.ColumnList)(unsafe.Pointer(&newCols)).Sort() + return newCols +} + +// Concat returns a copy of Columns with the given tcsList appended to the end. +func (cols Columns) Concat(tcsList ...Columns) Columns { + tcsNew := append(Columns{}, cols...) + for _, tcs := range tcsList { + tcsNew = append(tcsNew, tcs...) + } + return tcsNew +} + +// Union generates a list of SQL selectors against the given columns. +// +// For each column in tcs, if the column also exist in tcsFrom, it will be selected. If the column does not exist NULL will be selected. +func (cols Columns) Union(tcsFrom Columns) Columns { + tcsNew := append(Columns{}, cols...) +TCS: + for i, tc := range cols { + for _, tcFrom := range tcsFrom { + if tc.Name == tcFrom.Name { + continue TCS + } + } + tcsNew[i].Type = "" + } + return tcsNew +} + +// Tags returns a Columns list of the columns which are tags. +func (cols Columns) Tags() Columns { + var newCols []Column + for _, tc := range cols { + if tc.Role == utils.TagColType { + newCols = append(newCols, tc) + } + } + return newCols +} + +// Fields returns a Columns list of the columns which are fields. +func (cols Columns) Fields() Columns { + var newCols []Column + for _, tc := range cols { + if tc.Role == utils.FieldColType { + newCols = append(newCols, tc) + } + } + return newCols +} + +// Hash returns a hash of the column names. The hash is base-32 encoded string, up to 7 characters long with no padding. +// +// This can be useful as an identifier for supporting table renaming + unions in the case of non-modifiable tables. +func (cols Columns) Hash() string { + hash := fnv.New32a() + for _, tc := range cols.Sorted() { + _, _ = hash.Write([]byte(tc.Name)) + _, _ = hash.Write([]byte{0}) + } + return strings.ToLower(base32.StdEncoding.WithPadding(base32.NoPadding).EncodeToString(hash.Sum(nil))) +} + +type Template template.Template + +func (t *Template) UnmarshalText(text []byte) error { + tmpl := template.New("") + tmpl.Option("missingkey=error") + tmpl.Funcs(templateFuncs) + tmpl.Funcs(sprig.TxtFuncMap()) + tt, err := tmpl.Parse(string(text)) + if err != nil { + return err + } + *t = Template(*tt) + return nil +} + +func (t *Template) Render(table *Table, newColumns []utils.Column, metricTable *Table, tagTable *Table) ([]byte, error) { + tcs := NewColumns(newColumns).Sorted() + data := map[string]interface{}{ + "table": table, + "columns": tcs, + "allColumns": tcs.Concat(table.Columns).Sorted(), + "metricTable": metricTable, + "tagTable": tagTable, + } + + buf := bytes.NewBuffer(nil) + err := (*template.Template)(t).Execute(buf, data) + return buf.Bytes(), err +} diff --git a/plugins/outputs/postgresql/table_manager.go b/plugins/outputs/postgresql/table_manager.go new file mode 100644 index 0000000000000..8cf7e0704d375 --- /dev/null +++ b/plugins/outputs/postgresql/table_manager.go @@ -0,0 +1,400 @@ +package postgresql + +import ( + "context" + "fmt" + "strings" + "sync" + + "github.com/jackc/pgx/v4" + + "github.com/influxdata/telegraf/plugins/outputs/postgresql/sqltemplate" + "github.com/influxdata/telegraf/plugins/outputs/postgresql/utils" +) + +// This is an arbitrary constant value shared between multiple telegraf processes used for locking schema updates. +const schemaAdvisoryLockID int64 = 5705450890675909945 + +type tableState struct { + name string + columns map[string]utils.Column + sync.RWMutex +} + +type TableManager struct { + *Postgresql + + // map[tableName]map[columnName]utils.Column + tables map[string]*tableState + tablesMutex sync.Mutex +} + +// NewTableManager returns an instance of the tables.Manager interface +// that can handle checking and updating the state of tables in the PG database. +func NewTableManager(postgresql *Postgresql) *TableManager { + return &TableManager{ + Postgresql: postgresql, + tables: make(map[string]*tableState), + } +} + +// ClearTableCache clear the table structure cache. +func (tm *TableManager) ClearTableCache() { + tm.tablesMutex.Lock() + for _, tbl := range tm.tables { + tbl.Lock() + tbl.columns = nil + tbl.Unlock() + } + tm.tablesMutex.Unlock() + + if tm.tagsCache != nil { + tm.tagsCache.Clear() + } +} + +func (tm *TableManager) table(name string) *tableState { + tm.tablesMutex.Lock() + tbl := tm.tables[name] + if tbl == nil { + tbl = &tableState{name: name} + tm.tables[name] = tbl + } + tm.tablesMutex.Unlock() + return tbl +} + +// MatchSource scans through the metrics, determining what columns are needed for inserting, and ensuring the DB schema matches. +// +// If the schema does not match, and schema updates are disabled: +// If a field missing from the DB, the field is omitted. +// If a tag is missing from the DB, the metric is dropped. +func (tm *TableManager) MatchSource(ctx context.Context, db dbh, rowSource *TableSource) error { + metricTable := tm.table(rowSource.Name()) + var tagTable *tableState + if tm.TagsAsForeignKeys { + tagTable = tm.table(metricTable.name + tm.TagTableSuffix) + + missingCols, err := tm.EnsureStructure( + ctx, + db, + tagTable, + rowSource.TagTableColumns(), + tm.TagTableCreateTemplates, + tm.TagTableAddColumnTemplates, + metricTable, + tagTable, + ) + if err != nil { + if isTempError(err) { + return err + } + tm.Postgresql.Logger.Errorf("permanent error updating schema for %s: %w", tagTable.name, err) + } + + if len(missingCols) > 0 { + colDefs := make([]string, len(missingCols)) + for i, col := range missingCols { + if err := rowSource.DropColumn(col); err != nil { + return fmt.Errorf("metric/table mismatch: Unable to omit field/column from \"%s\": %w", tagTable.name, err) + } + colDefs[i] = col.Name + " " + col.Type + } + tm.Logger.Errorf("table '%s' is missing tag columns (dropping metrics): %s", + tagTable.name, + strings.Join(colDefs, ", ")) + } + } + + missingCols, err := tm.EnsureStructure( + ctx, + db, + metricTable, + rowSource.MetricTableColumns(), + tm.CreateTemplates, + tm.AddColumnTemplates, + metricTable, + tagTable, + ) + if err != nil { + if isTempError(err) { + return err + } + tm.Postgresql.Logger.Errorf("permanent error updating schema for %s: %w", metricTable.name, err) + } + + if len(missingCols) > 0 { + colDefs := make([]string, len(missingCols)) + for i, col := range missingCols { + if err := rowSource.DropColumn(col); err != nil { + return fmt.Errorf("metric/table mismatch: Unable to omit field/column from \"%s\": %w", metricTable.name, err) + } + colDefs[i] = col.Name + " " + col.Type + } + tm.Logger.Errorf("table '%s' is missing columns (omitting fields): %s", + metricTable.name, + strings.Join(colDefs, ", ")) + } + + return nil +} + +// EnsureStructure ensures that the table identified by tableName contains the provided columns. +// +// createTemplates and addColumnTemplates are the templates which are executed in the event of table create or alter +// (respectively). +// metricsTableName and tagsTableName are passed to the templates. +// +// If the table cannot be modified, the returned column list is the columns which are missing from the table. This +// includes when an error is returned. +//nolint:revive +func (tm *TableManager) EnsureStructure( + ctx context.Context, + db dbh, + tbl *tableState, + columns []utils.Column, + createTemplates []*sqltemplate.Template, + addColumnsTemplates []*sqltemplate.Template, + metricsTable *tableState, + tagsTable *tableState, +) ([]utils.Column, error) { + // Sort so that: + // * When we create/alter the table the columns are in a sane order (telegraf gives us the fields in random order) + // * When we display errors about missing columns, the order is also sane, and consistent + utils.ColumnList(columns).Sort() + + // rlock, read, runlock, wlock, read, read_db, wlock_db, read_db, write_db, wunlock_db, wunlock + + // rlock + tbl.RLock() + // read + currCols := tbl.columns + // runlock + tbl.RUnlock() + missingCols := diffMissingColumns(currCols, columns) + if len(missingCols) == 0 { + return nil, nil + } + + // wlock + // We also need to lock the other table as it may be referenced by a template. + // To prevent deadlock, the metric & tag table must always be locked in the same order: 1) Tag, 2) Metric + if tbl == tagsTable { + tagsTable.Lock() + defer tagsTable.Unlock() + + metricsTable.RLock() + defer metricsTable.RUnlock() + } else { + if tagsTable != nil { + tagsTable.RLock() + defer tagsTable.RUnlock() + } + + metricsTable.Lock() + defer metricsTable.Unlock() + } + + // read + currCols = tbl.columns + missingCols = diffMissingColumns(currCols, columns) + if len(missingCols) == 0 { + return nil, nil + } + + // read_db + var err error + if currCols, err = tm.getColumns(ctx, db, tbl.name); err != nil { + return nil, err + } + tbl.columns = currCols + missingCols = diffMissingColumns(currCols, columns) + if len(missingCols) == 0 { + tbl.columns = currCols + return nil, nil + } + + if len(currCols) == 0 && len(createTemplates) == 0 { + // can't create + return missingCols, nil + } + if len(currCols) != 0 && len(addColumnsTemplates) == 0 { + // can't add + return missingCols, nil + } + + // wlock_db + tx, err := db.Begin(ctx) + if err != nil { + return missingCols, err + } + defer tx.Rollback(ctx) //nolint:errcheck + // It's possible to have multiple telegraf processes, in which we can't ensure they all lock tables in the same + // order. So to prevent possible deadlocks, we have to have a single lock for all schema modifications. + if _, err := tx.Exec(ctx, "SELECT pg_advisory_xact_lock($1)", schemaAdvisoryLockID); err != nil { + return missingCols, err + } + + // read_db + if currCols, err = tm.getColumns(ctx, tx, tbl.name); err != nil { + return nil, err + } + tbl.columns = currCols + if currCols != nil { + missingCols = diffMissingColumns(currCols, columns) + if len(missingCols) == 0 { + return nil, nil + } + } + + // write_db + var tmpls []*sqltemplate.Template + if len(currCols) == 0 { + tmpls = createTemplates + } else { + tmpls = addColumnsTemplates + } + if err := tm.update(ctx, tx, tbl, tmpls, missingCols, metricsTable, tagsTable); err != nil { + return missingCols, err + } + + if currCols, err = tm.getColumns(ctx, tx, tbl.name); err != nil { + return missingCols, err + } + + if err := tx.Commit(ctx); err != nil { + return missingCols, err + } + + tbl.columns = currCols + + // wunlock_db (deferred) + // wunlock (deferred) + + return nil, nil +} + +func (tm *TableManager) getColumns(ctx context.Context, db dbh, name string) (map[string]utils.Column, error) { + rows, err := db.Query(ctx, ` + SELECT + column_name, + CASE WHEN data_type='USER-DEFINED' THEN udt_name ELSE data_type END, + col_description(format('%I.%I', table_schema, table_name)::regclass::oid, ordinal_position) + FROM information_schema.columns + WHERE table_schema = $1 and table_name = $2`, tm.Schema, name) + if err != nil { + return nil, err + } + defer rows.Close() + + cols := make(map[string]utils.Column) + for rows.Next() { + var colName, colType string + desc := new(string) + err := rows.Scan(&colName, &colType, &desc) + if err != nil { + return nil, err + } + + role := utils.FieldColType + switch colName { + case timeColumnName: + role = utils.TimeColType + case tagIDColumnName: + role = utils.TagsIDColType + case tagsJSONColumnName: + role = utils.TagColType + case fieldsJSONColumnName: + role = utils.FieldColType + default: + // We don't want to monopolize the column comment (preventing user from storing other information there), so just look at the first word + if desc != nil { + descWords := strings.Split(*desc, " ") + if descWords[0] == "tag" { + role = utils.TagColType + } + } + } + + cols[colName] = utils.Column{ + Name: colName, + Type: colType, + Role: role, + } + } + + return cols, rows.Err() +} + +//nolint:revive +func (tm *TableManager) update(ctx context.Context, + tx pgx.Tx, + state *tableState, + tmpls []*sqltemplate.Template, + missingCols []utils.Column, + metricsTable *tableState, + tagsTable *tableState, +) error { + tmplTable := sqltemplate.NewTable(tm.Schema, state.name, colMapToSlice(state.columns)) + metricsTmplTable := sqltemplate.NewTable(tm.Schema, metricsTable.name, colMapToSlice(metricsTable.columns)) + var tagsTmplTable *sqltemplate.Table + if tagsTable != nil { + tagsTmplTable = sqltemplate.NewTable(tm.Schema, tagsTable.name, colMapToSlice(tagsTable.columns)) + } else { + tagsTmplTable = sqltemplate.NewTable("", "", nil) + } + + for _, tmpl := range tmpls { + sql, err := tmpl.Render(tmplTable, missingCols, metricsTmplTable, tagsTmplTable) + if err != nil { + return err + } + if _, err := tx.Exec(ctx, string(sql)); err != nil { + return fmt.Errorf("executing `%s`: %w", sql, err) + } + } + + // We need to be able to determine the role of the column when reading the structure back (because of the templates). + // For some columns we can determine this by the column name (time, tag_id, etc). However tags and fields can have any + // name, and look the same. So we add a comment to tag columns, and through process of elimination what remains are + // field columns. + for _, col := range missingCols { + if col.Role != utils.TagColType { + continue + } + stmt := fmt.Sprintf("COMMENT ON COLUMN %s.%s IS 'tag'", + tmplTable.String(), sqltemplate.QuoteIdentifier(col.Name)) + if _, err := tx.Exec(ctx, stmt); err != nil { + return fmt.Errorf("setting column role comment: %s", err) + } + } + + return nil +} + +// diffMissingColumns filters srcColumns to the ones not present in dbColumns. +func diffMissingColumns(dbColumns map[string]utils.Column, srcColumns []utils.Column) []utils.Column { + if len(dbColumns) == 0 { + return srcColumns + } + + var missingColumns []utils.Column + for _, srcCol := range srcColumns { + if _, ok := dbColumns[srcCol.Name]; !ok { + missingColumns = append(missingColumns, srcCol) + continue + } + } + return missingColumns +} + +func colMapToSlice(colMap map[string]utils.Column) []utils.Column { + if colMap == nil { + return nil + } + cols := make([]utils.Column, 0, len(colMap)) + for _, col := range colMap { + cols = append(cols, col) + } + return cols +} diff --git a/plugins/outputs/postgresql/table_manager_test.go b/plugins/outputs/postgresql/table_manager_test.go new file mode 100644 index 0000000000000..82fd830338891 --- /dev/null +++ b/plugins/outputs/postgresql/table_manager_test.go @@ -0,0 +1,339 @@ +package postgresql + +import ( + "strings" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/influxdata/telegraf" + "github.com/influxdata/telegraf/plugins/outputs/postgresql/sqltemplate" + "github.com/influxdata/telegraf/plugins/outputs/postgresql/utils" +) + +func TestTableManager_EnsureStructure(t *testing.T) { + p := newPostgresqlTest(t) + require.NoError(t, p.Connect()) + + cols := []utils.Column{ + p.columnFromTag("foo", ""), + p.columnFromField("baz", 0), + } + missingCols, err := p.tableManager.EnsureStructure( + ctx, + p.db, + p.tableManager.table(t.Name()), + cols, + p.CreateTemplates, + p.AddColumnTemplates, + p.tableManager.table(t.Name()), + nil, + ) + require.NoError(t, err) + assert.Empty(t, missingCols) + + tblCols := p.tableManager.table(t.Name()).columns + assert.EqualValues(t, cols[0], tblCols["foo"]) + assert.EqualValues(t, cols[1], tblCols["baz"]) +} + +func TestTableManager_EnsureStructure_alter(t *testing.T) { + p := newPostgresqlTest(t) + require.NoError(t, p.Connect()) + + cols := []utils.Column{ + p.columnFromTag("foo", ""), + p.columnFromField("bar", 0), + } + _, err := p.tableManager.EnsureStructure( + ctx, + p.db, + p.tableManager.table(t.Name()), + cols, + p.CreateTemplates, + p.AddColumnTemplates, + p.tableManager.table(t.Name()), + nil, + ) + require.NoError(t, err) + + cols = append(cols, p.columnFromField("baz", 0)) + missingCols, err := p.tableManager.EnsureStructure( + ctx, + p.db, + p.tableManager.table(t.Name()), + cols, + p.CreateTemplates, + p.AddColumnTemplates, + p.tableManager.table(t.Name()), + nil, + ) + require.NoError(t, err) + assert.Empty(t, missingCols) + + tblCols := p.tableManager.table(t.Name()).columns + assert.EqualValues(t, cols[0], tblCols["foo"]) + assert.EqualValues(t, cols[1], tblCols["bar"]) + assert.EqualValues(t, cols[2], tblCols["baz"]) +} + +func TestTableManager_getColumns(t *testing.T) { + p := newPostgresqlTest(t) + require.NoError(t, p.Connect()) + + cols := []utils.Column{ + p.columnFromTag("foo", ""), + p.columnFromField("baz", 0), + } + _, err := p.tableManager.EnsureStructure( + ctx, + p.db, + p.tableManager.table(t.Name()), + cols, + p.CreateTemplates, + p.AddColumnTemplates, + p.tableManager.table(t.Name()), + nil, + ) + require.NoError(t, err) + + p.tableManager.ClearTableCache() + require.Empty(t, p.tableManager.table(t.Name()).columns) + + curCols, err := p.tableManager.getColumns(ctx, p.db, t.Name()) + require.NoError(t, err) + + assert.EqualValues(t, cols[0], curCols["foo"]) + assert.EqualValues(t, cols[1], curCols["baz"]) +} + +func TestTableManager_MatchSource(t *testing.T) { + p := newPostgresqlTest(t) + p.TagsAsForeignKeys = true + require.NoError(t, p.Connect()) + + metrics := []telegraf.Metric{ + newMetric(t, "", MSS{"tag": "foo"}, MSI{"a": 1}), + } + tsrc := NewTableSources(p.Postgresql, metrics)[t.Name()] + + require.NoError(t, p.tableManager.MatchSource(ctx, p.db, tsrc)) + assert.Contains(t, p.tableManager.table(t.Name()+p.TagTableSuffix).columns, "tag") + assert.Contains(t, p.tableManager.table(t.Name()).columns, "a") +} + +func TestTableManager_MatchSource_UnsignedIntegers(t *testing.T) { + p := newPostgresqlTest(t) + p.UseUint8 = true + _ = p.Init() + if err := p.Connect(); err != nil { + if strings.Contains(err.Error(), "retreiving OID for uint8 data type") { + t.Skipf("pguint extension is not installed") + t.SkipNow() + } + require.NoError(t, err) + } + + metrics := []telegraf.Metric{ + newMetric(t, "", nil, MSI{"a": uint64(1)}), + } + tsrc := NewTableSources(p.Postgresql, metrics)[t.Name()] + + require.NoError(t, p.tableManager.MatchSource(ctx, p.db, tsrc)) + assert.Equal(t, PgUint8, p.tableManager.table(t.Name()).columns["a"].Type) +} + +func TestTableManager_noCreateTable(t *testing.T) { + p := newPostgresqlTest(t) + p.CreateTemplates = nil + require.NoError(t, p.Connect()) + + metrics := []telegraf.Metric{ + newMetric(t, "", MSS{"tag": "foo"}, MSI{"a": 1}), + } + tsrc := NewTableSources(p.Postgresql, metrics)[t.Name()] + + require.Error(t, p.tableManager.MatchSource(ctx, p.db, tsrc)) +} + +func TestTableManager_noCreateTagTable(t *testing.T) { + p := newPostgresqlTest(t) + p.TagTableCreateTemplates = nil + p.TagsAsForeignKeys = true + require.NoError(t, p.Connect()) + + metrics := []telegraf.Metric{ + newMetric(t, "", MSS{"tag": "foo"}, MSI{"a": 1}), + } + tsrc := NewTableSources(p.Postgresql, metrics)[t.Name()] + + require.Error(t, p.tableManager.MatchSource(ctx, p.db, tsrc)) +} + +// verify that TableManager updates & caches the DB table structure unless the incoming metric can't fit. +func TestTableManager_cache(t *testing.T) { + p := newPostgresqlTest(t) + p.TagsAsForeignKeys = true + require.NoError(t, p.Connect()) + + metrics := []telegraf.Metric{ + newMetric(t, "", MSS{"tag": "foo"}, MSI{"a": 1}), + } + tsrc := NewTableSources(p.Postgresql, metrics)[t.Name()] + + require.NoError(t, p.tableManager.MatchSource(ctx, p.db, tsrc)) +} + +// Verify that when alter statements are disabled and a metric comes in with a new tag key, that the tag is omitted. +func TestTableManager_noAlterMissingTag(t *testing.T) { + p := newPostgresqlTest(t) + p.AddColumnTemplates = []*sqltemplate.Template{} + require.NoError(t, p.Connect()) + + metrics := []telegraf.Metric{ + newMetric(t, "", MSS{"tag": "foo"}, MSI{"a": 1}), + } + tsrc := NewTableSources(p.Postgresql, metrics)[t.Name()] + require.NoError(t, p.tableManager.MatchSource(ctx, p.db, tsrc)) + + metrics = []telegraf.Metric{ + newMetric(t, "", MSS{"tag": "foo"}, MSI{"a": 2}), + newMetric(t, "", MSS{"tag": "foo", "bar": "baz"}, MSI{"a": 3}), + } + tsrc = NewTableSources(p.Postgresql, metrics)[t.Name()] + require.NoError(t, p.tableManager.MatchSource(ctx, p.db, tsrc)) + assert.NotContains(t, tsrc.ColumnNames(), "bar") +} + +// Verify that when using foreign tags and alter statements are disabled and a metric comes in with a new tag key, that +// the tag is omitted. +func TestTableManager_noAlterMissingTagTableTag(t *testing.T) { + p := newPostgresqlTest(t) + p.TagsAsForeignKeys = true + p.TagTableAddColumnTemplates = []*sqltemplate.Template{} + require.NoError(t, p.Connect()) + + metrics := []telegraf.Metric{ + newMetric(t, "", MSS{"tag": "foo"}, MSI{"a": 1}), + } + tsrc := NewTableSources(p.Postgresql, metrics)[t.Name()] + require.NoError(t, p.tableManager.MatchSource(ctx, p.db, tsrc)) + + metrics = []telegraf.Metric{ + newMetric(t, "", MSS{"tag": "foo"}, MSI{"a": 2}), + newMetric(t, "", MSS{"tag": "foo", "bar": "baz"}, MSI{"a": 3}), + } + tsrc = NewTableSources(p.Postgresql, metrics)[t.Name()] + ttsrc := NewTagTableSource(tsrc) + require.NoError(t, p.tableManager.MatchSource(ctx, p.db, tsrc)) + assert.NotContains(t, ttsrc.ColumnNames(), "bar") +} + +// Verify that when using foreign tags and alter statements generate a permanent error and a metric comes in with a new +// tag key, that the tag is omitted. +func TestTableManager_badAlterTagTable(t *testing.T) { + p := newPostgresqlTest(t) + p.TagsAsForeignKeys = true + tmpl := &sqltemplate.Template{} + _ = tmpl.UnmarshalText([]byte("bad")) + p.TagTableAddColumnTemplates = []*sqltemplate.Template{tmpl} + require.NoError(t, p.Connect()) + + metrics := []telegraf.Metric{ + newMetric(t, "", MSS{"tag": "foo"}, MSI{"a": 1}), + } + tsrc := NewTableSources(p.Postgresql, metrics)[t.Name()] + require.NoError(t, p.tableManager.MatchSource(ctx, p.db, tsrc)) + + metrics = []telegraf.Metric{ + newMetric(t, "", MSS{"tag": "foo"}, MSI{"a": 2}), + newMetric(t, "", MSS{"tag": "foo", "bar": "baz"}, MSI{"a": 3}), + } + tsrc = NewTableSources(p.Postgresql, metrics)[t.Name()] + ttsrc := NewTagTableSource(tsrc) + require.NoError(t, p.tableManager.MatchSource(ctx, p.db, tsrc)) + assert.NotContains(t, ttsrc.ColumnNames(), "bar") +} + +// verify that when alter statements are disabled and a metric comes in with a new field key, that the field is omitted. +func TestTableManager_noAlterMissingField(t *testing.T) { + p := newPostgresqlTest(t) + p.AddColumnTemplates = []*sqltemplate.Template{} + require.NoError(t, p.Connect()) + + metrics := []telegraf.Metric{ + newMetric(t, "", MSS{"tag": "foo"}, MSI{"a": 1}), + } + tsrc := NewTableSources(p.Postgresql, metrics)[t.Name()] + require.NoError(t, p.tableManager.MatchSource(ctx, p.db, tsrc)) + + metrics = []telegraf.Metric{ + newMetric(t, "", MSS{"tag": "foo"}, MSI{"a": 2}), + newMetric(t, "", MSS{"tag": "foo"}, MSI{"a": 3, "b": 3}), + } + tsrc = NewTableSources(p.Postgresql, metrics)[t.Name()] + require.NoError(t, p.tableManager.MatchSource(ctx, p.db, tsrc)) + assert.NotContains(t, tsrc.ColumnNames(), "b") +} + +// verify that when alter statements generate a permanent error and a metric comes in with a new field key, that the field is omitted. +func TestTableManager_badAlterField(t *testing.T) { + p := newPostgresqlTest(t) + tmpl := &sqltemplate.Template{} + _ = tmpl.UnmarshalText([]byte("bad")) + p.AddColumnTemplates = []*sqltemplate.Template{tmpl} + require.NoError(t, p.Connect()) + + metrics := []telegraf.Metric{ + newMetric(t, "", MSS{"tag": "foo"}, MSI{"a": 1}), + } + tsrc := NewTableSources(p.Postgresql, metrics)[t.Name()] + require.NoError(t, p.tableManager.MatchSource(ctx, p.db, tsrc)) + + metrics = []telegraf.Metric{ + newMetric(t, "", MSS{"tag": "foo"}, MSI{"a": 2}), + newMetric(t, "", MSS{"tag": "foo"}, MSI{"a": 3, "b": 3}), + } + tsrc = NewTableSources(p.Postgresql, metrics)[t.Name()] + require.NoError(t, p.tableManager.MatchSource(ctx, p.db, tsrc)) + assert.NotContains(t, tsrc.ColumnNames(), "b") +} + +func TestTableManager_addColumnTemplates(t *testing.T) { + p := newPostgresqlTest(t) + p.TagsAsForeignKeys = true + require.NoError(t, p.Connect()) + + metrics := []telegraf.Metric{ + newMetric(t, "", MSS{"foo": "bar"}, MSI{"a": 1}), + } + tsrc := NewTableSources(p.Postgresql, metrics)[t.Name()] + require.NoError(t, p.tableManager.MatchSource(ctx, p.db, tsrc)) + + p = newPostgresqlTest(t) + p.TagsAsForeignKeys = true + tmpl := &sqltemplate.Template{} + require.NoError(t, tmpl.UnmarshalText([]byte(`-- addColumnTemplate: {{ . }}`))) + p.AddColumnTemplates = append(p.AddColumnTemplates, tmpl) + require.NoError(t, p.Connect()) + + metrics = []telegraf.Metric{ + newMetric(t, "", MSS{"pop": "tart"}, MSI{"a": 1, "b": 2}), + } + tsrc = NewTableSources(p.Postgresql, metrics)[t.Name()] + require.NoError(t, p.tableManager.MatchSource(ctx, p.db, tsrc)) + p.Logger.Info("ok") + var log string + for _, l := range p.Logger.Logs() { + if strings.Contains(l.String(), "-- addColumnTemplate") { + log = l.String() + break + } + } + assert.Contains(t, log, `table:"public"."TestTableManager_addColumnTemplates"`) + assert.Contains(t, log, `columns:"b" bigint`) + assert.Contains(t, log, `allColumns:"time" timestamp without time zone, "tag_id" bigint, "a" bigint, "b" bigint`) + assert.Contains(t, log, `metricTable:"public"."TestTableManager_addColumnTemplates"`) + assert.Contains(t, log, `tagTable:"public"."TestTableManager_addColumnTemplates_tag"`) +} diff --git a/plugins/outputs/postgresql/table_source.go b/plugins/outputs/postgresql/table_source.go new file mode 100644 index 0000000000000..351cdaf2fe446 --- /dev/null +++ b/plugins/outputs/postgresql/table_source.go @@ -0,0 +1,436 @@ +package postgresql + +import ( + "fmt" + "hash/fnv" + + "github.com/influxdata/telegraf" + "github.com/influxdata/telegraf/plugins/outputs/postgresql/utils" +) + +type columnList struct { + columns []utils.Column + indices map[string]int +} + +func newColumnList() *columnList { + return &columnList{ + indices: map[string]int{}, + } +} + +func (cl *columnList) Add(column utils.Column) bool { + if _, ok := cl.indices[column.Name]; ok { + return false + } + cl.columns = append(cl.columns, column) + cl.indices[column.Name] = len(cl.columns) - 1 + return true +} + +func (cl *columnList) Remove(name string) bool { + idx, ok := cl.indices[name] + if !ok { + return false + } + cl.columns = append(cl.columns[:idx], cl.columns[idx+1:]...) + delete(cl.indices, name) + + for i, col := range cl.columns[idx:] { + cl.indices[col.Name] = idx + i + } + + return true +} + +// TableSource satisfies pgx.CopyFromSource +type TableSource struct { + postgresql *Postgresql + metrics []telegraf.Metric + cursor int + cursorValues []interface{} + cursorError error + // tagHashSalt is so that we can use a global tag cache for all tables. The salt is unique per table, and combined + // with the tag ID when looked up in the cache. + tagHashSalt int64 + + tagColumns *columnList + // tagSets is the list of tag IDs to tag values in use within the TableSource. The position of each value in the list + // corresponds to the key name in the tagColumns list. + // This data is used to build out the foreign tag table when enabled. + tagSets map[int64][]*telegraf.Tag + + fieldColumns *columnList + + droppedTagColumns []string +} + +func NewTableSources(p *Postgresql, metrics []telegraf.Metric) map[string]*TableSource { + tableSources := map[string]*TableSource{} + + for _, m := range metrics { + tsrc := tableSources[m.Name()] + if tsrc == nil { + tsrc = NewTableSource(p, m.Name()) + tableSources[m.Name()] = tsrc + } + tsrc.AddMetric(m) + } + + return tableSources +} + +func NewTableSource(postgresql *Postgresql, name string) *TableSource { + h := fnv.New64a() + _, _ = h.Write([]byte(name)) + + tsrc := &TableSource{ + postgresql: postgresql, + cursor: -1, + tagSets: make(map[int64][]*telegraf.Tag), + tagHashSalt: int64(h.Sum64()), + } + if !postgresql.TagsAsJsonb { + tsrc.tagColumns = newColumnList() + } + if !postgresql.FieldsAsJsonb { + tsrc.fieldColumns = newColumnList() + } + return tsrc +} + +func (tsrc *TableSource) AddMetric(metric telegraf.Metric) { + if tsrc.postgresql.TagsAsForeignKeys { + tagID := utils.GetTagID(metric) + if _, ok := tsrc.tagSets[tagID]; !ok { + tsrc.tagSets[tagID] = metric.TagList() + } + } + + if !tsrc.postgresql.TagsAsJsonb { + for _, t := range metric.TagList() { + tsrc.tagColumns.Add(tsrc.postgresql.columnFromTag(t.Key, t.Value)) + } + } + + if !tsrc.postgresql.FieldsAsJsonb { + for _, f := range metric.FieldList() { + tsrc.fieldColumns.Add(tsrc.postgresql.columnFromField(f.Key, f.Value)) + } + } + + tsrc.metrics = append(tsrc.metrics, metric) +} + +func (tsrc *TableSource) Name() string { + if len(tsrc.metrics) == 0 { + return "" + } + return tsrc.metrics[0].Name() +} + +// Returns the superset of all tags of all metrics. +func (tsrc *TableSource) TagColumns() []utils.Column { + var cols []utils.Column + + if tsrc.postgresql.TagsAsJsonb { + cols = append(cols, tagsJSONColumn) + } else { + cols = append(cols, tsrc.tagColumns.columns...) + } + + return cols +} + +// Returns the superset of all fields of all metrics. +func (tsrc *TableSource) FieldColumns() []utils.Column { + return tsrc.fieldColumns.columns +} + +// Returns the full column list, including time, tag id or tags, and fields. +func (tsrc *TableSource) MetricTableColumns() []utils.Column { + cols := []utils.Column{ + timeColumn, + } + + if tsrc.postgresql.TagsAsForeignKeys { + cols = append(cols, tagIDColumn) + } else { + cols = append(cols, tsrc.TagColumns()...) + } + + if tsrc.postgresql.FieldsAsJsonb { + cols = append(cols, fieldsJSONColumn) + } else { + cols = append(cols, tsrc.FieldColumns()...) + } + + return cols +} + +func (tsrc *TableSource) TagTableColumns() []utils.Column { + cols := []utils.Column{ + tagIDColumn, + } + + cols = append(cols, tsrc.TagColumns()...) + + return cols +} + +func (tsrc *TableSource) ColumnNames() []string { + cols := tsrc.MetricTableColumns() + names := make([]string, len(cols)) + for i, col := range cols { + names[i] = col.Name + } + return names +} + +// Drops the specified column. +// If column is a tag column, any metrics containing the tag will be skipped. +// If column is a field column, any metrics containing the field will have it omitted. +func (tsrc *TableSource) DropColumn(col utils.Column) error { + switch col.Role { + case utils.TagColType: + return tsrc.dropTagColumn(col) + case utils.FieldColType: + return tsrc.dropFieldColumn(col) + case utils.TimeColType, utils.TagsIDColType: + return fmt.Errorf("critical column \"%s\"", col.Name) + default: + return fmt.Errorf("internal error: unknown column \"%s\"", col.Name) + } +} + +// Drops the tag column from conversion. Any metrics containing this tag will be skipped. +func (tsrc *TableSource) dropTagColumn(col utils.Column) error { + if col.Role != utils.TagColType || tsrc.postgresql.TagsAsJsonb { + return fmt.Errorf("internal error: Tried to perform an invalid tag drop. measurement=%s tag=%s", tsrc.Name(), col.Name) + } + tsrc.droppedTagColumns = append(tsrc.droppedTagColumns, col.Name) + + if !tsrc.tagColumns.Remove(col.Name) { + return nil + } + + for setID, set := range tsrc.tagSets { + for _, tag := range set { + if tag.Key == col.Name { + // The tag is defined, so drop the whole set + delete(tsrc.tagSets, setID) + break + } + } + } + return nil +} + +// Drops the field column from conversion. Any metrics containing this field will have the field omitted. +func (tsrc *TableSource) dropFieldColumn(col utils.Column) error { + if col.Role != utils.FieldColType || tsrc.postgresql.FieldsAsJsonb { + return fmt.Errorf("internal error: Tried to perform an invalid field drop. measurement=%s field=%s", tsrc.Name(), col.Name) + } + + tsrc.fieldColumns.Remove(col.Name) + return nil +} + +func (tsrc *TableSource) Next() bool { + for { + if tsrc.cursor+1 >= len(tsrc.metrics) { + tsrc.cursorValues = nil + tsrc.cursorError = nil + return false + } + tsrc.cursor++ + + tsrc.cursorValues, tsrc.cursorError = tsrc.getValues() + if tsrc.cursorValues != nil || tsrc.cursorError != nil { + return true + } + } +} + +func (tsrc *TableSource) Reset() { + tsrc.cursor = -1 +} + +// getValues calculates the values for the metric at the cursor position. +// If the metric cannot be emitted, such as due to dropped tags, or all fields dropped, the return value is nil. +func (tsrc *TableSource) getValues() ([]interface{}, error) { + metric := tsrc.metrics[tsrc.cursor] + + values := []interface{}{ + metric.Time().UTC(), + } + + if !tsrc.postgresql.TagsAsForeignKeys { + if !tsrc.postgresql.TagsAsJsonb { + // tags_as_foreignkey=false, tags_as_json=false + tagValues := make([]interface{}, len(tsrc.tagColumns.columns)) + for _, tag := range metric.TagList() { + tagPos, ok := tsrc.tagColumns.indices[tag.Key] + if !ok { + // tag has been dropped, we can't emit or we risk collision with another metric + return nil, nil + } + tagValues[tagPos] = tag.Value + } + values = append(values, tagValues...) + } else { + // tags_as_foreign_key=false, tags_as_json=true + values = append(values, utils.TagListToJSON(metric.TagList())) + } + } else { + // tags_as_foreignkey=true + tagID := utils.GetTagID(metric) + if tsrc.postgresql.ForeignTagConstraint { + if _, ok := tsrc.tagSets[tagID]; !ok { + // tag has been dropped + return nil, nil + } + } + values = append(values, tagID) + } + + if !tsrc.postgresql.FieldsAsJsonb { + // fields_as_json=false + fieldValues := make([]interface{}, len(tsrc.fieldColumns.columns)) + fieldsEmpty := true + for _, field := range metric.FieldList() { + // we might have dropped the field due to the table missing the column & schema updates being turned off + if fPos, ok := tsrc.fieldColumns.indices[field.Key]; ok { + fieldValues[fPos] = field.Value + fieldsEmpty = false + } + } + if fieldsEmpty { + // all fields have been dropped. Don't emit a metric with just tags and no fields. + return nil, nil + } + values = append(values, fieldValues...) + } else { + // fields_as_json=true + value, err := utils.FieldListToJSON(metric.FieldList()) + if err != nil { + return nil, err + } + values = append(values, value) + } + + return values, nil +} + +func (tsrc *TableSource) Values() ([]interface{}, error) { + return tsrc.cursorValues, tsrc.cursorError +} + +func (tsrc *TableSource) Err() error { + return nil +} + +type TagTableSource struct { + *TableSource + tagIDs []int64 + + cursor int + cursorValues []interface{} + cursorError error +} + +func NewTagTableSource(tsrc *TableSource) *TagTableSource { + ttsrc := &TagTableSource{ + TableSource: tsrc, + cursor: -1, + } + + ttsrc.tagIDs = make([]int64, 0, len(tsrc.tagSets)) + for tagID := range tsrc.tagSets { + ttsrc.tagIDs = append(ttsrc.tagIDs, tagID) + } + + return ttsrc +} + +func (ttsrc *TagTableSource) Name() string { + return ttsrc.TableSource.Name() + ttsrc.postgresql.TagTableSuffix +} + +func (ttsrc *TagTableSource) cacheCheck(tagID int64) bool { + // Adding the 2 hashes is good enough. It's not a perfect solution, but given that we're operating in an int64 + // space, the risk of collision is extremely small. + key := ttsrc.tagHashSalt + tagID + _, err := ttsrc.postgresql.tagsCache.GetInt(key) + return err == nil +} +func (ttsrc *TagTableSource) cacheTouch(tagID int64) { + key := ttsrc.tagHashSalt + tagID + _ = ttsrc.postgresql.tagsCache.SetInt(key, nil, 0) +} + +func (ttsrc *TagTableSource) ColumnNames() []string { + cols := ttsrc.TagTableColumns() + names := make([]string, len(cols)) + for i, col := range cols { + names[i] = col.Name + } + return names +} + +func (ttsrc *TagTableSource) Next() bool { + for { + if ttsrc.cursor+1 >= len(ttsrc.tagIDs) { + ttsrc.cursorValues = nil + return false + } + ttsrc.cursor++ + + if ttsrc.cacheCheck(ttsrc.tagIDs[ttsrc.cursor]) { + // tag ID already inserted + continue + } + + ttsrc.cursorValues = ttsrc.getValues() + if ttsrc.cursorValues != nil { + return true + } + } +} + +func (ttsrc *TagTableSource) Reset() { + ttsrc.cursor = -1 +} + +func (ttsrc *TagTableSource) getValues() []interface{} { + tagID := ttsrc.tagIDs[ttsrc.cursor] + tagSet := ttsrc.tagSets[tagID] + + var values []interface{} + if !ttsrc.postgresql.TagsAsJsonb { + values = make([]interface{}, len(ttsrc.TableSource.tagColumns.indices)+1) + for _, tag := range tagSet { + values[ttsrc.TableSource.tagColumns.indices[tag.Key]+1] = tag.Value // +1 to account for tag_id column + } + } else { + values = make([]interface{}, 2) + values[1] = utils.TagListToJSON(tagSet) + } + values[0] = tagID + + return values +} + +func (ttsrc *TagTableSource) Values() ([]interface{}, error) { + return ttsrc.cursorValues, ttsrc.cursorError +} + +func (ttsrc *TagTableSource) UpdateCache() { + for _, tagID := range ttsrc.tagIDs { + ttsrc.cacheTouch(tagID) + } +} + +func (ttsrc *TagTableSource) Err() error { + return nil +} diff --git a/plugins/outputs/postgresql/table_source_test.go b/plugins/outputs/postgresql/table_source_test.go new file mode 100644 index 0000000000000..03f2712ce0075 --- /dev/null +++ b/plugins/outputs/postgresql/table_source_test.go @@ -0,0 +1,277 @@ +package postgresql + +import ( + "encoding/json" + "testing" + "time" + + "github.com/coocood/freecache" + "github.com/jackc/pgx/v4" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/influxdata/telegraf" + "github.com/influxdata/telegraf/plugins/outputs/postgresql/utils" +) + +func TestTableSource(t *testing.T) { +} + +type source interface { + pgx.CopyFromSource + ColumnNames() []string +} + +func nextSrcRow(src source) MSI { + if !src.Next() { + return nil + } + row := MSI{} + vals, err := src.Values() + if err != nil { + panic(err) + } + for i, name := range src.ColumnNames() { + row[name] = vals[i] + } + return row +} + +func TestTableSource_tagJSONB(t *testing.T) { + p := newPostgresqlTest(t) + p.TagsAsJsonb = true + + metrics := []telegraf.Metric{ + newMetric(t, "", MSS{"a": "one", "b": "two"}, MSI{"v": 1}), + } + + tsrc := NewTableSources(p.Postgresql, metrics)[t.Name()] + row := nextSrcRow(tsrc) + require.NoError(t, tsrc.Err()) + + assert.IsType(t, time.Time{}, row["time"]) + var tags MSI + require.NoError(t, json.Unmarshal(row["tags"].([]byte), &tags)) + assert.EqualValues(t, MSI{"a": "one", "b": "two"}, tags) + assert.EqualValues(t, 1, row["v"]) +} + +func TestTableSource_tagTable(t *testing.T) { + p := newPostgresqlTest(t) + p.TagsAsForeignKeys = true + p.tagsCache = freecache.NewCache(5 * 1024 * 1024) + + metrics := []telegraf.Metric{ + newMetric(t, "", MSS{"a": "one", "b": "two"}, MSI{"v": 1}), + } + + tsrc := NewTableSources(p.Postgresql, metrics)[t.Name()] + ttsrc := NewTagTableSource(tsrc) + ttrow := nextSrcRow(ttsrc) + assert.EqualValues(t, "one", ttrow["a"]) + assert.EqualValues(t, "two", ttrow["b"]) + + row := nextSrcRow(tsrc) + assert.Equal(t, row["tag_id"], ttrow["tag_id"]) +} + +func TestTableSource_tagTableJSONB(t *testing.T) { + p := newPostgresqlTest(t) + p.TagsAsForeignKeys = true + p.TagsAsJsonb = true + p.tagsCache = freecache.NewCache(5 * 1024 * 1024) + + metrics := []telegraf.Metric{ + newMetric(t, "", MSS{"a": "one", "b": "two"}, MSI{"v": 1}), + } + + tsrc := NewTableSources(p.Postgresql, metrics)[t.Name()] + ttsrc := NewTagTableSource(tsrc) + ttrow := nextSrcRow(ttsrc) + var tags MSI + require.NoError(t, json.Unmarshal(ttrow["tags"].([]byte), &tags)) + assert.EqualValues(t, MSI{"a": "one", "b": "two"}, tags) +} + +func TestTableSource_fieldsJSONB(t *testing.T) { + p := newPostgresqlTest(t) + p.FieldsAsJsonb = true + + metrics := []telegraf.Metric{ + newMetric(t, "", MSS{"tag": "foo"}, MSI{"a": 1, "b": 2}), + } + + tsrc := NewTableSources(p.Postgresql, metrics)[t.Name()] + row := nextSrcRow(tsrc) + var fields MSI + require.NoError(t, json.Unmarshal(row["fields"].([]byte), &fields)) + // json unmarshals numbers as floats + assert.EqualValues(t, MSI{"a": 1.0, "b": 2.0}, fields) +} + +// TagsAsForeignKeys=false +// Test that when a tag column is dropped, all metrics containing that tag are dropped. +func TestTableSource_DropColumn_tag(t *testing.T) { + p := newPostgresqlTest(t) + + metrics := []telegraf.Metric{ + newMetric(t, "", MSS{"a": "one", "b": "two"}, MSI{"v": 1}), + newMetric(t, "", MSS{"a": "one"}, MSI{"v": 2}), + } + tsrc := NewTableSources(p.Postgresql, metrics)[t.Name()] + + // Drop column "b" + var col utils.Column + for _, c := range tsrc.TagColumns() { + if c.Name == "b" { + col = c + break + } + } + _ = tsrc.DropColumn(col) + + row := nextSrcRow(tsrc) + assert.EqualValues(t, "one", row["a"]) + assert.EqualValues(t, 2, row["v"]) + assert.False(t, tsrc.Next()) +} + +// TagsAsForeignKeys=true, ForeignTagConstraint=true +// Test that when a tag column is dropped, all metrics containing that tag are dropped. +func TestTableSource_DropColumn_tag_fkTrue_fcTrue(t *testing.T) { + p := newPostgresqlTest(t) + p.TagsAsForeignKeys = true + p.ForeignTagConstraint = true + p.tagsCache = freecache.NewCache(5 * 1024 * 1024) + + metrics := []telegraf.Metric{ + newMetric(t, "", MSS{"a": "one", "b": "two"}, MSI{"v": 1}), + newMetric(t, "", MSS{"a": "one"}, MSI{"v": 2}), + } + tsrc := NewTableSources(p.Postgresql, metrics)[t.Name()] + + // Drop column "b" + var col utils.Column + for _, c := range tsrc.TagColumns() { + if c.Name == "b" { + col = c + break + } + } + _ = tsrc.DropColumn(col) + + ttsrc := NewTagTableSource(tsrc) + row := nextSrcRow(ttsrc) + assert.EqualValues(t, "one", row["a"]) + assert.False(t, ttsrc.Next()) + + row = nextSrcRow(tsrc) + assert.EqualValues(t, 2, row["v"]) + assert.False(t, tsrc.Next()) +} + +// TagsAsForeignKeys=true, ForeignTagConstraint=false +// Test that when a tag column is dropped, metrics are still added while the tag is not. +func TestTableSource_DropColumn_tag_fkTrue_fcFalse(t *testing.T) { + p := newPostgresqlTest(t) + p.TagsAsForeignKeys = true + p.ForeignTagConstraint = false + p.tagsCache = freecache.NewCache(5 * 1024 * 1024) + + metrics := []telegraf.Metric{ + newMetric(t, "", MSS{"a": "one", "b": "two"}, MSI{"v": 1}), + newMetric(t, "", MSS{"a": "one"}, MSI{"v": 2}), + } + tsrc := NewTableSources(p.Postgresql, metrics)[t.Name()] + + // Drop column "b" + var col utils.Column + for _, c := range tsrc.TagColumns() { + if c.Name == "b" { + col = c + break + } + } + _ = tsrc.DropColumn(col) + + ttsrc := NewTagTableSource(tsrc) + row := nextSrcRow(ttsrc) + assert.EqualValues(t, "one", row["a"]) + assert.False(t, ttsrc.Next()) + + row = nextSrcRow(tsrc) + assert.EqualValues(t, 1, row["v"]) + row = nextSrcRow(tsrc) + assert.EqualValues(t, 2, row["v"]) +} + +// Test that when a field is dropped, only the field is dropped, and all rows remain, unless it was the only field. +func TestTableSource_DropColumn_field(t *testing.T) { + p := newPostgresqlTest(t) + + metrics := []telegraf.Metric{ + newMetric(t, "", MSS{"tag": "foo"}, MSI{"a": 1}), + newMetric(t, "", MSS{"tag": "foo"}, MSI{"a": 2, "b": 3}), + } + tsrc := NewTableSources(p.Postgresql, metrics)[t.Name()] + + // Drop column "a" + var col utils.Column + for _, c := range tsrc.FieldColumns() { + if c.Name == "a" { + col = c + break + } + } + _ = tsrc.DropColumn(col) + + row := nextSrcRow(tsrc) + assert.EqualValues(t, "foo", row["tag"]) + assert.EqualValues(t, 3, row["b"]) + assert.False(t, tsrc.Next()) +} + +func TestTableSource_InconsistentTags(t *testing.T) { + p := newPostgresqlTest(t) + + metrics := []telegraf.Metric{ + newMetric(t, "", MSS{"a": "1"}, MSI{"b": 2}), + newMetric(t, "", MSS{"c": "3"}, MSI{"d": 4}), + } + tsrc := NewTableSources(p.Postgresql, metrics)[t.Name()] + + trow := nextSrcRow(tsrc) + assert.EqualValues(t, "1", trow["a"]) + assert.EqualValues(t, nil, trow["c"]) + + trow = nextSrcRow(tsrc) + assert.EqualValues(t, nil, trow["a"]) + assert.EqualValues(t, "3", trow["c"]) +} + +func TestTagTableSource_InconsistentTags(t *testing.T) { + p := newPostgresqlTest(t) + p.TagsAsForeignKeys = true + p.tagsCache = freecache.NewCache(5 * 1024 * 1024) + + metrics := []telegraf.Metric{ + newMetric(t, "", MSS{"a": "1"}, MSI{"b": 2}), + newMetric(t, "", MSS{"c": "3"}, MSI{"d": 4}), + } + tsrc := NewTableSources(p.Postgresql, metrics)[t.Name()] + ttsrc := NewTagTableSource(tsrc) + + // ttsrc is in non-deterministic order + expected := []MSI{ + {"a": "1", "c": nil}, + {"a": nil, "c": "3"}, + } + + var actual []MSI + for row := nextSrcRow(ttsrc); row != nil; row = nextSrcRow(ttsrc) { + delete(row, "tag_id") + actual = append(actual, row) + } + + assert.ElementsMatch(t, expected, actual) +} diff --git a/plugins/outputs/postgresql/utils/column.go b/plugins/outputs/postgresql/utils/column.go new file mode 100644 index 0000000000000..5363a7d2d8a33 --- /dev/null +++ b/plugins/outputs/postgresql/utils/column.go @@ -0,0 +1,53 @@ +package utils + +// This is split out from the 'postgresql' package as its depended upon by both the 'postgresql' and +// 'postgresql/template' packages. + +import ( + "sort" + "strings" +) + +// ColumnRole specifies the role of a column in a metric. +// It helps map the columns to the DB. +type ColumnRole int + +const ( + TimeColType ColumnRole = iota + 1 + TagsIDColType + TagColType + FieldColType +) + +type Column struct { + Name string + // the data type of each column should have in the db. used when checking + // if the schema matches or it needs updates + Type string + // the role each column has, helps properly map the metric to the db + Role ColumnRole +} + +// ColumnList implements sort.Interface. +// Columns are sorted first into groups of time,tag_id,tags,fields, and then alphabetically within +// each group. +type ColumnList []Column + +func (cl ColumnList) Len() int { + return len(cl) +} + +func (cl ColumnList) Less(i, j int) bool { + if cl[i].Role != cl[j].Role { + return cl[i].Role < cl[j].Role + } + return strings.ToLower(cl[i].Name) < strings.ToLower(cl[j].Name) +} + +func (cl ColumnList) Swap(i, j int) { + cl[i], cl[j] = cl[j], cl[i] +} + +func (cl ColumnList) Sort() { + sort.Sort(cl) +} diff --git a/plugins/outputs/postgresql/utils/utils.go b/plugins/outputs/postgresql/utils/utils.go new file mode 100644 index 0000000000000..2d8d1a6007460 --- /dev/null +++ b/plugins/outputs/postgresql/utils/utils.go @@ -0,0 +1,116 @@ +package utils + +import ( + "context" + "encoding/json" + "hash/fnv" + "strings" + "sync/atomic" + + "github.com/jackc/pgx/v4" + + "github.com/influxdata/telegraf" +) + +func TagListToJSON(tagList []*telegraf.Tag) []byte { + tags := make(map[string]string, len(tagList)) + for _, tag := range tagList { + tags[tag.Key] = tag.Value + } + bs, _ := json.Marshal(tags) + return bs +} + +func FieldListToJSON(fieldList []*telegraf.Field) ([]byte, error) { + fields := make(map[string]interface{}, len(fieldList)) + for _, field := range fieldList { + fields[field.Key] = field.Value + } + return json.Marshal(fields) +} + +// QuoteIdentifier returns a sanitized string safe to use in SQL as an identifier +func QuoteIdentifier(name string) string { + return pgx.Identifier{name}.Sanitize() +} + +// QuoteLiteral returns a sanitized string safe to use in sql as a string literal +func QuoteLiteral(name string) string { + return "'" + strings.Replace(name, "'", "''", -1) + "'" +} + +// FullTableName returns a sanitized table name with its schema (if supplied) +func FullTableName(schema, name string) pgx.Identifier { + if schema != "" { + return pgx.Identifier{schema, name} + } + + return pgx.Identifier{name} +} + +// pgxLogger makes telegraf.Logger compatible with pgx.Logger +type PGXLogger struct { + telegraf.Logger +} + +func (l PGXLogger) Log(_ context.Context, level pgx.LogLevel, msg string, data map[string]interface{}) { + switch level { + case pgx.LogLevelError: + l.Errorf("PG %s - %+v", msg, data) + case pgx.LogLevelWarn: + l.Warnf("PG %s - %+v", msg, data) + case pgx.LogLevelInfo, pgx.LogLevelNone: + l.Infof("PG %s - %+v", msg, data) + case pgx.LogLevelDebug, pgx.LogLevelTrace: + l.Debugf("PG %s - %+v", msg, data) + default: + l.Debugf("PG %s - %+v", msg, data) + } +} + +func GetTagID(metric telegraf.Metric) int64 { + hash := fnv.New64a() + for _, tag := range metric.TagList() { + _, _ = hash.Write([]byte(tag.Key)) + _, _ = hash.Write([]byte{0}) + _, _ = hash.Write([]byte(tag.Value)) + _, _ = hash.Write([]byte{0}) + } + // Convert to int64 as postgres does not support uint64 + return int64(hash.Sum64()) +} + +// WaitGroup is similar to sync.WaitGroup, but allows interruptable waiting (e.g. a timeout). +type WaitGroup struct { + count int32 + done chan struct{} +} + +func NewWaitGroup() *WaitGroup { + return &WaitGroup{ + done: make(chan struct{}), + } +} + +func (wg *WaitGroup) Add(i int32) { + select { + case <-wg.done: + panic("use of an already-done WaitGroup") + default: + } + atomic.AddInt32(&wg.count, i) +} + +func (wg *WaitGroup) Done() { + i := atomic.AddInt32(&wg.count, -1) + if i == 0 { + close(wg.done) + } + if i < 0 { + panic("too many Done() calls") + } +} + +func (wg *WaitGroup) C() <-chan struct{} { + return wg.done +}