From 2825854d52e31d4e800300a63cd080c87d24afab Mon Sep 17 00:00:00 2001 From: Matt Moore Date: Wed, 15 Jul 2020 12:06:33 -0700 Subject: [PATCH] [master] Auto-update dependencies (#287) Produced via: `./hack/update-deps.sh --upgrade && ./hack/update-codegen.sh` /assign n3wscott vagababov /cc n3wscott vagababov --- go.mod | 2 +- go.sum | 30 +- .../exporter/ocagent/go.mod | 2 + .../exporter/ocagent/ocagent.go | 6 +- .../exporter/prometheus/go.mod | 12 +- .../exporter/prometheus/go.sum | 126 ++- .../exporter/prometheus/prometheus.go | 39 +- .../exporter/prometheus/sanitize.go | 18 +- .../exporter/stackdriver/go.mod | 11 +- .../exporter/stackdriver/go.sum | 39 +- .../exporter/stackdriver/metrics.go | 4 +- .../{ => aws}/aws_identity_doc_utils.go | 4 +- .../aws/monitored_resources.go | 97 ++ .../monitoredresource/deprecated.go | 101 +++ .../{ => gcp}/gcp_metadata_config.go | 4 +- .../gcp/monitored_resources.go | 168 ++++ .../monitoredresource/monitored_resources.go | 180 +--- .../exporter/stackdriver/resource.go | 108 ++- .../exporter/stackdriver/stackdriver.go | 21 +- .../exporter/stackdriver/stats.go | 2 +- .../exporter/stackdriver/trace.go | 42 + vendor/github.com/alecthomas/template/LICENSE | 27 + .../github.com/alecthomas/template/README.md | 25 + vendor/github.com/alecthomas/template/doc.go | 406 +++++++++ vendor/github.com/alecthomas/template/exec.go | 845 ++++++++++++++++++ .../github.com/alecthomas/template/funcs.go | 598 +++++++++++++ vendor/github.com/alecthomas/template/go.mod | 1 + .../github.com/alecthomas/template/helper.go | 108 +++ .../alecthomas/template/parse/lex.go | 556 ++++++++++++ .../alecthomas/template/parse/node.go | 834 +++++++++++++++++ .../alecthomas/template/parse/parse.go | 700 +++++++++++++++ .../alecthomas/template/template.go | 218 +++++ vendor/github.com/alecthomas/units/COPYING | 19 + vendor/github.com/alecthomas/units/README.md | 11 + vendor/github.com/alecthomas/units/bytes.go | 85 ++ vendor/github.com/alecthomas/units/doc.go | 13 + vendor/github.com/alecthomas/units/go.mod | 3 + vendor/github.com/alecthomas/units/go.sum | 11 + vendor/github.com/alecthomas/units/si.go | 50 ++ vendor/github.com/alecthomas/units/util.go | 138 +++ .../go-windows-terminal-sequences/LICENSE | 9 + .../go-windows-terminal-sequences/README.md | 42 + .../go-windows-terminal-sequences/go.mod | 1 + .../sequences.go | 35 + .../sequences_dummy.go | 11 + .../common/log/eventlog_formatter.go | 89 ++ .../github.com/prometheus/common/log/log.go | 368 ++++++++ .../prometheus/common/log/syslog_formatter.go | 126 +++ .../prometheus/statsd_exporter/LICENSE | 201 +++++ .../prometheus/statsd_exporter/NOTICE | 5 + .../statsd_exporter/pkg/mapper/action.go | 42 + .../statsd_exporter/pkg/mapper/escape.go | 74 ++ .../statsd_exporter/pkg/mapper/fsm/README.md | 132 +++ .../statsd_exporter/pkg/mapper/fsm/dump.go | 48 + .../pkg/mapper/fsm/formatter.go | 76 ++ .../statsd_exporter/pkg/mapper/fsm/fsm.go | 326 +++++++ .../statsd_exporter/pkg/mapper/fsm/fsm.png | Bin 0 -> 33685 bytes .../statsd_exporter/pkg/mapper/fsm/minmax.go | 30 + .../statsd_exporter/pkg/mapper/mapper.go | 386 ++++++++ .../pkg/mapper/mapper_cache.go | 198 ++++ .../statsd_exporter/pkg/mapper/match.go | 41 + .../statsd_exporter/pkg/mapper/metric_type.go | 43 + .../statsd_exporter/pkg/mapper/timer.go | 41 + vendor/github.com/sirupsen/logrus/.gitignore | 2 + .../github.com/sirupsen/logrus/.golangci.yml | 40 + vendor/github.com/sirupsen/logrus/.travis.yml | 17 + .../github.com/sirupsen/logrus/CHANGELOG.md | 223 +++++ vendor/github.com/sirupsen/logrus/LICENSE | 21 + vendor/github.com/sirupsen/logrus/README.md | 513 +++++++++++ vendor/github.com/sirupsen/logrus/alt_exit.go | 76 ++ .../github.com/sirupsen/logrus/appveyor.yml | 14 + vendor/github.com/sirupsen/logrus/doc.go | 26 + vendor/github.com/sirupsen/logrus/entry.go | 426 +++++++++ vendor/github.com/sirupsen/logrus/exported.go | 225 +++++ .../github.com/sirupsen/logrus/formatter.go | 78 ++ vendor/github.com/sirupsen/logrus/go.mod | 11 + vendor/github.com/sirupsen/logrus/go.sum | 12 + vendor/github.com/sirupsen/logrus/hooks.go | 34 + .../sirupsen/logrus/json_formatter.go | 125 +++ vendor/github.com/sirupsen/logrus/logger.go | 352 ++++++++ vendor/github.com/sirupsen/logrus/logrus.go | 186 ++++ .../logrus/terminal_check_appengine.go | 11 + .../sirupsen/logrus/terminal_check_bsd.go | 13 + .../sirupsen/logrus/terminal_check_js.go | 7 + .../logrus/terminal_check_no_terminal.go | 11 + .../logrus/terminal_check_notappengine.go | 17 + .../sirupsen/logrus/terminal_check_solaris.go | 11 + .../sirupsen/logrus/terminal_check_unix.go | 13 + .../sirupsen/logrus/terminal_check_windows.go | 34 + .../sirupsen/logrus/text_formatter.go | 334 +++++++ vendor/github.com/sirupsen/logrus/writer.go | 70 ++ .../golang.org/x/sys/windows/registry/key.go | 198 ++++ .../x/sys/windows/registry/mksyscall.go | 9 + .../x/sys/windows/registry/syscall.go | 32 + .../x/sys/windows/registry/value.go | 386 ++++++++ .../sys/windows/registry/zsyscall_windows.go | 120 +++ .../x/sys/windows/svc/eventlog/install.go | 80 ++ .../x/sys/windows/svc/eventlog/log.go | 70 ++ .../alecthomas/kingpin.v2/.travis.yml | 4 + vendor/gopkg.in/alecthomas/kingpin.v2/COPYING | 19 + .../gopkg.in/alecthomas/kingpin.v2/README.md | 674 ++++++++++++++ .../gopkg.in/alecthomas/kingpin.v2/actions.go | 42 + vendor/gopkg.in/alecthomas/kingpin.v2/app.go | 688 ++++++++++++++ vendor/gopkg.in/alecthomas/kingpin.v2/args.go | 184 ++++ vendor/gopkg.in/alecthomas/kingpin.v2/cmd.go | 274 ++++++ .../alecthomas/kingpin.v2/completions.go | 33 + vendor/gopkg.in/alecthomas/kingpin.v2/doc.go | 68 ++ .../gopkg.in/alecthomas/kingpin.v2/envar.go | 45 + .../gopkg.in/alecthomas/kingpin.v2/flags.go | 308 +++++++ .../gopkg.in/alecthomas/kingpin.v2/global.go | 94 ++ .../alecthomas/kingpin.v2/guesswidth.go | 9 + .../alecthomas/kingpin.v2/guesswidth_unix.go | 38 + .../gopkg.in/alecthomas/kingpin.v2/model.go | 227 +++++ .../gopkg.in/alecthomas/kingpin.v2/parser.go | 396 ++++++++ .../gopkg.in/alecthomas/kingpin.v2/parsers.go | 212 +++++ .../alecthomas/kingpin.v2/templates.go | 262 ++++++ .../gopkg.in/alecthomas/kingpin.v2/usage.go | 211 +++++ .../gopkg.in/alecthomas/kingpin.v2/values.go | 470 ++++++++++ .../alecthomas/kingpin.v2/values.json | 25 + .../alecthomas/kingpin.v2/values_generated.go | 821 +++++++++++++++++ .../pkg/apis/duck/v1/addressable_types.go | 4 +- .../pkg/apis/duck/v1/kresource_type.go | 7 + .../pkg/apis/duck/v1/podspec_types.go | 9 +- .../pkg/apis/duck/v1/source_types.go | 8 +- .../pkg/apis/duck/v1/status_types.go | 3 + .../knative.dev/pkg/controller/controller.go | 2 +- .../pkg/controller/stats_reporter.go | 10 +- vendor/knative.dev/pkg/hash/bucketer.go | 121 +++ vendor/knative.dev/pkg/hash/doc.go | 27 + vendor/knative.dev/pkg/hash/hash.go | 166 ++++ .../knative.dev/pkg/leaderelection/config.go | 18 +- .../knative.dev/pkg/leaderelection/context.go | 57 +- .../knative.dev/pkg/leaderelection/ordinal.go | 27 - vendor/knative.dev/pkg/metrics/config.go | 31 +- vendor/knative.dev/pkg/metrics/exporter.go | 59 +- .../pkg/metrics/metricskey/constants.go | 24 + .../pkg/metrics/monitored_resources.go | 34 - .../metrics/monitored_resources_eventing.go | 159 ---- .../metrics/monitored_resources_serving.go | 73 -- .../pkg/metrics/opencensus_exporter.go | 24 +- .../pkg/metrics/prometheus_exporter.go | 18 +- vendor/knative.dev/pkg/metrics/record.go | 1 + .../knative.dev/pkg/metrics/resource_view.go | 323 +++++++ .../pkg/metrics/stackdriver_exporter.go | 181 +++- vendor/modules.txt | 28 +- 145 files changed, 17608 insertions(+), 695 deletions(-) rename vendor/contrib.go.opencensus.io/exporter/stackdriver/monitoredresource/{ => aws}/aws_identity_doc_utils.go (96%) create mode 100644 vendor/contrib.go.opencensus.io/exporter/stackdriver/monitoredresource/aws/monitored_resources.go create mode 100644 vendor/contrib.go.opencensus.io/exporter/stackdriver/monitoredresource/deprecated.go rename vendor/contrib.go.opencensus.io/exporter/stackdriver/monitoredresource/{ => gcp}/gcp_metadata_config.go (98%) create mode 100644 vendor/contrib.go.opencensus.io/exporter/stackdriver/monitoredresource/gcp/monitored_resources.go create mode 100644 vendor/github.com/alecthomas/template/LICENSE create mode 100644 vendor/github.com/alecthomas/template/README.md create mode 100644 vendor/github.com/alecthomas/template/doc.go create mode 100644 vendor/github.com/alecthomas/template/exec.go create mode 100644 vendor/github.com/alecthomas/template/funcs.go create mode 100644 vendor/github.com/alecthomas/template/go.mod create mode 100644 vendor/github.com/alecthomas/template/helper.go create mode 100644 vendor/github.com/alecthomas/template/parse/lex.go create mode 100644 vendor/github.com/alecthomas/template/parse/node.go create mode 100644 vendor/github.com/alecthomas/template/parse/parse.go create mode 100644 vendor/github.com/alecthomas/template/template.go create mode 100644 vendor/github.com/alecthomas/units/COPYING create mode 100644 vendor/github.com/alecthomas/units/README.md create mode 100644 vendor/github.com/alecthomas/units/bytes.go create mode 100644 vendor/github.com/alecthomas/units/doc.go create mode 100644 vendor/github.com/alecthomas/units/go.mod create mode 100644 vendor/github.com/alecthomas/units/go.sum create mode 100644 vendor/github.com/alecthomas/units/si.go create mode 100644 vendor/github.com/alecthomas/units/util.go create mode 100644 vendor/github.com/konsorten/go-windows-terminal-sequences/LICENSE create mode 100644 vendor/github.com/konsorten/go-windows-terminal-sequences/README.md create mode 100644 vendor/github.com/konsorten/go-windows-terminal-sequences/go.mod create mode 100644 vendor/github.com/konsorten/go-windows-terminal-sequences/sequences.go create mode 100644 vendor/github.com/konsorten/go-windows-terminal-sequences/sequences_dummy.go create mode 100644 vendor/github.com/prometheus/common/log/eventlog_formatter.go create mode 100644 vendor/github.com/prometheus/common/log/log.go create mode 100644 vendor/github.com/prometheus/common/log/syslog_formatter.go create mode 100644 vendor/github.com/prometheus/statsd_exporter/LICENSE create mode 100644 vendor/github.com/prometheus/statsd_exporter/NOTICE create mode 100644 vendor/github.com/prometheus/statsd_exporter/pkg/mapper/action.go create mode 100644 vendor/github.com/prometheus/statsd_exporter/pkg/mapper/escape.go create mode 100644 vendor/github.com/prometheus/statsd_exporter/pkg/mapper/fsm/README.md create mode 100644 vendor/github.com/prometheus/statsd_exporter/pkg/mapper/fsm/dump.go create mode 100644 vendor/github.com/prometheus/statsd_exporter/pkg/mapper/fsm/formatter.go create mode 100644 vendor/github.com/prometheus/statsd_exporter/pkg/mapper/fsm/fsm.go create mode 100644 vendor/github.com/prometheus/statsd_exporter/pkg/mapper/fsm/fsm.png create mode 100644 vendor/github.com/prometheus/statsd_exporter/pkg/mapper/fsm/minmax.go create mode 100644 vendor/github.com/prometheus/statsd_exporter/pkg/mapper/mapper.go create mode 100644 vendor/github.com/prometheus/statsd_exporter/pkg/mapper/mapper_cache.go create mode 100644 vendor/github.com/prometheus/statsd_exporter/pkg/mapper/match.go create mode 100644 vendor/github.com/prometheus/statsd_exporter/pkg/mapper/metric_type.go create mode 100644 vendor/github.com/prometheus/statsd_exporter/pkg/mapper/timer.go create mode 100644 vendor/github.com/sirupsen/logrus/.gitignore create mode 100644 vendor/github.com/sirupsen/logrus/.golangci.yml create mode 100644 vendor/github.com/sirupsen/logrus/.travis.yml create mode 100644 vendor/github.com/sirupsen/logrus/CHANGELOG.md create mode 100644 vendor/github.com/sirupsen/logrus/LICENSE create mode 100644 vendor/github.com/sirupsen/logrus/README.md create mode 100644 vendor/github.com/sirupsen/logrus/alt_exit.go create mode 100644 vendor/github.com/sirupsen/logrus/appveyor.yml create mode 100644 vendor/github.com/sirupsen/logrus/doc.go create mode 100644 vendor/github.com/sirupsen/logrus/entry.go create mode 100644 vendor/github.com/sirupsen/logrus/exported.go create mode 100644 vendor/github.com/sirupsen/logrus/formatter.go create mode 100644 vendor/github.com/sirupsen/logrus/go.mod create mode 100644 vendor/github.com/sirupsen/logrus/go.sum create mode 100644 vendor/github.com/sirupsen/logrus/hooks.go create mode 100644 vendor/github.com/sirupsen/logrus/json_formatter.go create mode 100644 vendor/github.com/sirupsen/logrus/logger.go create mode 100644 vendor/github.com/sirupsen/logrus/logrus.go create mode 100644 vendor/github.com/sirupsen/logrus/terminal_check_appengine.go create mode 100644 vendor/github.com/sirupsen/logrus/terminal_check_bsd.go create mode 100644 vendor/github.com/sirupsen/logrus/terminal_check_js.go create mode 100644 vendor/github.com/sirupsen/logrus/terminal_check_no_terminal.go create mode 100644 vendor/github.com/sirupsen/logrus/terminal_check_notappengine.go create mode 100644 vendor/github.com/sirupsen/logrus/terminal_check_solaris.go create mode 100644 vendor/github.com/sirupsen/logrus/terminal_check_unix.go create mode 100644 vendor/github.com/sirupsen/logrus/terminal_check_windows.go create mode 100644 vendor/github.com/sirupsen/logrus/text_formatter.go create mode 100644 vendor/github.com/sirupsen/logrus/writer.go create mode 100644 vendor/golang.org/x/sys/windows/registry/key.go create mode 100644 vendor/golang.org/x/sys/windows/registry/mksyscall.go create mode 100644 vendor/golang.org/x/sys/windows/registry/syscall.go create mode 100644 vendor/golang.org/x/sys/windows/registry/value.go create mode 100644 vendor/golang.org/x/sys/windows/registry/zsyscall_windows.go create mode 100644 vendor/golang.org/x/sys/windows/svc/eventlog/install.go create mode 100644 vendor/golang.org/x/sys/windows/svc/eventlog/log.go create mode 100644 vendor/gopkg.in/alecthomas/kingpin.v2/.travis.yml create mode 100644 vendor/gopkg.in/alecthomas/kingpin.v2/COPYING create mode 100644 vendor/gopkg.in/alecthomas/kingpin.v2/README.md create mode 100644 vendor/gopkg.in/alecthomas/kingpin.v2/actions.go create mode 100644 vendor/gopkg.in/alecthomas/kingpin.v2/app.go create mode 100644 vendor/gopkg.in/alecthomas/kingpin.v2/args.go create mode 100644 vendor/gopkg.in/alecthomas/kingpin.v2/cmd.go create mode 100644 vendor/gopkg.in/alecthomas/kingpin.v2/completions.go create mode 100644 vendor/gopkg.in/alecthomas/kingpin.v2/doc.go create mode 100644 vendor/gopkg.in/alecthomas/kingpin.v2/envar.go create mode 100644 vendor/gopkg.in/alecthomas/kingpin.v2/flags.go create mode 100644 vendor/gopkg.in/alecthomas/kingpin.v2/global.go create mode 100644 vendor/gopkg.in/alecthomas/kingpin.v2/guesswidth.go create mode 100644 vendor/gopkg.in/alecthomas/kingpin.v2/guesswidth_unix.go create mode 100644 vendor/gopkg.in/alecthomas/kingpin.v2/model.go create mode 100644 vendor/gopkg.in/alecthomas/kingpin.v2/parser.go create mode 100644 vendor/gopkg.in/alecthomas/kingpin.v2/parsers.go create mode 100644 vendor/gopkg.in/alecthomas/kingpin.v2/templates.go create mode 100644 vendor/gopkg.in/alecthomas/kingpin.v2/usage.go create mode 100644 vendor/gopkg.in/alecthomas/kingpin.v2/values.go create mode 100644 vendor/gopkg.in/alecthomas/kingpin.v2/values.json create mode 100644 vendor/gopkg.in/alecthomas/kingpin.v2/values_generated.go create mode 100644 vendor/knative.dev/pkg/hash/bucketer.go create mode 100644 vendor/knative.dev/pkg/hash/doc.go create mode 100644 vendor/knative.dev/pkg/hash/hash.go delete mode 100644 vendor/knative.dev/pkg/leaderelection/ordinal.go delete mode 100644 vendor/knative.dev/pkg/metrics/monitored_resources.go delete mode 100644 vendor/knative.dev/pkg/metrics/monitored_resources_eventing.go delete mode 100644 vendor/knative.dev/pkg/metrics/monitored_resources_serving.go create mode 100644 vendor/knative.dev/pkg/metrics/resource_view.go diff --git a/go.mod b/go.mod index 19e2cefc..ffbb87b2 100644 --- a/go.mod +++ b/go.mod @@ -16,7 +16,7 @@ require ( k8s.io/client-go v11.0.1-0.20190805182717-6502b5e7b1b5+incompatible k8s.io/code-generator v0.18.0 k8s.io/kube-openapi v0.0.0-20200410145947-bcb3869e6f29 - knative.dev/pkg v0.0.0-20200714070918-ac02cac99b88 + knative.dev/pkg v0.0.0-20200715082932-e863db03445f knative.dev/test-infra v0.0.0-20200713220518-5a4c4cad5372 ) diff --git a/go.sum b/go.sum index b573064e..3d73c059 100644 --- a/go.sum +++ b/go.sum @@ -45,14 +45,16 @@ code.gitea.io/sdk/gitea v0.12.0/go.mod h1:z3uwDV/b9Ls47NGukYM9XhnHtqPh/J+t40lsUr contrib.go.opencensus.io/exporter/aws v0.0.0-20181029163544-2befc13012d0/go.mod h1:uu1P0UCM/6RbsMrgPa98ll8ZcHM858i/AD06a9aLRCA= contrib.go.opencensus.io/exporter/ocagent v0.4.12/go.mod h1:450APlNTSR6FrvC3CTRqYosuDstRB9un7SOx2k/9ckA= contrib.go.opencensus.io/exporter/ocagent v0.5.0/go.mod h1:ImxhfLRpxoYiSq891pBrLVhN+qmP8BTVvdH2YLs7Gl0= -contrib.go.opencensus.io/exporter/ocagent v0.6.0 h1:Z1n6UAyr0QwM284yUuh5Zd8JlvxUGAhFZcgMJkMPrGM= -contrib.go.opencensus.io/exporter/ocagent v0.6.0/go.mod h1:zmKjrJcdo0aYcVS7bmEeSEBLPA9YJp5bjrofdU3pIXs= +contrib.go.opencensus.io/exporter/ocagent v0.6.1-0.20200121232419-b3d61dbd6252 h1:78QgIAE2FeU2klDzCuergwddNYzuC9EihGyR2Eq+/XM= +contrib.go.opencensus.io/exporter/ocagent v0.6.1-0.20200121232419-b3d61dbd6252/go.mod h1:COXDBN+YYlzrujnt2M1Ym3DBY4O3GlMujpfhzvS27Rk= contrib.go.opencensus.io/exporter/prometheus v0.1.0 h1:SByaIoWwNgMdPSgl5sMqM2KDE5H/ukPWBRo314xiDvg= contrib.go.opencensus.io/exporter/prometheus v0.1.0/go.mod h1:cGFniUXGZlKRjzOyuZJ6mgB+PgBcCIa79kEKR8YCW+A= +contrib.go.opencensus.io/exporter/prometheus v0.2.1-0.20200609204449-6bcf6f8577f0 h1:2O3c1g5CzMc1+Uah4Waot9Obm0yw70VXJzWaP6Fz3nw= +contrib.go.opencensus.io/exporter/prometheus v0.2.1-0.20200609204449-6bcf6f8577f0/go.mod h1:MjHoxkI7Ny27toPeFkRbXbzVjzIGkwOAptrAy8Mxtm8= contrib.go.opencensus.io/exporter/stackdriver v0.12.1/go.mod h1:iwB6wGarfphGGe/e5CWqyUk/cLzKnWsOKPVW3no6OTw= contrib.go.opencensus.io/exporter/stackdriver v0.12.8/go.mod h1:XyyafDnFOsqoxHJgTFycKZMrRUrPThLh2iYTJF6uoO0= -contrib.go.opencensus.io/exporter/stackdriver v0.12.9-0.20191108183826-59d068f8d8ff h1:g4QkFNN0ak+sCs/jqbhYLNkQaF1NVaKVoQ4Xm1RV3wM= -contrib.go.opencensus.io/exporter/stackdriver v0.12.9-0.20191108183826-59d068f8d8ff/go.mod h1:XyyafDnFOsqoxHJgTFycKZMrRUrPThLh2iYTJF6uoO0= +contrib.go.opencensus.io/exporter/stackdriver v0.13.2 h1:5lKLBwUuq4S6pTbYaBtWmnay3eJfKNS3qL8M8HM5fM4= +contrib.go.opencensus.io/exporter/stackdriver v0.13.2/go.mod h1:aXENhDJ1Y4lIg4EUaVTwzvYETVNZk10Pu26tevFKLUc= contrib.go.opencensus.io/exporter/zipkin v0.1.1/go.mod h1:GMvdSl3eJ2gapOaLKzTKE3qDgUkJ86k9k3yY2eqwkzc= contrib.go.opencensus.io/integrations/ocsql v0.1.4/go.mod h1:8DsSdjz3F+APR+0z0WkU1aRorQCFfRxvqjUUPMbF3fE= contrib.go.opencensus.io/resource v0.1.1/go.mod h1:F361eGI91LCmW1I/Saf+rX0+OFcigGlFvXwEGEnkRLA= @@ -138,9 +140,11 @@ github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4Rq github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/ghQa61ZWa/C2Aw3RkjiTBOix7dkqa1VLIs= github.com/alecthomas/kingpin v2.2.6+incompatible/go.mod h1:59OFYbFVLKQKq+mqrL6Rw5bR0c3ACQaawgXx0QYndlE= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d h1:UQZhZ2O0vMHr2cI+DC1Mbh0TJxzA3RcLoMsFw+aXw7E= github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= github.com/andybalholm/brotli v0.0.0-20190621154722-5f990b63d2d6/go.mod h1:+lx6/Aqd1kLJ1GQfkvOnaZ1WGmLpMpbprPuIOOZX30U= @@ -215,6 +219,7 @@ github.com/census-instrumentation/opencensus-proto v0.2.1 h1:glEXhBS5PSLLv4IXzLA github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/cespare/xxhash/v2 v2.1.0/go.mod h1:dgIUBU3pDso/gPgZ1osOZ0iQf77oPR28Tjxl5dIMyVM= github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chai2010/gettext-go v0.0.0-20160711120539-c6fed771bfd5/go.mod h1:/iP1qXHoty45bqomnu2LM+VVyAEdWN+vtSHGlQgyxbw= @@ -449,6 +454,7 @@ github.com/golang/groupcache v0.0.0-20180513044358-24b0969c4cb7/go.mod h1:cIg4er github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6 h1:ZgQEtGgCBiWRM39fZuwSd1LwSqqSW0hOdXCYYDX0R3I= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191027212112-611e8accdfc9/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -689,6 +695,7 @@ github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgo github.com/klauspost/pgzip v1.2.1/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.3 h1:CE8S1cTafDpPvMhIxNJKvHsGVBgn1xWYf1NbHQhywc8= github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= @@ -873,6 +880,7 @@ github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDf github.com/prometheus/client_golang v1.0.0 h1:vrDKnkGzuGvhNAL56c7DBz29ZL+KxnoR0x7enabFceM= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g= +github.com/prometheus/client_golang v1.2.1/go.mod h1:XMU6Z2MjaRKVu/dC1qupJI9SiNkDYzz3xecMgSW/F+U= github.com/prometheus/client_golang v1.5.0 h1:Ctq0iGpCmr3jeP77kbF2UxgvRwzWWz+4Bh9/vJTyg1A= github.com/prometheus/client_golang v1.5.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= github.com/prometheus/client_model v0.0.0-20171117100541-99fa1f4be8e5/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= @@ -902,10 +910,13 @@ github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7z github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= github.com/prometheus/procfs v0.0.5/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= +github.com/prometheus/procfs v0.0.6/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= github.com/prometheus/procfs v0.0.10/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= github.com/prometheus/procfs v0.0.11 h1:DhHlBtkHWPYi8O2y31JkK0TF+DGM+51OopZjH/Ia5qI= github.com/prometheus/procfs v0.0.11/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= +github.com/prometheus/statsd_exporter v0.15.0 h1:UiwC1L5HkxEPeapXdm2Ye0u1vUJfTj7uwT5yydYpa1E= +github.com/prometheus/statsd_exporter v0.15.0/go.mod h1:Dv8HnkoLQkeEjkIE4/2ndAA7WL1zHKK7WMqFQqu72rw= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/quasilyte/go-consistent v0.0.0-20190521200055-c6f3937de18c/go.mod h1:5STLWrekHfjyYwxBRVRXNOSewLJ3PWfDJd1VyTS21fI= github.com/quasilyte/go-ruleguard v0.1.2-0.20200318202121-b00d7a75d3d8/go.mod h1:CGFX09Ci3pq9QZdj86B+VGIdNj4VyCo2iPOGS9esB/k= @@ -947,6 +958,7 @@ github.com/sirupsen/logrus v1.0.4-0.20170822132746-89742aefa4b2/go.mod h1:pMByvH github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/sirupsen/logrus v1.6.0 h1:UBcNElsrwanuuMsnGSlYmtmgbb23qDR5dG+6X6Oo89I= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/assertions v1.0.0/go.mod h1:kHHU4qYBaI3q23Pp3VPrmWhuIUrLW/7eUrw0BU5VaoM= @@ -1072,6 +1084,7 @@ go.opencensus.io v0.22.1/go.mod h1:Ap50jQcDJrx6rB6VgeeFPtuPIf3wMRvRfrfYDO6+BmA= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3 h1:8sGtKOrtQqkN1bp2AtX+misvLIlOmsEsNd+9NIcPEm8= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.4-0.20200608061201-1901b56b9515/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4 h1:LYy1Hy3MJdrCdMwwzxA/dRok4ejH+RwNGbuoD9fCjto= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= @@ -1283,6 +1296,7 @@ golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191112214154-59a1497f0cea/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191113165036-4c7a9d0fe056/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1358,6 +1372,7 @@ golang.org/x/tools v0.0.0-20190910044552-dd2b5c81c578/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190920225731-5eefd052ad72/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190927191325-030b2cf1153e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191010075000-0337d82405ff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191010171213-8abd42400456/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= @@ -1533,6 +1548,7 @@ google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGj google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U= +gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20141024133853-64131543e789/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -1642,10 +1658,8 @@ k8s.io/utils v0.0.0-20200124190032-861946025e34/go.mod h1:sZAwmy6armz5eXlNoLmJcl knative.dev/caching v0.0.0-20200116200605-67bca2c83dfa/go.mod h1:dHXFU6CGlLlbzaWc32g80cR92iuBSpsslDNBWI8C7eg= knative.dev/eventing-contrib v0.11.2/go.mod h1:SnXZgSGgMSMLNFTwTnpaOH7hXDzTFtw0J8OmHflNx3g= knative.dev/pkg v0.0.0-20200207155214-fef852970f43/go.mod h1:pgODObA1dTyhNoFxPZTTjNWfx6F0aKsKzn+vaT9XO/Q= -knative.dev/pkg v0.0.0-20200714070918-ac02cac99b88 h1:qcOoc14NrP2T0mB7I/e6Fl6JdvWEOwE1kJkrqsqqPJU= -knative.dev/pkg v0.0.0-20200714070918-ac02cac99b88/go.mod h1:2xVLIH5SNUripobZvOEz3w/Ta9xqMkw7QmFIa2cbDFY= -knative.dev/test-infra v0.0.0-20200713045417-850e4e37918d h1:Q3LrAYSi+Ii2yZVUiA5Y3Jr4TCU6g/XN9ClVosejpJk= -knative.dev/test-infra v0.0.0-20200713045417-850e4e37918d/go.mod h1:vtT6dLs/iNj8pKcfag8CSVqHKNMgyCFtU/g1pV7Bovs= +knative.dev/pkg v0.0.0-20200715082932-e863db03445f h1:2bO5Qm4rZlhYUYCVhlnDZ3tMQuB6JogpIMZlXggSaJM= +knative.dev/pkg v0.0.0-20200715082932-e863db03445f/go.mod h1:yFXTzV2GIB30Qs6pdJNCbtKaIEhxH9fsmrrltAqfjWE= knative.dev/test-infra v0.0.0-20200713220518-5a4c4cad5372 h1:NZzdNmKYP3L7fut/SNOxLgTgXVvQrygXiYpAeIMGMwM= knative.dev/test-infra v0.0.0-20200713220518-5a4c4cad5372/go.mod h1:vtT6dLs/iNj8pKcfag8CSVqHKNMgyCFtU/g1pV7Bovs= modernc.org/cc v1.0.0/go.mod h1:1Sk4//wdnYJiUIxnW8ddKpaOJCF37yAdqYnkxUpaYxw= diff --git a/vendor/contrib.go.opencensus.io/exporter/ocagent/go.mod b/vendor/contrib.go.opencensus.io/exporter/ocagent/go.mod index b40fc0c7..ffa8ee66 100644 --- a/vendor/contrib.go.opencensus.io/exporter/ocagent/go.mod +++ b/vendor/contrib.go.opencensus.io/exporter/ocagent/go.mod @@ -12,3 +12,5 @@ require ( google.golang.org/genproto v0.0.0-20190716160619-c506a9f90610 // indirect google.golang.org/grpc v1.22.0 ) + +go 1.13 diff --git a/vendor/contrib.go.opencensus.io/exporter/ocagent/ocagent.go b/vendor/contrib.go.opencensus.io/exporter/ocagent/ocagent.go index 24f41078..144e9372 100644 --- a/vendor/contrib.go.opencensus.io/exporter/ocagent/ocagent.go +++ b/vendor/contrib.go.opencensus.io/exporter/ocagent/ocagent.go @@ -1,4 +1,4 @@ -// Copyright 2018, OpenCensus Authors +// Copyright 2020, OpenCensus Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -504,7 +504,7 @@ func (ae *Exporter) uploadTraces(sdl []*trace.SpanData) { ae.senderMu.Lock() err := ae.traceExporter.Send(&agenttracepb.ExportTraceServiceRequest{ Spans: protoSpans, - Resource: resourceProtoFromEnv(), + Resource: ae.resource, }) ae.senderMu.Unlock() if err != nil { @@ -537,7 +537,7 @@ func (ae *Exporter) uploadViewData(vdl []*view.Data) { } req := &agentmetricspb.ExportMetricsServiceRequest{ Metrics: protoMetrics, - Resource: resourceProtoFromEnv(), + Resource: ae.resource, // TODO:(@odeke-em) // a) Figure out how to derive a Node from the environment // or better letting users of the exporter configure it. diff --git a/vendor/contrib.go.opencensus.io/exporter/prometheus/go.mod b/vendor/contrib.go.opencensus.io/exporter/prometheus/go.mod index af78dfba..1ed6efc9 100644 --- a/vendor/contrib.go.opencensus.io/exporter/prometheus/go.mod +++ b/vendor/contrib.go.opencensus.io/exporter/prometheus/go.mod @@ -1,6 +1,14 @@ module contrib.go.opencensus.io/exporter/prometheus require ( - github.com/prometheus/client_golang v0.9.2 - go.opencensus.io v0.21.0 + github.com/cespare/xxhash/v2 v2.1.1 // indirect + github.com/golang/groupcache v0.0.0-20191027212112-611e8accdfc9 // indirect + github.com/google/go-cmp v0.3.1 + github.com/prometheus/client_golang v1.2.1 + github.com/prometheus/procfs v0.0.6 // indirect + github.com/prometheus/statsd_exporter v0.15.0 + go.opencensus.io v0.22.4-0.20200608061201-1901b56b9515 + golang.org/x/sys v0.0.0-20191113165036-4c7a9d0fe056 // indirect ) + +go 1.13 diff --git a/vendor/contrib.go.opencensus.io/exporter/prometheus/go.sum b/vendor/contrib.go.opencensus.io/exporter/prometheus/go.sum index 9eba12a5..b65c1cfc 100644 --- a/vendor/contrib.go.opencensus.io/exporter/prometheus/go.sum +++ b/vendor/contrib.go.opencensus.io/exporter/prometheus/go.sum @@ -1,51 +1,149 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM= +github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4 h1:Hs82Z41s6SdL1CELW+XaDYmOH4hkBN4/N9og/AsOv7E= +github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 h1:xJ4a3vCFaGF/jqvzLMYoU8P317H5OQ+Via4RmuPwCS0= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0 h1:HWo1m869IqiPhD389kmkxeTalrjNbbJTC8LXupb+sl0= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/cespare/xxhash/v2 v2.1.0 h1:yTUvW7Vhb89inJ+8irsUqiWjh8iT6sQPZiQzI6ReGkA= +github.com/cespare/xxhash/v2 v2.1.0/go.mod h1:dgIUBU3pDso/gPgZ1osOZ0iQf77oPR28Tjxl5dIMyVM= +github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6 h1:ZgQEtGgCBiWRM39fZuwSd1LwSqqSW0hOdXCYYDX0R3I= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191027212112-611e8accdfc9 h1:uHTyIjqVhYRhLbJ8nIiOJHkEZZ+5YoOsAbD3sk82NiE= +github.com/golang/groupcache v0.0.0-20191027212112-611e8accdfc9/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ= -github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= -github.com/hashicorp/golang-lru v0.5.0 h1:CL2msUPvZTLb5O648aiLNJw3hnBxN2+1Jq8rCOH9wdo= -github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= -github.com/prometheus/client_golang v0.9.2 h1:awm861/B8OKDd2I/6o1dy3ra4BamzKhYOiGItCeZ740= -github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= +github.com/prometheus/client_golang v1.2.1 h1:JnMpQc6ppsNgw9QPAGF6Dod479itz7lvlsMzzNayLOI= +github.com/prometheus/client_golang v1.2.1/go.mod h1:XMU6Z2MjaRKVu/dC1qupJI9SiNkDYzz3xecMgSW/F+U= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910 h1:idejC8f05m9MGOsuEi1ATq9shN03HrxNkD/luQvxCv8= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= -github.com/prometheus/common v0.0.0-20181126121408-4724e9255275 h1:PnBWHBf+6L0jOqq0gIVUe6Yk0/QMZ640k6NvkxcBf+8= -github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= -github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a h1:9a8MnZMP0X2nLJdBg+pBmGgkJlSaKC2KaQmTCk1XDtE= -github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -go.opencensus.io v0.21.0 h1:mU6zScU4U1YAFPHEHYk+3JC4SY7JxgkqS10ZOSyksNg= -go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90 h1:S/YWwWx/RA8rT8tKFRuGUZhuA90OyIBpPCXkcbwU8DE= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4 h1:gQz4mCbXsO+nc9n1hCxHcGA3Zx3Eo+UHZoInFGUIXNM= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/common v0.4.1 h1:K0MGApIoQvMw27RTdJkPbr3JZ7DNbtxQNyi5STVM6Kw= +github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.7.0 h1:L+1lyG48J1zAQXA3RBX/nG/B3gjlHq0zTt2tlbJLyCY= +github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.5 h1:3+auTFlqw+ZaQYJARz6ArODtkaIwtvBTx3N2NehQlL8= +github.com/prometheus/procfs v0.0.5/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= +github.com/prometheus/procfs v0.0.6 h1:0qbH+Yqu/cj1ViVLvEWCP6qMQ4efWUj6bQqOEA0V0U4= +github.com/prometheus/procfs v0.0.6/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= +github.com/prometheus/statsd_exporter v0.15.0 h1:UiwC1L5HkxEPeapXdm2Ye0u1vUJfTj7uwT5yydYpa1E= +github.com/prometheus/statsd_exporter v0.15.0/go.mod h1:Dv8HnkoLQkeEjkIE4/2ndAA7WL1zHKK7WMqFQqu72rw= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +go.opencensus.io v0.22.2 h1:75k/FF0Q2YM8QYo07VPddOLBslDt1MZOdEslOHvmzAs= +go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.4-0.20200608061201-1901b56b9515 h1:YS3N5IEX0gd5pYMAT6Am+LQPPuQTNRv11JaZY0+BV0Y= +go.opencensus.io v0.22.4-0.20200608061201-1901b56b9515/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a h1:oWX7TPOiFAMXLq8o0ikBYfCJVlRHBcsciT5bXOrH628= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6 h1:bjcUS9ztw9kFmmIxJInhon/0Is3p+EHBKNgquIzo1OI= golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191010194322-b09406accb47 h1:/XfQ9z7ib8eEJX2hdgFTZJ/ntt0swNk5oYBziWeTCvY= +golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191113165036-4c7a9d0fe056 h1:dHtDnRWQtSx0Hjq9kvKFpBh9uPPKfQN70NZZmvssGwk= +golang.org/x/sys v0.0.0-20191113165036-4c7a9d0fe056/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/vendor/contrib.go.opencensus.io/exporter/prometheus/prometheus.go b/vendor/contrib.go.opencensus.io/exporter/prometheus/prometheus.go index 59ce1c0a..7cf883f5 100644 --- a/vendor/contrib.go.opencensus.io/exporter/prometheus/prometheus.go +++ b/vendor/contrib.go.opencensus.io/exporter/prometheus/prometheus.go @@ -23,6 +23,7 @@ import ( "sync" "context" + "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promhttp" "go.opencensus.io/metric/metricdata" @@ -44,6 +45,8 @@ type Exporter struct { type Options struct { Namespace string Registry *prometheus.Registry + Registerer prometheus.Registerer + Gatherer prometheus.Gatherer OnError func(err error) ConstLabels prometheus.Labels // ConstLabels will be set as labels on all views. } @@ -53,12 +56,19 @@ func NewExporter(o Options) (*Exporter, error) { if o.Registry == nil { o.Registry = prometheus.NewRegistry() } - collector := newCollector(o, o.Registry) + if o.Registerer == nil { + o.Registerer = o.Registry + } + if o.Gatherer == nil { + o.Gatherer = o.Registry + } + + collector := newCollector(o, o.Registerer) e := &Exporter{ opts: o, - g: o.Registry, + g: o.Gatherer, c: collector, - handler: promhttp.HandlerFor(o.Registry, promhttp.HandlerOpts{}), + handler: promhttp.HandlerFor(o.Gatherer, promhttp.HandlerOpts{}), } collector.ensureRegisteredOnce() @@ -112,7 +122,7 @@ type collector struct { registerOnce sync.Once // reg helps collector register views dynamically. - reg *prometheus.Registry + reg prometheus.Registerer // reader reads metrics from all registered producers. reader *metricexport.Reader @@ -132,7 +142,7 @@ func (c *collector) Collect(ch chan<- prometheus.Metric) { c.reader.ReadAndExport(me) } -func newCollector(opts Options, registrar *prometheus.Registry) *collector { +func newCollector(opts Options, registrar prometheus.Registerer) *collector { return &collector{ reg: registrar, opts: opts, @@ -140,11 +150,28 @@ func newCollector(opts Options, registrar *prometheus.Registry) *collector { } func (c *collector) toDesc(metric *metricdata.Metric) *prometheus.Desc { + var labels prometheus.Labels + if metric.Resource == nil { + labels = c.opts.ConstLabels + } else if c.opts.ConstLabels == nil { + labels = metric.Resource.Labels + } else { + labels = prometheus.Labels{} + for k, v := range c.opts.ConstLabels { + labels[k] = v + } + // Resource labels overwrite const labels. + for k, v := range metric.Resource.Labels { + labels[k] = v + } + } + return prometheus.NewDesc( metricName(c.opts.Namespace, metric), metric.Descriptor.Description, toPromLabels(metric.Descriptor.LabelKeys), - c.opts.ConstLabels) + labels) + } type metricExporter struct { diff --git a/vendor/contrib.go.opencensus.io/exporter/prometheus/sanitize.go b/vendor/contrib.go.opencensus.io/exporter/prometheus/sanitize.go index ed6d8a14..9c9a9c4d 100644 --- a/vendor/contrib.go.opencensus.io/exporter/prometheus/sanitize.go +++ b/vendor/contrib.go.opencensus.io/exporter/prometheus/sanitize.go @@ -15,8 +15,7 @@ package prometheus import ( - "strings" - "unicode" + "github.com/prometheus/statsd_exporter/pkg/mapper" ) const labelKeySizeLimit = 100 @@ -30,21 +29,10 @@ func sanitize(s string) string { if len(s) > labelKeySizeLimit { s = s[:labelKeySizeLimit] } - s = strings.Map(sanitizeRune, s) - if unicode.IsDigit(rune(s[0])) { - s = "key_" + s - } + + s = mapper.EscapeMetricName(s) if s[0] == '_' { s = "key" + s } return s } - -// converts anything that is not a letter or digit to an underscore -func sanitizeRune(r rune) rune { - if unicode.IsLetter(r) || unicode.IsDigit(r) { - return r - } - // Everything else turns into an underscore - return '_' -} diff --git a/vendor/contrib.go.opencensus.io/exporter/stackdriver/go.mod b/vendor/contrib.go.opencensus.io/exporter/stackdriver/go.mod index 12bcbace..bdcd72ad 100644 --- a/vendor/contrib.go.opencensus.io/exporter/stackdriver/go.mod +++ b/vendor/contrib.go.opencensus.io/exporter/stackdriver/go.mod @@ -6,21 +6,18 @@ require ( cloud.google.com/go v0.45.1 github.com/aws/aws-sdk-go v1.23.20 github.com/census-instrumentation/opencensus-proto v0.2.1 - github.com/davecgh/go-spew v1.1.1 // indirect github.com/golang/protobuf v1.3.2 github.com/google/go-cmp v0.3.1 github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024 - github.com/stretchr/testify v1.3.0 // indirect - go.opencensus.io v0.22.1 + go.opencensus.io v0.22.4 golang.org/x/lint v0.0.0-20190409202823-959b441ac422 - golang.org/x/net v0.0.0-20190912160710-24e19bdeb0f2 + golang.org/x/net v0.0.0-20190923162816-aa69164e4478 golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e // indirect - golang.org/x/sys v0.0.0-20190912141932-bc967efca4b8 // indirect - golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0 + golang.org/x/tools v0.0.0-20191010075000-0337d82405ff google.golang.org/api v0.10.0 google.golang.org/appengine v1.6.2 // indirect google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51 google.golang.org/grpc v1.23.1 - honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc + honnef.co/go/tools v0.0.1-2019.2.3 ) diff --git a/vendor/contrib.go.opencensus.io/exporter/stackdriver/go.sum b/vendor/contrib.go.opencensus.io/exporter/stackdriver/go.sum index 5c5a219b..e219b190 100644 --- a/vendor/contrib.go.opencensus.io/exporter/stackdriver/go.sum +++ b/vendor/contrib.go.opencensus.io/exporter/stackdriver/go.sum @@ -20,8 +20,6 @@ github.com/client9/misspell v0.3.4 h1:ta993UF76GwbvJcIo3Y68y/M3WxlpEHPWIGDkJYwzJ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6 h1:ZgQEtGgCBiWRM39fZuwSd1LwSqqSW0hOdXCYYDX0R3I= @@ -46,6 +44,7 @@ github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/googleapis/gax-go/v2 v2.0.4 h1:hU4mGcQI4DaAYW+IbTun+2qEZVFxK0ySjQLTbS0VQKc= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM= @@ -58,17 +57,25 @@ github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af h1:pmfjZENx5i github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024 h1:rBMNdlhTLzJjJSDIjNEXX1Pz3Hmwmz91v+zycvx9PJc= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0 h1:C9hSCOW830chIVkdja34wa6Ky+IzWllkUinR+BtRZd4= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= -go.opencensus.io v0.22.1 h1:8dP3SGL7MPB94crU3bEPplMPe83FI4EouesJUeFHv50= -go.opencensus.io v0.22.1/go.mod h1:Ap50jQcDJrx6rB6VgeeFPtuPIf3wMRvRfrfYDO6+BmA= +go.opencensus.io v0.22.4 h1:LYy1Hy3MJdrCdMwwzxA/dRok4ejH+RwNGbuoD9fCjto= +go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -80,6 +87,7 @@ golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHl golang.org/x/lint v0.0.0-20190409202823-959b441ac422 h1:QzoH/1pFpZguR8NrRHLcO6jKqfv2zpuSqZLgdm7ZmjI= golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -92,8 +100,8 @@ golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190912160710-24e19bdeb0f2 h1:4dVFTC832rPn4pomLSz1vA+are2+dU19w1H8OngV7nc= -golang.org/x/net v0.0.0-20190912160710-24e19bdeb0f2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190923162816-aa69164e4478 h1:l5EDrHhldLYb3ZRHDUhXF7Om7MvYXnkV9/iQNo1lX6g= +golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421 h1:Wo7BWFiOk0QRFMLYMqJGFMd9CgUAcGx7V+qEg/h5IBI= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -118,9 +126,8 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0 h1:HyfiK1WMnHj5FXFXatD+Qs1A/xC2Run6RzeW1SyHxpc= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190912141932-bc967efca4b8 h1:41hwlulw1prEMBxLQSlMSux1zxJf07B3WPsdjJlKZxE= -golang.org/x/sys v0.0.0-20190912141932-bc967efca4b8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2 h1:z99zHgr7hKfrUcX/KsoJk5FJfjTceCKIp96+biqP4To= @@ -139,8 +146,12 @@ golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBn golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0 h1:Dh6fw+p6FyRl5x/FvNswO1ji0lIGzm3KP8Y9VkS9PTE= golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20191010075000-0337d82405ff h1:XdBG6es/oFDr1HwaxkxgVve7NB281QhxgK/i4voubFs= +golang.org/x/tools v0.0.0-20191010075000-0337d82405ff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/api v0.4.0 h1:KKgc1aqhV8wDPbDzlDtpvyjZFY3vjz85FP7p4wcQUyI= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0 h1:9sdfJOzWlkqPltHAuzT2Cp+yrBeY1KRVYgms8soxMwM= @@ -176,9 +187,17 @@ google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiq google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.23.1 h1:q4XQuHFC6I28BKZpo6IYyb3mNO+l7lSOxRuYTCiDfXk= google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc h1:/hemPrYIhOhy8zYrNj+069zDB68us2sMGsfkFJO0iZs= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= diff --git a/vendor/contrib.go.opencensus.io/exporter/stackdriver/metrics.go b/vendor/contrib.go.opencensus.io/exporter/stackdriver/metrics.go index a2df93f0..8a185b97 100644 --- a/vendor/contrib.go.opencensus.io/exporter/stackdriver/metrics.go +++ b/vendor/contrib.go.opencensus.io/exporter/stackdriver/metrics.go @@ -212,7 +212,9 @@ func metricLabelsToTsLabels(defaults map[string]labelValue, labelKeys []metricda for i, labelKey := range labelKeys { labelValue := labelValues[i] - labels[sanitize(labelKey.Key)] = labelValue.Value + if labelValue.Present { + labels[sanitize(labelKey.Key)] = labelValue.Value + } } return labels, nil diff --git a/vendor/contrib.go.opencensus.io/exporter/stackdriver/monitoredresource/aws_identity_doc_utils.go b/vendor/contrib.go.opencensus.io/exporter/stackdriver/monitoredresource/aws/aws_identity_doc_utils.go similarity index 96% rename from vendor/contrib.go.opencensus.io/exporter/stackdriver/monitoredresource/aws_identity_doc_utils.go rename to vendor/contrib.go.opencensus.io/exporter/stackdriver/monitoredresource/aws/aws_identity_doc_utils.go index ee519a4b..f83b4027 100644 --- a/vendor/contrib.go.opencensus.io/exporter/stackdriver/monitoredresource/aws_identity_doc_utils.go +++ b/vendor/contrib.go.opencensus.io/exporter/stackdriver/monitoredresource/aws/aws_identity_doc_utils.go @@ -1,4 +1,4 @@ -// Copyright 2018, OpenCensus Authors +// Copyright 2020, OpenCensus Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package monitoredresource +package aws import ( "github.com/aws/aws-sdk-go/aws/ec2metadata" diff --git a/vendor/contrib.go.opencensus.io/exporter/stackdriver/monitoredresource/aws/monitored_resources.go b/vendor/contrib.go.opencensus.io/exporter/stackdriver/monitoredresource/aws/monitored_resources.go new file mode 100644 index 00000000..5e4cde50 --- /dev/null +++ b/vendor/contrib.go.opencensus.io/exporter/stackdriver/monitoredresource/aws/monitored_resources.go @@ -0,0 +1,97 @@ +// Copyright 2020, OpenCensus Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package aws + +import ( + "fmt" + "sync" +) + +// Interface is a type that represent monitor resource that satisfies monitoredresource.Interface +type Interface interface { + + // MonitoredResource returns the resource type and resource labels. + MonitoredResource() (resType string, labels map[string]string) +} + +// EC2Instance represents aws_ec2_instance type monitored resource. +// For definition refer to +// https://cloud.google.com/monitoring/api/resources#tag_aws_ec2_instance +type EC2Instance struct { + + // AWSAccount is the AWS account number for the VM. + AWSAccount string + + // InstanceID is the instance id of the instance. + InstanceID string + + // Region is the AWS region for the VM. The format of this field is "aws:{region}", + // where supported values for {region} are listed at + // http://docs.aws.amazon.com/general/latest/gr/rande.html. + Region string +} + +// MonitoredResource returns resource type and resource labels for AWSEC2Instance +func (aws *EC2Instance) MonitoredResource() (resType string, labels map[string]string) { + labels = map[string]string{ + "aws_account": aws.AWSAccount, + "instance_id": aws.InstanceID, + "region": aws.Region, + } + return "aws_ec2_instance", labels +} + +// Autodetect auto detects monitored resources based on +// the environment where the application is running. +// It supports detection of following resource types +// 1. aws_ec2_instance: +// +// Returns MonitoredResInterface which implements getLabels() and getType() +// For resource definition go to https://cloud.google.com/monitoring/api/resources +func Autodetect() Interface { + return func() Interface { + detectOnce.Do(func() { + autoDetected = detectResourceType(retrieveAWSIdentityDocument()) + }) + return autoDetected + }() + +} + +// createAWSEC2InstanceMonitoredResource creates a aws_ec2_instance monitored resource +// awsIdentityDoc contains AWS EC2 specific attributes. +func createEC2InstanceMonitoredResource(awsIdentityDoc *awsIdentityDocument) *EC2Instance { + awsInstance := EC2Instance{ + AWSAccount: awsIdentityDoc.accountID, + InstanceID: awsIdentityDoc.instanceID, + Region: fmt.Sprintf("aws:%s", awsIdentityDoc.region), + } + return &awsInstance +} + +// detectOnce is used to make sure AWS metadata detect function executes only once. +var detectOnce sync.Once + +// autoDetected is the metadata detected after the first execution of Autodetect function. +var autoDetected Interface + +// detectResourceType determines the resource type. +// awsIdentityDoc contains AWS EC2 attributes. nil if it is not AWS EC2 environment +func detectResourceType(awsIdentityDoc *awsIdentityDocument) Interface { + if awsIdentityDoc != nil { + return createEC2InstanceMonitoredResource(awsIdentityDoc) + } + return nil +} diff --git a/vendor/contrib.go.opencensus.io/exporter/stackdriver/monitoredresource/deprecated.go b/vendor/contrib.go.opencensus.io/exporter/stackdriver/monitoredresource/deprecated.go new file mode 100644 index 00000000..fec5ec12 --- /dev/null +++ b/vendor/contrib.go.opencensus.io/exporter/stackdriver/monitoredresource/deprecated.go @@ -0,0 +1,101 @@ +// Copyright 2020, OpenCensus Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package monitoredresource + +import ( + "contrib.go.opencensus.io/exporter/stackdriver/monitoredresource/aws" + "contrib.go.opencensus.io/exporter/stackdriver/monitoredresource/gcp" +) + +// GKEContainer represents gke_container type monitored resource. +// For definition refer to +// https://cloud.google.com/monitoring/api/resources#tag_gke_container +// Deprecated: please use gcp.GKEContainer from "contrib.go.opencensus.io/exporter/stackdriver/monitoredresource/gcp". +type GKEContainer struct { + // ProjectID is the identifier of the GCP project associated with this resource, such as "my-project". + ProjectID string + + // InstanceID is the numeric VM instance identifier assigned by Compute Engine. + InstanceID string + + // ClusterName is the name for the cluster the container is running in. + ClusterName string + + // ContainerName is the name of the container. + ContainerName string + + // NamespaceID is the identifier for the cluster namespace the container is running in + NamespaceID string + + // PodID is the identifier for the pod the container is running in. + PodID string + + // Zone is the Compute Engine zone in which the VM is running. + Zone string + + // LoggingMonitoringV2Enabled is the identifier if user enabled V2 logging and monitoring for GKE + LoggingMonitoringV2Enabled bool +} + +// MonitoredResource returns resource type and resource labels for GKEContainer +func (gke *GKEContainer) MonitoredResource() (resType string, labels map[string]string) { + gcpGKE := gcp.GKEContainer(*gke) + return gcpGKE.MonitoredResource() +} + +// GCEInstance represents gce_instance type monitored resource. +// For definition refer to +// https://cloud.google.com/monitoring/api/resources#tag_gce_instance +// Deprecated: please use gcp.GCEInstance from "contrib.go.opencensus.io/exporter/stackdriver/monitoredresource/gcp". +type GCEInstance struct { + + // ProjectID is the identifier of the GCP project associated with this resource, such as "my-project". + ProjectID string + + // InstanceID is the numeric VM instance identifier assigned by Compute Engine. + InstanceID string + + // Zone is the Compute Engine zone in which the VM is running. + Zone string +} + +// MonitoredResource returns resource type and resource labels for GCEInstance +func (gce *GCEInstance) MonitoredResource() (resType string, labels map[string]string) { + gcpGCE := gcp.GCEInstance(*gce) + return gcpGCE.MonitoredResource() +} + +// AWSEC2Instance represents aws_ec2_instance type monitored resource. +// For definition refer to +// https://cloud.google.com/monitoring/api/resources#tag_aws_ec2_instance +// Deprecated: please use aws.EC2Container from "contrib.go.opencensus.io/exporter/stackdriver/monitoredresource/aws". +type AWSEC2Instance struct { + // AWSAccount is the AWS account number for the VM. + AWSAccount string + + // InstanceID is the instance id of the instance. + InstanceID string + + // Region is the AWS region for the VM. The format of this field is "aws:{region}", + // where supported values for {region} are listed at + // http://docs.aws.amazon.com/general/latest/gr/rande.html. + Region string +} + +// MonitoredResource returns resource type and resource labels for AWSEC2Instance +func (ec2 *AWSEC2Instance) MonitoredResource() (resType string, labels map[string]string) { + awsEC2 := aws.EC2Instance(*ec2) + return awsEC2.MonitoredResource() +} diff --git a/vendor/contrib.go.opencensus.io/exporter/stackdriver/monitoredresource/gcp_metadata_config.go b/vendor/contrib.go.opencensus.io/exporter/stackdriver/monitoredresource/gcp/gcp_metadata_config.go similarity index 98% rename from vendor/contrib.go.opencensus.io/exporter/stackdriver/monitoredresource/gcp_metadata_config.go rename to vendor/contrib.go.opencensus.io/exporter/stackdriver/monitoredresource/gcp/gcp_metadata_config.go index f0d88856..ccaade65 100644 --- a/vendor/contrib.go.opencensus.io/exporter/stackdriver/monitoredresource/gcp_metadata_config.go +++ b/vendor/contrib.go.opencensus.io/exporter/stackdriver/monitoredresource/gcp/gcp_metadata_config.go @@ -1,4 +1,4 @@ -// Copyright 2018, OpenCensus Authors +// Copyright 2020, OpenCensus Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package monitoredresource +package gcp import ( "context" diff --git a/vendor/contrib.go.opencensus.io/exporter/stackdriver/monitoredresource/gcp/monitored_resources.go b/vendor/contrib.go.opencensus.io/exporter/stackdriver/monitoredresource/gcp/monitored_resources.go new file mode 100644 index 00000000..1a08b1c8 --- /dev/null +++ b/vendor/contrib.go.opencensus.io/exporter/stackdriver/monitoredresource/gcp/monitored_resources.go @@ -0,0 +1,168 @@ +// Copyright 2020, OpenCensus Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package gcp + +import ( + "os" + "sync" +) + +// Interface is a type that represent monitor resource that satisfies monitoredresource.Interface +type Interface interface { + + // MonitoredResource returns the resource type and resource labels. + MonitoredResource() (resType string, labels map[string]string) +} + +// GKEContainer represents gke_container type monitored resource. +// For definition refer to +// https://cloud.google.com/monitoring/api/resources#tag_gke_container +type GKEContainer struct { + + // ProjectID is the identifier of the GCP project associated with this resource, such as "my-project". + ProjectID string + + // InstanceID is the numeric VM instance identifier assigned by Compute Engine. + InstanceID string + + // ClusterName is the name for the cluster the container is running in. + ClusterName string + + // ContainerName is the name of the container. + ContainerName string + + // NamespaceID is the identifier for the cluster namespace the container is running in + NamespaceID string + + // PodID is the identifier for the pod the container is running in. + PodID string + + // Zone is the Compute Engine zone in which the VM is running. + Zone string + + // LoggingMonitoringV2Enabled is the identifier if user enabled V2 logging and monitoring for GKE + LoggingMonitoringV2Enabled bool +} + +// MonitoredResource returns resource type and resource labels for GKEContainer +func (gke *GKEContainer) MonitoredResource() (resType string, labels map[string]string) { + labels = map[string]string{ + "project_id": gke.ProjectID, + "cluster_name": gke.ClusterName, + "container_name": gke.ContainerName, + } + var typ string + if gke.LoggingMonitoringV2Enabled { + typ = "k8s_container" + labels["pod_name"] = gke.PodID + labels["namespace_name"] = gke.NamespaceID + labels["location"] = gke.Zone + } else { + typ = "gke_container" + labels["pod_id"] = gke.PodID + labels["namespace_id"] = gke.NamespaceID + labels["zone"] = gke.Zone + labels["instance_id"] = gke.InstanceID + } + return typ, labels +} + +// GCEInstance represents gce_instance type monitored resource. +// For definition refer to +// https://cloud.google.com/monitoring/api/resources#tag_gce_instance +type GCEInstance struct { + + // ProjectID is the identifier of the GCP project associated with this resource, such as "my-project". + ProjectID string + + // InstanceID is the numeric VM instance identifier assigned by Compute Engine. + InstanceID string + + // Zone is the Compute Engine zone in which the VM is running. + Zone string +} + +// MonitoredResource returns resource type and resource labels for GCEInstance +func (gce *GCEInstance) MonitoredResource() (resType string, labels map[string]string) { + labels = map[string]string{ + "project_id": gce.ProjectID, + "instance_id": gce.InstanceID, + "zone": gce.Zone, + } + return "gce_instance", labels +} + +// Autodetect auto detects monitored resources based on +// the environment where the application is running. +// It supports detection of following resource types +// 1. gke_container: +// 2. gce_instance: +// +// Returns MonitoredResInterface which implements getLabels() and getType() +// For resource definition go to https://cloud.google.com/monitoring/api/resources +func Autodetect() Interface { + return func() Interface { + detectOnce.Do(func() { + autoDetected = detectResourceType(retrieveGCPMetadata()) + }) + return autoDetected + }() + +} + +// createGCEInstanceMonitoredResource creates a gce_instance monitored resource +// gcpMetadata contains GCP (GKE or GCE) specific attributes. +func createGCEInstanceMonitoredResource(gcpMetadata *gcpMetadata) *GCEInstance { + gceInstance := GCEInstance{ + ProjectID: gcpMetadata.projectID, + InstanceID: gcpMetadata.instanceID, + Zone: gcpMetadata.zone, + } + return &gceInstance +} + +// createGKEContainerMonitoredResource creates a gke_container monitored resource +// gcpMetadata contains GCP (GKE or GCE) specific attributes. +func createGKEContainerMonitoredResource(gcpMetadata *gcpMetadata) *GKEContainer { + gkeContainer := GKEContainer{ + ProjectID: gcpMetadata.projectID, + InstanceID: gcpMetadata.instanceID, + Zone: gcpMetadata.zone, + ContainerName: gcpMetadata.containerName, + ClusterName: gcpMetadata.clusterName, + NamespaceID: gcpMetadata.namespaceID, + PodID: gcpMetadata.podID, + LoggingMonitoringV2Enabled: gcpMetadata.monitoringV2, + } + return &gkeContainer +} + +// detectOnce is used to make sure GCP metadata detect function executes only once. +var detectOnce sync.Once + +// autoDetected is the metadata detected after the first execution of Autodetect function. +var autoDetected Interface + +// detectResourceType determines the resource type. +// gcpMetadata contains GCP (GKE or GCE) specific attributes. +func detectResourceType(gcpMetadata *gcpMetadata) Interface { + if os.Getenv("KUBERNETES_SERVICE_HOST") != "" && + gcpMetadata != nil && gcpMetadata.instanceID != "" { + return createGKEContainerMonitoredResource(gcpMetadata) + } else if gcpMetadata != nil && gcpMetadata.instanceID != "" { + return createGCEInstanceMonitoredResource(gcpMetadata) + } + return nil +} diff --git a/vendor/contrib.go.opencensus.io/exporter/stackdriver/monitoredresource/monitored_resources.go b/vendor/contrib.go.opencensus.io/exporter/stackdriver/monitoredresource/monitored_resources.go index 86e76002..46943eb1 100644 --- a/vendor/contrib.go.opencensus.io/exporter/stackdriver/monitoredresource/monitored_resources.go +++ b/vendor/contrib.go.opencensus.io/exporter/stackdriver/monitoredresource/monitored_resources.go @@ -1,4 +1,4 @@ -// Copyright 2018, OpenCensus Authors +// Copyright 2020, OpenCensus Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -15,123 +15,18 @@ package monitoredresource import ( - "fmt" - "os" "sync" + + "contrib.go.opencensus.io/exporter/stackdriver/monitoredresource/aws" + "contrib.go.opencensus.io/exporter/stackdriver/monitoredresource/gcp" ) // Interface is a type that represent monitor resource that satisfies monitoredresource.Interface type Interface interface { - // MonitoredResource returns the resource type and resource labels. MonitoredResource() (resType string, labels map[string]string) } -// GKEContainer represents gke_container type monitored resource. -// For definition refer to -// https://cloud.google.com/monitoring/api/resources#tag_gke_container -type GKEContainer struct { - - // ProjectID is the identifier of the GCP project associated with this resource, such as "my-project". - ProjectID string - - // InstanceID is the numeric VM instance identifier assigned by Compute Engine. - InstanceID string - - // ClusterName is the name for the cluster the container is running in. - ClusterName string - - // ContainerName is the name of the container. - ContainerName string - - // NamespaceID is the identifier for the cluster namespace the container is running in - NamespaceID string - - // PodID is the identifier for the pod the container is running in. - PodID string - - // Zone is the Compute Engine zone in which the VM is running. - Zone string - - // LoggingMonitoringV2Enabled is the identifier if user enabled V2 logging and monitoring for GKE - LoggingMonitoringV2Enabled bool -} - -// MonitoredResource returns resource type and resource labels for GKEContainer -func (gke *GKEContainer) MonitoredResource() (resType string, labels map[string]string) { - labels = map[string]string{ - "project_id": gke.ProjectID, - "cluster_name": gke.ClusterName, - "container_name": gke.ContainerName, - } - var typ string - if gke.LoggingMonitoringV2Enabled { - typ = "k8s_container" - labels["pod_name"] = gke.PodID - labels["namespace_name"] = gke.NamespaceID - labels["location"] = gke.Zone - } else { - typ = "gke_container" - labels["pod_id"] = gke.PodID - labels["namespace_id"] = gke.NamespaceID - labels["zone"] = gke.Zone - labels["instance_id"] = gke.InstanceID - } - return typ, labels -} - -// GCEInstance represents gce_instance type monitored resource. -// For definition refer to -// https://cloud.google.com/monitoring/api/resources#tag_gce_instance -type GCEInstance struct { - - // ProjectID is the identifier of the GCP project associated with this resource, such as "my-project". - ProjectID string - - // InstanceID is the numeric VM instance identifier assigned by Compute Engine. - InstanceID string - - // Zone is the Compute Engine zone in which the VM is running. - Zone string -} - -// MonitoredResource returns resource type and resource labels for GCEInstance -func (gce *GCEInstance) MonitoredResource() (resType string, labels map[string]string) { - labels = map[string]string{ - "project_id": gce.ProjectID, - "instance_id": gce.InstanceID, - "zone": gce.Zone, - } - return "gce_instance", labels -} - -// AWSEC2Instance represents aws_ec2_instance type monitored resource. -// For definition refer to -// https://cloud.google.com/monitoring/api/resources#tag_aws_ec2_instance -type AWSEC2Instance struct { - - // AWSAccount is the AWS account number for the VM. - AWSAccount string - - // InstanceID is the instance id of the instance. - InstanceID string - - // Region is the AWS region for the VM. The format of this field is "aws:{region}", - // where supported values for {region} are listed at - // http://docs.aws.amazon.com/general/latest/gr/rande.html. - Region string -} - -// MonitoredResource returns resource type and resource labels for AWSEC2Instance -func (aws *AWSEC2Instance) MonitoredResource() (resType string, labels map[string]string) { - labels = map[string]string{ - "aws_account": aws.AWSAccount, - "instance_id": aws.InstanceID, - "region": aws.Region, - } - return "aws_ec2_instance", labels -} - // Autodetect auto detects monitored resources based on // the environment where the application is running. // It supports detection of following resource types @@ -144,9 +39,7 @@ func (aws *AWSEC2Instance) MonitoredResource() (resType string, labels map[strin func Autodetect() Interface { return func() Interface { detectOnce.Do(func() { - var awsIdentityDoc *awsIdentityDocument - var gcpMetadata *gcpMetadata - + var awsDetect, gcpDetect Interface // First attempts to retrieve AWS Identity Doc and GCP metadata. // It then determines the resource type // In GCP and AWS environment both func finishes quickly. However, @@ -157,57 +50,21 @@ func Autodetect() Interface { go func() { defer wg.Done() - awsIdentityDoc = retrieveAWSIdentityDocument() + awsDetect = aws.Autodetect() }() go func() { defer wg.Done() - gcpMetadata = retrieveGCPMetadata() + gcpDetect = gcp.Autodetect() }() wg.Wait() - autoDetected = detectResourceType(awsIdentityDoc, gcpMetadata) + autoDetected = awsDetect + if gcpDetect != nil { + autoDetected = gcpDetect + } }) return autoDetected }() - -} - -// createAWSEC2InstanceMonitoredResource creates a aws_ec2_instance monitored resource -// awsIdentityDoc contains AWS EC2 specific attributes. -func createAWSEC2InstanceMonitoredResource(awsIdentityDoc *awsIdentityDocument) *AWSEC2Instance { - awsInstance := AWSEC2Instance{ - AWSAccount: awsIdentityDoc.accountID, - InstanceID: awsIdentityDoc.instanceID, - Region: fmt.Sprintf("aws:%s", awsIdentityDoc.region), - } - return &awsInstance -} - -// createGCEInstanceMonitoredResource creates a gce_instance monitored resource -// gcpMetadata contains GCP (GKE or GCE) specific attributes. -func createGCEInstanceMonitoredResource(gcpMetadata *gcpMetadata) *GCEInstance { - gceInstance := GCEInstance{ - ProjectID: gcpMetadata.projectID, - InstanceID: gcpMetadata.instanceID, - Zone: gcpMetadata.zone, - } - return &gceInstance -} - -// createGKEContainerMonitoredResource creates a gke_container monitored resource -// gcpMetadata contains GCP (GKE or GCE) specific attributes. -func createGKEContainerMonitoredResource(gcpMetadata *gcpMetadata) *GKEContainer { - gkeContainer := GKEContainer{ - ProjectID: gcpMetadata.projectID, - InstanceID: gcpMetadata.instanceID, - Zone: gcpMetadata.zone, - ContainerName: gcpMetadata.containerName, - ClusterName: gcpMetadata.clusterName, - NamespaceID: gcpMetadata.namespaceID, - PodID: gcpMetadata.podID, - LoggingMonitoringV2Enabled: gcpMetadata.monitoringV2, - } - return &gkeContainer } // detectOnce is used to make sure GCP and AWS metadata detect function executes only once. @@ -215,18 +72,3 @@ var detectOnce sync.Once // autoDetected is the metadata detected after the first execution of Autodetect function. var autoDetected Interface - -// detectResourceType determines the resource type. -// awsIdentityDoc contains AWS EC2 attributes. nil if it is not AWS EC2 environment -// gcpMetadata contains GCP (GKE or GCE) specific attributes. -func detectResourceType(awsIdentityDoc *awsIdentityDocument, gcpMetadata *gcpMetadata) Interface { - if os.Getenv("KUBERNETES_SERVICE_HOST") != "" && - gcpMetadata != nil && gcpMetadata.instanceID != "" { - return createGKEContainerMonitoredResource(gcpMetadata) - } else if gcpMetadata != nil && gcpMetadata.instanceID != "" { - return createGCEInstanceMonitoredResource(gcpMetadata) - } else if awsIdentityDoc != nil { - return createAWSEC2InstanceMonitoredResource(awsIdentityDoc) - } - return nil -} diff --git a/vendor/contrib.go.opencensus.io/exporter/stackdriver/resource.go b/vendor/contrib.go.opencensus.io/exporter/stackdriver/resource.go index 782011cb..e32c4dab 100644 --- a/vendor/contrib.go.opencensus.io/exporter/stackdriver/resource.go +++ b/vendor/contrib.go.opencensus.io/exporter/stackdriver/resource.go @@ -1,4 +1,4 @@ -// Copyright 2019, OpenCensus Authors +// Copyright 2020, OpenCensus Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -16,7 +16,9 @@ package stackdriver // import "contrib.go.opencensus.io/exporter/stackdriver" import ( "fmt" + "sync" + "contrib.go.opencensus.io/exporter/stackdriver/monitoredresource/gcp" "go.opencensus.io/resource" "go.opencensus.io/resource/resourcekeys" monitoredrespb "google.golang.org/genproto/googleapis/api/monitoredres" @@ -26,14 +28,47 @@ import ( // Consider exposing these labels and a type identifier in the future to allow // for customization. const ( - stackdriverLocation = "contrib.opencensus.io/exporter/stackdriver/location" stackdriverProjectID = "contrib.opencensus.io/exporter/stackdriver/project_id" + stackdriverLocation = "contrib.opencensus.io/exporter/stackdriver/location" + stackdriverClusterName = "contrib.opencesus.io/exporter/stackdriver/cluster_name" stackdriverGenericTaskNamespace = "contrib.opencensus.io/exporter/stackdriver/generic_task/namespace" stackdriverGenericTaskJob = "contrib.opencensus.io/exporter/stackdriver/generic_task/job" stackdriverGenericTaskID = "contrib.opencensus.io/exporter/stackdriver/generic_task/task_id" + + knativeResType = "knative_revision" + knativeServiceName = "service_name" + knativeRevisionName = "revision_name" + knativeConfigurationName = "configuration_name" + knativeNamespaceName = "namespace_name" + + appEngineInstanceType = "gae_instance" + + appEngineService = "appengine.service.id" + appEngineVersion = "appengine.version.id" + appEngineInstance = "appengine.instance.id" ) -// Mappings for the well-known OpenCensus resources to applicable Stackdriver resources. +var ( + // autodetectFunc returns a monitored resource that is autodetected. + // from the cloud environment at runtime. + autodetectFunc func() gcp.Interface + + // autodetectOnce is used to lazy initialize autodetectedLabels. + autodetectOnce *sync.Once + // autodetectedLabels stores all the labels from the autodetected monitored resource + // with a possible additional label for the GCP "location". + autodetectedLabels map[string]string +) + +func init() { + autodetectFunc = gcp.Autodetect + // monitoredresource.Autodetect only makes calls to the metadata APIs once + // and caches the results + autodetectOnce = new(sync.Once) +} + +// Mappings for the well-known OpenCensus resource label keys +// to applicable Stackdriver Monitored Resource label keys. var k8sContainerMap = map[string]string{ "project_id": stackdriverProjectID, "location": resourcekeys.CloudKeyZone, @@ -71,32 +106,83 @@ var awsResourceMap = map[string]string{ "aws_account": resourcekeys.CloudKeyAccountID, } +var appEngineInstanceMap = map[string]string{ + "project_id": stackdriverProjectID, + "location": resourcekeys.CloudKeyRegion, + "module_id": appEngineService, + "version_id": appEngineVersion, + "instance_id": appEngineInstance, +} + // Generic task resource. var genericResourceMap = map[string]string{ "project_id": stackdriverProjectID, - "location": stackdriverLocation, + "location": resourcekeys.CloudKeyZone, "namespace": stackdriverGenericTaskNamespace, "job": stackdriverGenericTaskJob, "task_id": stackdriverGenericTaskID, } +var knativeRevisionResourceMap = map[string]string{ + "project_id": stackdriverProjectID, + "location": resourcekeys.CloudKeyZone, + "cluster_name": resourcekeys.K8SKeyClusterName, + knativeServiceName: knativeServiceName, + knativeRevisionName: knativeRevisionName, + knativeConfigurationName: knativeConfigurationName, + knativeNamespaceName: knativeNamespaceName, +} + +// getAutodetectedLabels returns all the labels from the Monitored Resource detected +// from the environment by calling monitoredresource.Autodetect. If a "zone" label is detected, +// a "location" label is added with the same value to account for differences between +// Legacy Stackdriver and Stackdriver Kubernetes Engine Monitoring, +// see https://cloud.google.com/monitoring/kubernetes-engine/migration. +func getAutodetectedLabels() map[string]string { + autodetectOnce.Do(func() { + autodetectedLabels = map[string]string{} + if mr := autodetectFunc(); mr != nil { + _, labels := mr.MonitoredResource() + // accept "zone" value for "location" because values for location can be a zone + // or region, see https://cloud.google.com/docs/geography-and-regions + if _, ok := labels["zone"]; ok { + labels["location"] = labels["zone"] + } + + autodetectedLabels = labels + } + }) + + return autodetectedLabels +} + // returns transformed label map and true if all labels in match are found // in input except optional project_id. It returns false if at least one label // other than project_id is missing. func transformResource(match, input map[string]string) (map[string]string, bool) { output := make(map[string]string, len(input)) for dst, src := range match { - v, ok := input[src] - if ok { + if v, ok := input[src]; ok { output[dst] = v - } else if dst != "project_id" { + continue + } + + // attempt to autodetect missing labels, autodetected label keys should + // match destination label keys + if v, ok := getAutodetectedLabels()[dst]; ok { + output[dst] = v + continue + } + + if dst != "project_id" { return nil, true } } return output, false } -func defaultMapResource(res *resource.Resource) *monitoredrespb.MonitoredResource { +// DefaultMapResource implements default resource mapping for well-known resource types +func DefaultMapResource(res *resource.Resource) *monitoredrespb.MonitoredResource { match := genericResourceMap result := &monitoredrespb.MonitoredResource{ Type: "global", @@ -115,12 +201,18 @@ func defaultMapResource(res *resource.Resource) *monitoredrespb.MonitoredResourc case res.Type == resourcekeys.HostType && res.Labels[resourcekeys.K8SKeyClusterName] != "": result.Type = "k8s_node" match = k8sNodeMap + case res.Type == appEngineInstanceType: + result.Type = appEngineInstanceType + match = appEngineInstanceMap case res.Labels[resourcekeys.CloudKeyProvider] == resourcekeys.CloudProviderGCP: result.Type = "gce_instance" match = gcpResourceMap case res.Labels[resourcekeys.CloudKeyProvider] == resourcekeys.CloudProviderAWS: result.Type = "aws_ec2_instance" match = awsResourceMap + case res.Type == knativeResType: + result.Type = res.Type + match = knativeRevisionResourceMap } var missing bool diff --git a/vendor/contrib.go.opencensus.io/exporter/stackdriver/stackdriver.go b/vendor/contrib.go.opencensus.io/exporter/stackdriver/stackdriver.go index fafd06c2..fc969553 100644 --- a/vendor/contrib.go.opencensus.io/exporter/stackdriver/stackdriver.go +++ b/vendor/contrib.go.opencensus.io/exporter/stackdriver/stackdriver.go @@ -61,6 +61,7 @@ import ( traceapi "cloud.google.com/go/trace/apiv2" "contrib.go.opencensus.io/exporter/stackdriver/monitoredresource" "go.opencensus.io/resource" + "go.opencensus.io/resource/resourcekeys" "go.opencensus.io/stats/view" "go.opencensus.io/trace" "golang.org/x/oauth2/google" @@ -182,7 +183,7 @@ type Options struct { // MapResource converts a OpenCensus resource to a Stackdriver monitored resource. // - // If this field is unset, defaultMapResource will be used which encodes a set of default + // If this field is unset, DefaultMapResource will be used which encodes a set of default // conversions from auto-detected resources to well-known Stackdriver monitored resources. MapResource func(*resource.Resource) *monitoredrespb.MonitoredResource @@ -258,8 +259,7 @@ type Options struct { ReportingInterval time.Duration // NumberOfWorkers sets the number of go rountines that send requests - // to Stackdriver Monitoring. This is only used for Proto metrics export - // for now. The minimum number of workers is 1. + // to Stackdriver Monitoring and Trace. The minimum number of workers is 1. NumberOfWorkers int // ResourceByDescriptor may be provided to supply monitored resource dynamically @@ -333,7 +333,7 @@ func NewExporter(o Options) (*Exporter, error) { o.Resource = convertMonitoredResourceToPB(o.MonitoredResource) } if o.MapResource == nil { - o.MapResource = defaultMapResource + o.MapResource = DefaultMapResource } if o.ResourceDetector != nil { // For backwards-compatibility we still respect the deprecated resource field. @@ -346,13 +346,18 @@ func NewExporter(o Options) (*Exporter, error) { } // Populate internal resource labels for defaulting project_id, location, and // generic resource labels of applicable monitored resources. + if res.Labels == nil { + res.Labels = make(map[string]string) + } res.Labels[stackdriverProjectID] = o.ProjectID - res.Labels[stackdriverLocation] = o.Location + res.Labels[resourcekeys.CloudKeyZone] = o.Location res.Labels[stackdriverGenericTaskNamespace] = "default" res.Labels[stackdriverGenericTaskJob] = path.Base(os.Args[0]) res.Labels[stackdriverGenericTaskID] = getTaskValue() + log.Printf("OpenCensus detected resource: %v", res) o.Resource = o.MapResource(res) + log.Printf("OpenCensus using monitored resource: %v", o.Resource) } if o.MetricPrefix != "" && !strings.HasSuffix(o.MetricPrefix, "/") { o.MetricPrefix = o.MetricPrefix + "/" @@ -427,6 +432,12 @@ func (e *Exporter) ExportSpan(sd *trace.SpanData) { e.traceExporter.ExportSpan(sd) } +// PushTraceSpans exports a bundle of OpenCensus Spans. +// Returns number of dropped spans. +func (e *Exporter) PushTraceSpans(ctx context.Context, node *commonpb.Node, rsc *resourcepb.Resource, spans []*trace.SpanData) (int, error) { + return e.traceExporter.pushTraceSpans(ctx, node, rsc, spans) +} + func (e *Exporter) sdWithDefaultTraceAttributes(sd *trace.SpanData) *trace.SpanData { newSD := *sd newSD.Attributes = make(map[string]interface{}) diff --git a/vendor/contrib.go.opencensus.io/exporter/stackdriver/stats.go b/vendor/contrib.go.opencensus.io/exporter/stackdriver/stats.go index e0a02ca9..0dea6e40 100644 --- a/vendor/contrib.go.opencensus.io/exporter/stackdriver/stats.go +++ b/vendor/contrib.go.opencensus.io/exporter/stackdriver/stats.go @@ -536,7 +536,7 @@ func newTypedValue(vd *view.View, r *view.Row) *monitoringpb.TypedValue { } func shouldInsertZeroBound(bounds ...float64) bool { - if len(bounds) > 0 && bounds[0] != 0.0 { + if len(bounds) > 0 && bounds[0] > 0.0 { return true } return false diff --git a/vendor/contrib.go.opencensus.io/exporter/stackdriver/trace.go b/vendor/contrib.go.opencensus.io/exporter/stackdriver/trace.go index ee6535ee..ae9fa4e1 100644 --- a/vendor/contrib.go.opencensus.io/exporter/stackdriver/trace.go +++ b/vendor/contrib.go.opencensus.io/exporter/stackdriver/trace.go @@ -26,6 +26,9 @@ import ( "go.opencensus.io/trace" "google.golang.org/api/support/bundler" tracepb "google.golang.org/genproto/googleapis/devtools/cloudtrace/v2" + + commonpb "github.com/census-instrumentation/opencensus-proto/gen-go/agent/common/v1" + resourcepb "github.com/census-instrumentation/opencensus-proto/gen-go/resource/v1" ) // traceExporter is an implementation of trace.Exporter that uploads spans to @@ -76,6 +79,9 @@ func newTraceExporterWithClient(o Options, c *tracingclient.Client) *traceExport } else { b.BundleCountThreshold = 50 } + if o.NumberOfWorkers > 0 { + b.HandlerLimit = o.NumberOfWorkers + } // The measured "bytes" are not really bytes, see exportReceiver. b.BundleByteThreshold = b.BundleCountThreshold * 200 b.BundleByteLimit = b.BundleCountThreshold * 1000 @@ -114,6 +120,42 @@ func (e *traceExporter) Flush() { e.bundler.Flush() } +func (e *traceExporter) pushTraceSpans(ctx context.Context, node *commonpb.Node, r *resourcepb.Resource, spans []*trace.SpanData) (int, error) { + ctx, span := trace.StartSpan( + ctx, + "contrib.go.opencensus.io/exporter/stackdriver.PushTraceSpans", + trace.WithSampler(trace.NeverSample()), + ) + defer span.End() + span.AddAttributes(trace.Int64Attribute("num_spans", int64(len(spans)))) + + protoSpans := make([]*tracepb.Span, 0, len(spans)) + + res := e.o.Resource + if r != nil { + res = e.o.MapResource(resourcepbToResource(r)) + } + + for _, span := range spans { + protoSpans = append(protoSpans, protoFromSpanData(span, e.projectID, res)) + } + + req := tracepb.BatchWriteSpansRequest{ + Name: "projects/" + e.projectID, + Spans: protoSpans, + } + // Create a never-sampled span to prevent traces associated with exporter. + ctx, cancel := newContextWithTimeout(ctx, e.o.Timeout) + defer cancel() + + err := e.client.BatchWriteSpans(ctx, &req) + + if err != nil { + return len(spans), err + } + return 0, nil +} + // uploadSpans uploads a set of spans to Stackdriver. func (e *traceExporter) uploadSpans(spans []*tracepb.Span) { req := tracepb.BatchWriteSpansRequest{ diff --git a/vendor/github.com/alecthomas/template/LICENSE b/vendor/github.com/alecthomas/template/LICENSE new file mode 100644 index 00000000..74487567 --- /dev/null +++ b/vendor/github.com/alecthomas/template/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2012 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/github.com/alecthomas/template/README.md b/vendor/github.com/alecthomas/template/README.md new file mode 100644 index 00000000..ef6a8ee3 --- /dev/null +++ b/vendor/github.com/alecthomas/template/README.md @@ -0,0 +1,25 @@ +# Go's `text/template` package with newline elision + +This is a fork of Go 1.4's [text/template](http://golang.org/pkg/text/template/) package with one addition: a backslash immediately after a closing delimiter will delete all subsequent newlines until a non-newline. + +eg. + +``` +{{if true}}\ +hello +{{end}}\ +``` + +Will result in: + +``` +hello\n +``` + +Rather than: + +``` +\n +hello\n +\n +``` diff --git a/vendor/github.com/alecthomas/template/doc.go b/vendor/github.com/alecthomas/template/doc.go new file mode 100644 index 00000000..223c595c --- /dev/null +++ b/vendor/github.com/alecthomas/template/doc.go @@ -0,0 +1,406 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +/* +Package template implements data-driven templates for generating textual output. + +To generate HTML output, see package html/template, which has the same interface +as this package but automatically secures HTML output against certain attacks. + +Templates are executed by applying them to a data structure. Annotations in the +template refer to elements of the data structure (typically a field of a struct +or a key in a map) to control execution and derive values to be displayed. +Execution of the template walks the structure and sets the cursor, represented +by a period '.' and called "dot", to the value at the current location in the +structure as execution proceeds. + +The input text for a template is UTF-8-encoded text in any format. +"Actions"--data evaluations or control structures--are delimited by +"{{" and "}}"; all text outside actions is copied to the output unchanged. +Actions may not span newlines, although comments can. + +Once parsed, a template may be executed safely in parallel. + +Here is a trivial example that prints "17 items are made of wool". + + type Inventory struct { + Material string + Count uint + } + sweaters := Inventory{"wool", 17} + tmpl, err := template.New("test").Parse("{{.Count}} items are made of {{.Material}}") + if err != nil { panic(err) } + err = tmpl.Execute(os.Stdout, sweaters) + if err != nil { panic(err) } + +More intricate examples appear below. + +Actions + +Here is the list of actions. "Arguments" and "pipelines" are evaluations of +data, defined in detail below. + +*/ +// {{/* a comment */}} +// A comment; discarded. May contain newlines. +// Comments do not nest and must start and end at the +// delimiters, as shown here. +/* + + {{pipeline}} + The default textual representation of the value of the pipeline + is copied to the output. + + {{if pipeline}} T1 {{end}} + If the value of the pipeline is empty, no output is generated; + otherwise, T1 is executed. The empty values are false, 0, any + nil pointer or interface value, and any array, slice, map, or + string of length zero. + Dot is unaffected. + + {{if pipeline}} T1 {{else}} T0 {{end}} + If the value of the pipeline is empty, T0 is executed; + otherwise, T1 is executed. Dot is unaffected. + + {{if pipeline}} T1 {{else if pipeline}} T0 {{end}} + To simplify the appearance of if-else chains, the else action + of an if may include another if directly; the effect is exactly + the same as writing + {{if pipeline}} T1 {{else}}{{if pipeline}} T0 {{end}}{{end}} + + {{range pipeline}} T1 {{end}} + The value of the pipeline must be an array, slice, map, or channel. + If the value of the pipeline has length zero, nothing is output; + otherwise, dot is set to the successive elements of the array, + slice, or map and T1 is executed. If the value is a map and the + keys are of basic type with a defined order ("comparable"), the + elements will be visited in sorted key order. + + {{range pipeline}} T1 {{else}} T0 {{end}} + The value of the pipeline must be an array, slice, map, or channel. + If the value of the pipeline has length zero, dot is unaffected and + T0 is executed; otherwise, dot is set to the successive elements + of the array, slice, or map and T1 is executed. + + {{template "name"}} + The template with the specified name is executed with nil data. + + {{template "name" pipeline}} + The template with the specified name is executed with dot set + to the value of the pipeline. + + {{with pipeline}} T1 {{end}} + If the value of the pipeline is empty, no output is generated; + otherwise, dot is set to the value of the pipeline and T1 is + executed. + + {{with pipeline}} T1 {{else}} T0 {{end}} + If the value of the pipeline is empty, dot is unaffected and T0 + is executed; otherwise, dot is set to the value of the pipeline + and T1 is executed. + +Arguments + +An argument is a simple value, denoted by one of the following. + + - A boolean, string, character, integer, floating-point, imaginary + or complex constant in Go syntax. These behave like Go's untyped + constants, although raw strings may not span newlines. + - The keyword nil, representing an untyped Go nil. + - The character '.' (period): + . + The result is the value of dot. + - A variable name, which is a (possibly empty) alphanumeric string + preceded by a dollar sign, such as + $piOver2 + or + $ + The result is the value of the variable. + Variables are described below. + - The name of a field of the data, which must be a struct, preceded + by a period, such as + .Field + The result is the value of the field. Field invocations may be + chained: + .Field1.Field2 + Fields can also be evaluated on variables, including chaining: + $x.Field1.Field2 + - The name of a key of the data, which must be a map, preceded + by a period, such as + .Key + The result is the map element value indexed by the key. + Key invocations may be chained and combined with fields to any + depth: + .Field1.Key1.Field2.Key2 + Although the key must be an alphanumeric identifier, unlike with + field names they do not need to start with an upper case letter. + Keys can also be evaluated on variables, including chaining: + $x.key1.key2 + - The name of a niladic method of the data, preceded by a period, + such as + .Method + The result is the value of invoking the method with dot as the + receiver, dot.Method(). Such a method must have one return value (of + any type) or two return values, the second of which is an error. + If it has two and the returned error is non-nil, execution terminates + and an error is returned to the caller as the value of Execute. + Method invocations may be chained and combined with fields and keys + to any depth: + .Field1.Key1.Method1.Field2.Key2.Method2 + Methods can also be evaluated on variables, including chaining: + $x.Method1.Field + - The name of a niladic function, such as + fun + The result is the value of invoking the function, fun(). The return + types and values behave as in methods. Functions and function + names are described below. + - A parenthesized instance of one the above, for grouping. The result + may be accessed by a field or map key invocation. + print (.F1 arg1) (.F2 arg2) + (.StructValuedMethod "arg").Field + +Arguments may evaluate to any type; if they are pointers the implementation +automatically indirects to the base type when required. +If an evaluation yields a function value, such as a function-valued +field of a struct, the function is not invoked automatically, but it +can be used as a truth value for an if action and the like. To invoke +it, use the call function, defined below. + +A pipeline is a possibly chained sequence of "commands". A command is a simple +value (argument) or a function or method call, possibly with multiple arguments: + + Argument + The result is the value of evaluating the argument. + .Method [Argument...] + The method can be alone or the last element of a chain but, + unlike methods in the middle of a chain, it can take arguments. + The result is the value of calling the method with the + arguments: + dot.Method(Argument1, etc.) + functionName [Argument...] + The result is the value of calling the function associated + with the name: + function(Argument1, etc.) + Functions and function names are described below. + +Pipelines + +A pipeline may be "chained" by separating a sequence of commands with pipeline +characters '|'. In a chained pipeline, the result of the each command is +passed as the last argument of the following command. The output of the final +command in the pipeline is the value of the pipeline. + +The output of a command will be either one value or two values, the second of +which has type error. If that second value is present and evaluates to +non-nil, execution terminates and the error is returned to the caller of +Execute. + +Variables + +A pipeline inside an action may initialize a variable to capture the result. +The initialization has syntax + + $variable := pipeline + +where $variable is the name of the variable. An action that declares a +variable produces no output. + +If a "range" action initializes a variable, the variable is set to the +successive elements of the iteration. Also, a "range" may declare two +variables, separated by a comma: + + range $index, $element := pipeline + +in which case $index and $element are set to the successive values of the +array/slice index or map key and element, respectively. Note that if there is +only one variable, it is assigned the element; this is opposite to the +convention in Go range clauses. + +A variable's scope extends to the "end" action of the control structure ("if", +"with", or "range") in which it is declared, or to the end of the template if +there is no such control structure. A template invocation does not inherit +variables from the point of its invocation. + +When execution begins, $ is set to the data argument passed to Execute, that is, +to the starting value of dot. + +Examples + +Here are some example one-line templates demonstrating pipelines and variables. +All produce the quoted word "output": + + {{"\"output\""}} + A string constant. + {{`"output"`}} + A raw string constant. + {{printf "%q" "output"}} + A function call. + {{"output" | printf "%q"}} + A function call whose final argument comes from the previous + command. + {{printf "%q" (print "out" "put")}} + A parenthesized argument. + {{"put" | printf "%s%s" "out" | printf "%q"}} + A more elaborate call. + {{"output" | printf "%s" | printf "%q"}} + A longer chain. + {{with "output"}}{{printf "%q" .}}{{end}} + A with action using dot. + {{with $x := "output" | printf "%q"}}{{$x}}{{end}} + A with action that creates and uses a variable. + {{with $x := "output"}}{{printf "%q" $x}}{{end}} + A with action that uses the variable in another action. + {{with $x := "output"}}{{$x | printf "%q"}}{{end}} + The same, but pipelined. + +Functions + +During execution functions are found in two function maps: first in the +template, then in the global function map. By default, no functions are defined +in the template but the Funcs method can be used to add them. + +Predefined global functions are named as follows. + + and + Returns the boolean AND of its arguments by returning the + first empty argument or the last argument, that is, + "and x y" behaves as "if x then y else x". All the + arguments are evaluated. + call + Returns the result of calling the first argument, which + must be a function, with the remaining arguments as parameters. + Thus "call .X.Y 1 2" is, in Go notation, dot.X.Y(1, 2) where + Y is a func-valued field, map entry, or the like. + The first argument must be the result of an evaluation + that yields a value of function type (as distinct from + a predefined function such as print). The function must + return either one or two result values, the second of which + is of type error. If the arguments don't match the function + or the returned error value is non-nil, execution stops. + html + Returns the escaped HTML equivalent of the textual + representation of its arguments. + index + Returns the result of indexing its first argument by the + following arguments. Thus "index x 1 2 3" is, in Go syntax, + x[1][2][3]. Each indexed item must be a map, slice, or array. + js + Returns the escaped JavaScript equivalent of the textual + representation of its arguments. + len + Returns the integer length of its argument. + not + Returns the boolean negation of its single argument. + or + Returns the boolean OR of its arguments by returning the + first non-empty argument or the last argument, that is, + "or x y" behaves as "if x then x else y". All the + arguments are evaluated. + print + An alias for fmt.Sprint + printf + An alias for fmt.Sprintf + println + An alias for fmt.Sprintln + urlquery + Returns the escaped value of the textual representation of + its arguments in a form suitable for embedding in a URL query. + +The boolean functions take any zero value to be false and a non-zero +value to be true. + +There is also a set of binary comparison operators defined as +functions: + + eq + Returns the boolean truth of arg1 == arg2 + ne + Returns the boolean truth of arg1 != arg2 + lt + Returns the boolean truth of arg1 < arg2 + le + Returns the boolean truth of arg1 <= arg2 + gt + Returns the boolean truth of arg1 > arg2 + ge + Returns the boolean truth of arg1 >= arg2 + +For simpler multi-way equality tests, eq (only) accepts two or more +arguments and compares the second and subsequent to the first, +returning in effect + + arg1==arg2 || arg1==arg3 || arg1==arg4 ... + +(Unlike with || in Go, however, eq is a function call and all the +arguments will be evaluated.) + +The comparison functions work on basic types only (or named basic +types, such as "type Celsius float32"). They implement the Go rules +for comparison of values, except that size and exact type are +ignored, so any integer value, signed or unsigned, may be compared +with any other integer value. (The arithmetic value is compared, +not the bit pattern, so all negative integers are less than all +unsigned integers.) However, as usual, one may not compare an int +with a float32 and so on. + +Associated templates + +Each template is named by a string specified when it is created. Also, each +template is associated with zero or more other templates that it may invoke by +name; such associations are transitive and form a name space of templates. + +A template may use a template invocation to instantiate another associated +template; see the explanation of the "template" action above. The name must be +that of a template associated with the template that contains the invocation. + +Nested template definitions + +When parsing a template, another template may be defined and associated with the +template being parsed. Template definitions must appear at the top level of the +template, much like global variables in a Go program. + +The syntax of such definitions is to surround each template declaration with a +"define" and "end" action. + +The define action names the template being created by providing a string +constant. Here is a simple example: + + `{{define "T1"}}ONE{{end}} + {{define "T2"}}TWO{{end}} + {{define "T3"}}{{template "T1"}} {{template "T2"}}{{end}} + {{template "T3"}}` + +This defines two templates, T1 and T2, and a third T3 that invokes the other two +when it is executed. Finally it invokes T3. If executed this template will +produce the text + + ONE TWO + +By construction, a template may reside in only one association. If it's +necessary to have a template addressable from multiple associations, the +template definition must be parsed multiple times to create distinct *Template +values, or must be copied with the Clone or AddParseTree method. + +Parse may be called multiple times to assemble the various associated templates; +see the ParseFiles and ParseGlob functions and methods for simple ways to parse +related templates stored in files. + +A template may be executed directly or through ExecuteTemplate, which executes +an associated template identified by name. To invoke our example above, we +might write, + + err := tmpl.Execute(os.Stdout, "no data needed") + if err != nil { + log.Fatalf("execution failed: %s", err) + } + +or to invoke a particular template explicitly by name, + + err := tmpl.ExecuteTemplate(os.Stdout, "T2", "no data needed") + if err != nil { + log.Fatalf("execution failed: %s", err) + } + +*/ +package template diff --git a/vendor/github.com/alecthomas/template/exec.go b/vendor/github.com/alecthomas/template/exec.go new file mode 100644 index 00000000..c3078e5d --- /dev/null +++ b/vendor/github.com/alecthomas/template/exec.go @@ -0,0 +1,845 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package template + +import ( + "bytes" + "fmt" + "io" + "reflect" + "runtime" + "sort" + "strings" + + "github.com/alecthomas/template/parse" +) + +// state represents the state of an execution. It's not part of the +// template so that multiple executions of the same template +// can execute in parallel. +type state struct { + tmpl *Template + wr io.Writer + node parse.Node // current node, for errors + vars []variable // push-down stack of variable values. +} + +// variable holds the dynamic value of a variable such as $, $x etc. +type variable struct { + name string + value reflect.Value +} + +// push pushes a new variable on the stack. +func (s *state) push(name string, value reflect.Value) { + s.vars = append(s.vars, variable{name, value}) +} + +// mark returns the length of the variable stack. +func (s *state) mark() int { + return len(s.vars) +} + +// pop pops the variable stack up to the mark. +func (s *state) pop(mark int) { + s.vars = s.vars[0:mark] +} + +// setVar overwrites the top-nth variable on the stack. Used by range iterations. +func (s *state) setVar(n int, value reflect.Value) { + s.vars[len(s.vars)-n].value = value +} + +// varValue returns the value of the named variable. +func (s *state) varValue(name string) reflect.Value { + for i := s.mark() - 1; i >= 0; i-- { + if s.vars[i].name == name { + return s.vars[i].value + } + } + s.errorf("undefined variable: %s", name) + return zero +} + +var zero reflect.Value + +// at marks the state to be on node n, for error reporting. +func (s *state) at(node parse.Node) { + s.node = node +} + +// doublePercent returns the string with %'s replaced by %%, if necessary, +// so it can be used safely inside a Printf format string. +func doublePercent(str string) string { + if strings.Contains(str, "%") { + str = strings.Replace(str, "%", "%%", -1) + } + return str +} + +// errorf formats the error and terminates processing. +func (s *state) errorf(format string, args ...interface{}) { + name := doublePercent(s.tmpl.Name()) + if s.node == nil { + format = fmt.Sprintf("template: %s: %s", name, format) + } else { + location, context := s.tmpl.ErrorContext(s.node) + format = fmt.Sprintf("template: %s: executing %q at <%s>: %s", location, name, doublePercent(context), format) + } + panic(fmt.Errorf(format, args...)) +} + +// errRecover is the handler that turns panics into returns from the top +// level of Parse. +func errRecover(errp *error) { + e := recover() + if e != nil { + switch err := e.(type) { + case runtime.Error: + panic(e) + case error: + *errp = err + default: + panic(e) + } + } +} + +// ExecuteTemplate applies the template associated with t that has the given name +// to the specified data object and writes the output to wr. +// If an error occurs executing the template or writing its output, +// execution stops, but partial results may already have been written to +// the output writer. +// A template may be executed safely in parallel. +func (t *Template) ExecuteTemplate(wr io.Writer, name string, data interface{}) error { + tmpl := t.tmpl[name] + if tmpl == nil { + return fmt.Errorf("template: no template %q associated with template %q", name, t.name) + } + return tmpl.Execute(wr, data) +} + +// Execute applies a parsed template to the specified data object, +// and writes the output to wr. +// If an error occurs executing the template or writing its output, +// execution stops, but partial results may already have been written to +// the output writer. +// A template may be executed safely in parallel. +func (t *Template) Execute(wr io.Writer, data interface{}) (err error) { + defer errRecover(&err) + value := reflect.ValueOf(data) + state := &state{ + tmpl: t, + wr: wr, + vars: []variable{{"$", value}}, + } + t.init() + if t.Tree == nil || t.Root == nil { + var b bytes.Buffer + for name, tmpl := range t.tmpl { + if tmpl.Tree == nil || tmpl.Root == nil { + continue + } + if b.Len() > 0 { + b.WriteString(", ") + } + fmt.Fprintf(&b, "%q", name) + } + var s string + if b.Len() > 0 { + s = "; defined templates are: " + b.String() + } + state.errorf("%q is an incomplete or empty template%s", t.Name(), s) + } + state.walk(value, t.Root) + return +} + +// Walk functions step through the major pieces of the template structure, +// generating output as they go. +func (s *state) walk(dot reflect.Value, node parse.Node) { + s.at(node) + switch node := node.(type) { + case *parse.ActionNode: + // Do not pop variables so they persist until next end. + // Also, if the action declares variables, don't print the result. + val := s.evalPipeline(dot, node.Pipe) + if len(node.Pipe.Decl) == 0 { + s.printValue(node, val) + } + case *parse.IfNode: + s.walkIfOrWith(parse.NodeIf, dot, node.Pipe, node.List, node.ElseList) + case *parse.ListNode: + for _, node := range node.Nodes { + s.walk(dot, node) + } + case *parse.RangeNode: + s.walkRange(dot, node) + case *parse.TemplateNode: + s.walkTemplate(dot, node) + case *parse.TextNode: + if _, err := s.wr.Write(node.Text); err != nil { + s.errorf("%s", err) + } + case *parse.WithNode: + s.walkIfOrWith(parse.NodeWith, dot, node.Pipe, node.List, node.ElseList) + default: + s.errorf("unknown node: %s", node) + } +} + +// walkIfOrWith walks an 'if' or 'with' node. The two control structures +// are identical in behavior except that 'with' sets dot. +func (s *state) walkIfOrWith(typ parse.NodeType, dot reflect.Value, pipe *parse.PipeNode, list, elseList *parse.ListNode) { + defer s.pop(s.mark()) + val := s.evalPipeline(dot, pipe) + truth, ok := isTrue(val) + if !ok { + s.errorf("if/with can't use %v", val) + } + if truth { + if typ == parse.NodeWith { + s.walk(val, list) + } else { + s.walk(dot, list) + } + } else if elseList != nil { + s.walk(dot, elseList) + } +} + +// isTrue reports whether the value is 'true', in the sense of not the zero of its type, +// and whether the value has a meaningful truth value. +func isTrue(val reflect.Value) (truth, ok bool) { + if !val.IsValid() { + // Something like var x interface{}, never set. It's a form of nil. + return false, true + } + switch val.Kind() { + case reflect.Array, reflect.Map, reflect.Slice, reflect.String: + truth = val.Len() > 0 + case reflect.Bool: + truth = val.Bool() + case reflect.Complex64, reflect.Complex128: + truth = val.Complex() != 0 + case reflect.Chan, reflect.Func, reflect.Ptr, reflect.Interface: + truth = !val.IsNil() + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + truth = val.Int() != 0 + case reflect.Float32, reflect.Float64: + truth = val.Float() != 0 + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + truth = val.Uint() != 0 + case reflect.Struct: + truth = true // Struct values are always true. + default: + return + } + return truth, true +} + +func (s *state) walkRange(dot reflect.Value, r *parse.RangeNode) { + s.at(r) + defer s.pop(s.mark()) + val, _ := indirect(s.evalPipeline(dot, r.Pipe)) + // mark top of stack before any variables in the body are pushed. + mark := s.mark() + oneIteration := func(index, elem reflect.Value) { + // Set top var (lexically the second if there are two) to the element. + if len(r.Pipe.Decl) > 0 { + s.setVar(1, elem) + } + // Set next var (lexically the first if there are two) to the index. + if len(r.Pipe.Decl) > 1 { + s.setVar(2, index) + } + s.walk(elem, r.List) + s.pop(mark) + } + switch val.Kind() { + case reflect.Array, reflect.Slice: + if val.Len() == 0 { + break + } + for i := 0; i < val.Len(); i++ { + oneIteration(reflect.ValueOf(i), val.Index(i)) + } + return + case reflect.Map: + if val.Len() == 0 { + break + } + for _, key := range sortKeys(val.MapKeys()) { + oneIteration(key, val.MapIndex(key)) + } + return + case reflect.Chan: + if val.IsNil() { + break + } + i := 0 + for ; ; i++ { + elem, ok := val.Recv() + if !ok { + break + } + oneIteration(reflect.ValueOf(i), elem) + } + if i == 0 { + break + } + return + case reflect.Invalid: + break // An invalid value is likely a nil map, etc. and acts like an empty map. + default: + s.errorf("range can't iterate over %v", val) + } + if r.ElseList != nil { + s.walk(dot, r.ElseList) + } +} + +func (s *state) walkTemplate(dot reflect.Value, t *parse.TemplateNode) { + s.at(t) + tmpl := s.tmpl.tmpl[t.Name] + if tmpl == nil { + s.errorf("template %q not defined", t.Name) + } + // Variables declared by the pipeline persist. + dot = s.evalPipeline(dot, t.Pipe) + newState := *s + newState.tmpl = tmpl + // No dynamic scoping: template invocations inherit no variables. + newState.vars = []variable{{"$", dot}} + newState.walk(dot, tmpl.Root) +} + +// Eval functions evaluate pipelines, commands, and their elements and extract +// values from the data structure by examining fields, calling methods, and so on. +// The printing of those values happens only through walk functions. + +// evalPipeline returns the value acquired by evaluating a pipeline. If the +// pipeline has a variable declaration, the variable will be pushed on the +// stack. Callers should therefore pop the stack after they are finished +// executing commands depending on the pipeline value. +func (s *state) evalPipeline(dot reflect.Value, pipe *parse.PipeNode) (value reflect.Value) { + if pipe == nil { + return + } + s.at(pipe) + for _, cmd := range pipe.Cmds { + value = s.evalCommand(dot, cmd, value) // previous value is this one's final arg. + // If the object has type interface{}, dig down one level to the thing inside. + if value.Kind() == reflect.Interface && value.Type().NumMethod() == 0 { + value = reflect.ValueOf(value.Interface()) // lovely! + } + } + for _, variable := range pipe.Decl { + s.push(variable.Ident[0], value) + } + return value +} + +func (s *state) notAFunction(args []parse.Node, final reflect.Value) { + if len(args) > 1 || final.IsValid() { + s.errorf("can't give argument to non-function %s", args[0]) + } +} + +func (s *state) evalCommand(dot reflect.Value, cmd *parse.CommandNode, final reflect.Value) reflect.Value { + firstWord := cmd.Args[0] + switch n := firstWord.(type) { + case *parse.FieldNode: + return s.evalFieldNode(dot, n, cmd.Args, final) + case *parse.ChainNode: + return s.evalChainNode(dot, n, cmd.Args, final) + case *parse.IdentifierNode: + // Must be a function. + return s.evalFunction(dot, n, cmd, cmd.Args, final) + case *parse.PipeNode: + // Parenthesized pipeline. The arguments are all inside the pipeline; final is ignored. + return s.evalPipeline(dot, n) + case *parse.VariableNode: + return s.evalVariableNode(dot, n, cmd.Args, final) + } + s.at(firstWord) + s.notAFunction(cmd.Args, final) + switch word := firstWord.(type) { + case *parse.BoolNode: + return reflect.ValueOf(word.True) + case *parse.DotNode: + return dot + case *parse.NilNode: + s.errorf("nil is not a command") + case *parse.NumberNode: + return s.idealConstant(word) + case *parse.StringNode: + return reflect.ValueOf(word.Text) + } + s.errorf("can't evaluate command %q", firstWord) + panic("not reached") +} + +// idealConstant is called to return the value of a number in a context where +// we don't know the type. In that case, the syntax of the number tells us +// its type, and we use Go rules to resolve. Note there is no such thing as +// a uint ideal constant in this situation - the value must be of int type. +func (s *state) idealConstant(constant *parse.NumberNode) reflect.Value { + // These are ideal constants but we don't know the type + // and we have no context. (If it was a method argument, + // we'd know what we need.) The syntax guides us to some extent. + s.at(constant) + switch { + case constant.IsComplex: + return reflect.ValueOf(constant.Complex128) // incontrovertible. + case constant.IsFloat && !isHexConstant(constant.Text) && strings.IndexAny(constant.Text, ".eE") >= 0: + return reflect.ValueOf(constant.Float64) + case constant.IsInt: + n := int(constant.Int64) + if int64(n) != constant.Int64 { + s.errorf("%s overflows int", constant.Text) + } + return reflect.ValueOf(n) + case constant.IsUint: + s.errorf("%s overflows int", constant.Text) + } + return zero +} + +func isHexConstant(s string) bool { + return len(s) > 2 && s[0] == '0' && (s[1] == 'x' || s[1] == 'X') +} + +func (s *state) evalFieldNode(dot reflect.Value, field *parse.FieldNode, args []parse.Node, final reflect.Value) reflect.Value { + s.at(field) + return s.evalFieldChain(dot, dot, field, field.Ident, args, final) +} + +func (s *state) evalChainNode(dot reflect.Value, chain *parse.ChainNode, args []parse.Node, final reflect.Value) reflect.Value { + s.at(chain) + // (pipe).Field1.Field2 has pipe as .Node, fields as .Field. Eval the pipeline, then the fields. + pipe := s.evalArg(dot, nil, chain.Node) + if len(chain.Field) == 0 { + s.errorf("internal error: no fields in evalChainNode") + } + return s.evalFieldChain(dot, pipe, chain, chain.Field, args, final) +} + +func (s *state) evalVariableNode(dot reflect.Value, variable *parse.VariableNode, args []parse.Node, final reflect.Value) reflect.Value { + // $x.Field has $x as the first ident, Field as the second. Eval the var, then the fields. + s.at(variable) + value := s.varValue(variable.Ident[0]) + if len(variable.Ident) == 1 { + s.notAFunction(args, final) + return value + } + return s.evalFieldChain(dot, value, variable, variable.Ident[1:], args, final) +} + +// evalFieldChain evaluates .X.Y.Z possibly followed by arguments. +// dot is the environment in which to evaluate arguments, while +// receiver is the value being walked along the chain. +func (s *state) evalFieldChain(dot, receiver reflect.Value, node parse.Node, ident []string, args []parse.Node, final reflect.Value) reflect.Value { + n := len(ident) + for i := 0; i < n-1; i++ { + receiver = s.evalField(dot, ident[i], node, nil, zero, receiver) + } + // Now if it's a method, it gets the arguments. + return s.evalField(dot, ident[n-1], node, args, final, receiver) +} + +func (s *state) evalFunction(dot reflect.Value, node *parse.IdentifierNode, cmd parse.Node, args []parse.Node, final reflect.Value) reflect.Value { + s.at(node) + name := node.Ident + function, ok := findFunction(name, s.tmpl) + if !ok { + s.errorf("%q is not a defined function", name) + } + return s.evalCall(dot, function, cmd, name, args, final) +} + +// evalField evaluates an expression like (.Field) or (.Field arg1 arg2). +// The 'final' argument represents the return value from the preceding +// value of the pipeline, if any. +func (s *state) evalField(dot reflect.Value, fieldName string, node parse.Node, args []parse.Node, final, receiver reflect.Value) reflect.Value { + if !receiver.IsValid() { + return zero + } + typ := receiver.Type() + receiver, _ = indirect(receiver) + // Unless it's an interface, need to get to a value of type *T to guarantee + // we see all methods of T and *T. + ptr := receiver + if ptr.Kind() != reflect.Interface && ptr.CanAddr() { + ptr = ptr.Addr() + } + if method := ptr.MethodByName(fieldName); method.IsValid() { + return s.evalCall(dot, method, node, fieldName, args, final) + } + hasArgs := len(args) > 1 || final.IsValid() + // It's not a method; must be a field of a struct or an element of a map. The receiver must not be nil. + receiver, isNil := indirect(receiver) + if isNil { + s.errorf("nil pointer evaluating %s.%s", typ, fieldName) + } + switch receiver.Kind() { + case reflect.Struct: + tField, ok := receiver.Type().FieldByName(fieldName) + if ok { + field := receiver.FieldByIndex(tField.Index) + if tField.PkgPath != "" { // field is unexported + s.errorf("%s is an unexported field of struct type %s", fieldName, typ) + } + // If it's a function, we must call it. + if hasArgs { + s.errorf("%s has arguments but cannot be invoked as function", fieldName) + } + return field + } + s.errorf("%s is not a field of struct type %s", fieldName, typ) + case reflect.Map: + // If it's a map, attempt to use the field name as a key. + nameVal := reflect.ValueOf(fieldName) + if nameVal.Type().AssignableTo(receiver.Type().Key()) { + if hasArgs { + s.errorf("%s is not a method but has arguments", fieldName) + } + return receiver.MapIndex(nameVal) + } + } + s.errorf("can't evaluate field %s in type %s", fieldName, typ) + panic("not reached") +} + +var ( + errorType = reflect.TypeOf((*error)(nil)).Elem() + fmtStringerType = reflect.TypeOf((*fmt.Stringer)(nil)).Elem() +) + +// evalCall executes a function or method call. If it's a method, fun already has the receiver bound, so +// it looks just like a function call. The arg list, if non-nil, includes (in the manner of the shell), arg[0] +// as the function itself. +func (s *state) evalCall(dot, fun reflect.Value, node parse.Node, name string, args []parse.Node, final reflect.Value) reflect.Value { + if args != nil { + args = args[1:] // Zeroth arg is function name/node; not passed to function. + } + typ := fun.Type() + numIn := len(args) + if final.IsValid() { + numIn++ + } + numFixed := len(args) + if typ.IsVariadic() { + numFixed = typ.NumIn() - 1 // last arg is the variadic one. + if numIn < numFixed { + s.errorf("wrong number of args for %s: want at least %d got %d", name, typ.NumIn()-1, len(args)) + } + } else if numIn < typ.NumIn()-1 || !typ.IsVariadic() && numIn != typ.NumIn() { + s.errorf("wrong number of args for %s: want %d got %d", name, typ.NumIn(), len(args)) + } + if !goodFunc(typ) { + // TODO: This could still be a confusing error; maybe goodFunc should provide info. + s.errorf("can't call method/function %q with %d results", name, typ.NumOut()) + } + // Build the arg list. + argv := make([]reflect.Value, numIn) + // Args must be evaluated. Fixed args first. + i := 0 + for ; i < numFixed && i < len(args); i++ { + argv[i] = s.evalArg(dot, typ.In(i), args[i]) + } + // Now the ... args. + if typ.IsVariadic() { + argType := typ.In(typ.NumIn() - 1).Elem() // Argument is a slice. + for ; i < len(args); i++ { + argv[i] = s.evalArg(dot, argType, args[i]) + } + } + // Add final value if necessary. + if final.IsValid() { + t := typ.In(typ.NumIn() - 1) + if typ.IsVariadic() { + t = t.Elem() + } + argv[i] = s.validateType(final, t) + } + result := fun.Call(argv) + // If we have an error that is not nil, stop execution and return that error to the caller. + if len(result) == 2 && !result[1].IsNil() { + s.at(node) + s.errorf("error calling %s: %s", name, result[1].Interface().(error)) + } + return result[0] +} + +// canBeNil reports whether an untyped nil can be assigned to the type. See reflect.Zero. +func canBeNil(typ reflect.Type) bool { + switch typ.Kind() { + case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice: + return true + } + return false +} + +// validateType guarantees that the value is valid and assignable to the type. +func (s *state) validateType(value reflect.Value, typ reflect.Type) reflect.Value { + if !value.IsValid() { + if typ == nil || canBeNil(typ) { + // An untyped nil interface{}. Accept as a proper nil value. + return reflect.Zero(typ) + } + s.errorf("invalid value; expected %s", typ) + } + if typ != nil && !value.Type().AssignableTo(typ) { + if value.Kind() == reflect.Interface && !value.IsNil() { + value = value.Elem() + if value.Type().AssignableTo(typ) { + return value + } + // fallthrough + } + // Does one dereference or indirection work? We could do more, as we + // do with method receivers, but that gets messy and method receivers + // are much more constrained, so it makes more sense there than here. + // Besides, one is almost always all you need. + switch { + case value.Kind() == reflect.Ptr && value.Type().Elem().AssignableTo(typ): + value = value.Elem() + if !value.IsValid() { + s.errorf("dereference of nil pointer of type %s", typ) + } + case reflect.PtrTo(value.Type()).AssignableTo(typ) && value.CanAddr(): + value = value.Addr() + default: + s.errorf("wrong type for value; expected %s; got %s", typ, value.Type()) + } + } + return value +} + +func (s *state) evalArg(dot reflect.Value, typ reflect.Type, n parse.Node) reflect.Value { + s.at(n) + switch arg := n.(type) { + case *parse.DotNode: + return s.validateType(dot, typ) + case *parse.NilNode: + if canBeNil(typ) { + return reflect.Zero(typ) + } + s.errorf("cannot assign nil to %s", typ) + case *parse.FieldNode: + return s.validateType(s.evalFieldNode(dot, arg, []parse.Node{n}, zero), typ) + case *parse.VariableNode: + return s.validateType(s.evalVariableNode(dot, arg, nil, zero), typ) + case *parse.PipeNode: + return s.validateType(s.evalPipeline(dot, arg), typ) + case *parse.IdentifierNode: + return s.evalFunction(dot, arg, arg, nil, zero) + case *parse.ChainNode: + return s.validateType(s.evalChainNode(dot, arg, nil, zero), typ) + } + switch typ.Kind() { + case reflect.Bool: + return s.evalBool(typ, n) + case reflect.Complex64, reflect.Complex128: + return s.evalComplex(typ, n) + case reflect.Float32, reflect.Float64: + return s.evalFloat(typ, n) + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return s.evalInteger(typ, n) + case reflect.Interface: + if typ.NumMethod() == 0 { + return s.evalEmptyInterface(dot, n) + } + case reflect.String: + return s.evalString(typ, n) + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + return s.evalUnsignedInteger(typ, n) + } + s.errorf("can't handle %s for arg of type %s", n, typ) + panic("not reached") +} + +func (s *state) evalBool(typ reflect.Type, n parse.Node) reflect.Value { + s.at(n) + if n, ok := n.(*parse.BoolNode); ok { + value := reflect.New(typ).Elem() + value.SetBool(n.True) + return value + } + s.errorf("expected bool; found %s", n) + panic("not reached") +} + +func (s *state) evalString(typ reflect.Type, n parse.Node) reflect.Value { + s.at(n) + if n, ok := n.(*parse.StringNode); ok { + value := reflect.New(typ).Elem() + value.SetString(n.Text) + return value + } + s.errorf("expected string; found %s", n) + panic("not reached") +} + +func (s *state) evalInteger(typ reflect.Type, n parse.Node) reflect.Value { + s.at(n) + if n, ok := n.(*parse.NumberNode); ok && n.IsInt { + value := reflect.New(typ).Elem() + value.SetInt(n.Int64) + return value + } + s.errorf("expected integer; found %s", n) + panic("not reached") +} + +func (s *state) evalUnsignedInteger(typ reflect.Type, n parse.Node) reflect.Value { + s.at(n) + if n, ok := n.(*parse.NumberNode); ok && n.IsUint { + value := reflect.New(typ).Elem() + value.SetUint(n.Uint64) + return value + } + s.errorf("expected unsigned integer; found %s", n) + panic("not reached") +} + +func (s *state) evalFloat(typ reflect.Type, n parse.Node) reflect.Value { + s.at(n) + if n, ok := n.(*parse.NumberNode); ok && n.IsFloat { + value := reflect.New(typ).Elem() + value.SetFloat(n.Float64) + return value + } + s.errorf("expected float; found %s", n) + panic("not reached") +} + +func (s *state) evalComplex(typ reflect.Type, n parse.Node) reflect.Value { + if n, ok := n.(*parse.NumberNode); ok && n.IsComplex { + value := reflect.New(typ).Elem() + value.SetComplex(n.Complex128) + return value + } + s.errorf("expected complex; found %s", n) + panic("not reached") +} + +func (s *state) evalEmptyInterface(dot reflect.Value, n parse.Node) reflect.Value { + s.at(n) + switch n := n.(type) { + case *parse.BoolNode: + return reflect.ValueOf(n.True) + case *parse.DotNode: + return dot + case *parse.FieldNode: + return s.evalFieldNode(dot, n, nil, zero) + case *parse.IdentifierNode: + return s.evalFunction(dot, n, n, nil, zero) + case *parse.NilNode: + // NilNode is handled in evalArg, the only place that calls here. + s.errorf("evalEmptyInterface: nil (can't happen)") + case *parse.NumberNode: + return s.idealConstant(n) + case *parse.StringNode: + return reflect.ValueOf(n.Text) + case *parse.VariableNode: + return s.evalVariableNode(dot, n, nil, zero) + case *parse.PipeNode: + return s.evalPipeline(dot, n) + } + s.errorf("can't handle assignment of %s to empty interface argument", n) + panic("not reached") +} + +// indirect returns the item at the end of indirection, and a bool to indicate if it's nil. +// We indirect through pointers and empty interfaces (only) because +// non-empty interfaces have methods we might need. +func indirect(v reflect.Value) (rv reflect.Value, isNil bool) { + for ; v.Kind() == reflect.Ptr || v.Kind() == reflect.Interface; v = v.Elem() { + if v.IsNil() { + return v, true + } + if v.Kind() == reflect.Interface && v.NumMethod() > 0 { + break + } + } + return v, false +} + +// printValue writes the textual representation of the value to the output of +// the template. +func (s *state) printValue(n parse.Node, v reflect.Value) { + s.at(n) + iface, ok := printableValue(v) + if !ok { + s.errorf("can't print %s of type %s", n, v.Type()) + } + fmt.Fprint(s.wr, iface) +} + +// printableValue returns the, possibly indirected, interface value inside v that +// is best for a call to formatted printer. +func printableValue(v reflect.Value) (interface{}, bool) { + if v.Kind() == reflect.Ptr { + v, _ = indirect(v) // fmt.Fprint handles nil. + } + if !v.IsValid() { + return "", true + } + + if !v.Type().Implements(errorType) && !v.Type().Implements(fmtStringerType) { + if v.CanAddr() && (reflect.PtrTo(v.Type()).Implements(errorType) || reflect.PtrTo(v.Type()).Implements(fmtStringerType)) { + v = v.Addr() + } else { + switch v.Kind() { + case reflect.Chan, reflect.Func: + return nil, false + } + } + } + return v.Interface(), true +} + +// Types to help sort the keys in a map for reproducible output. + +type rvs []reflect.Value + +func (x rvs) Len() int { return len(x) } +func (x rvs) Swap(i, j int) { x[i], x[j] = x[j], x[i] } + +type rvInts struct{ rvs } + +func (x rvInts) Less(i, j int) bool { return x.rvs[i].Int() < x.rvs[j].Int() } + +type rvUints struct{ rvs } + +func (x rvUints) Less(i, j int) bool { return x.rvs[i].Uint() < x.rvs[j].Uint() } + +type rvFloats struct{ rvs } + +func (x rvFloats) Less(i, j int) bool { return x.rvs[i].Float() < x.rvs[j].Float() } + +type rvStrings struct{ rvs } + +func (x rvStrings) Less(i, j int) bool { return x.rvs[i].String() < x.rvs[j].String() } + +// sortKeys sorts (if it can) the slice of reflect.Values, which is a slice of map keys. +func sortKeys(v []reflect.Value) []reflect.Value { + if len(v) <= 1 { + return v + } + switch v[0].Kind() { + case reflect.Float32, reflect.Float64: + sort.Sort(rvFloats{v}) + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + sort.Sort(rvInts{v}) + case reflect.String: + sort.Sort(rvStrings{v}) + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + sort.Sort(rvUints{v}) + } + return v +} diff --git a/vendor/github.com/alecthomas/template/funcs.go b/vendor/github.com/alecthomas/template/funcs.go new file mode 100644 index 00000000..39ee5ed6 --- /dev/null +++ b/vendor/github.com/alecthomas/template/funcs.go @@ -0,0 +1,598 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package template + +import ( + "bytes" + "errors" + "fmt" + "io" + "net/url" + "reflect" + "strings" + "unicode" + "unicode/utf8" +) + +// FuncMap is the type of the map defining the mapping from names to functions. +// Each function must have either a single return value, or two return values of +// which the second has type error. In that case, if the second (error) +// return value evaluates to non-nil during execution, execution terminates and +// Execute returns that error. +type FuncMap map[string]interface{} + +var builtins = FuncMap{ + "and": and, + "call": call, + "html": HTMLEscaper, + "index": index, + "js": JSEscaper, + "len": length, + "not": not, + "or": or, + "print": fmt.Sprint, + "printf": fmt.Sprintf, + "println": fmt.Sprintln, + "urlquery": URLQueryEscaper, + + // Comparisons + "eq": eq, // == + "ge": ge, // >= + "gt": gt, // > + "le": le, // <= + "lt": lt, // < + "ne": ne, // != +} + +var builtinFuncs = createValueFuncs(builtins) + +// createValueFuncs turns a FuncMap into a map[string]reflect.Value +func createValueFuncs(funcMap FuncMap) map[string]reflect.Value { + m := make(map[string]reflect.Value) + addValueFuncs(m, funcMap) + return m +} + +// addValueFuncs adds to values the functions in funcs, converting them to reflect.Values. +func addValueFuncs(out map[string]reflect.Value, in FuncMap) { + for name, fn := range in { + v := reflect.ValueOf(fn) + if v.Kind() != reflect.Func { + panic("value for " + name + " not a function") + } + if !goodFunc(v.Type()) { + panic(fmt.Errorf("can't install method/function %q with %d results", name, v.Type().NumOut())) + } + out[name] = v + } +} + +// addFuncs adds to values the functions in funcs. It does no checking of the input - +// call addValueFuncs first. +func addFuncs(out, in FuncMap) { + for name, fn := range in { + out[name] = fn + } +} + +// goodFunc checks that the function or method has the right result signature. +func goodFunc(typ reflect.Type) bool { + // We allow functions with 1 result or 2 results where the second is an error. + switch { + case typ.NumOut() == 1: + return true + case typ.NumOut() == 2 && typ.Out(1) == errorType: + return true + } + return false +} + +// findFunction looks for a function in the template, and global map. +func findFunction(name string, tmpl *Template) (reflect.Value, bool) { + if tmpl != nil && tmpl.common != nil { + if fn := tmpl.execFuncs[name]; fn.IsValid() { + return fn, true + } + } + if fn := builtinFuncs[name]; fn.IsValid() { + return fn, true + } + return reflect.Value{}, false +} + +// Indexing. + +// index returns the result of indexing its first argument by the following +// arguments. Thus "index x 1 2 3" is, in Go syntax, x[1][2][3]. Each +// indexed item must be a map, slice, or array. +func index(item interface{}, indices ...interface{}) (interface{}, error) { + v := reflect.ValueOf(item) + for _, i := range indices { + index := reflect.ValueOf(i) + var isNil bool + if v, isNil = indirect(v); isNil { + return nil, fmt.Errorf("index of nil pointer") + } + switch v.Kind() { + case reflect.Array, reflect.Slice, reflect.String: + var x int64 + switch index.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + x = index.Int() + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + x = int64(index.Uint()) + default: + return nil, fmt.Errorf("cannot index slice/array with type %s", index.Type()) + } + if x < 0 || x >= int64(v.Len()) { + return nil, fmt.Errorf("index out of range: %d", x) + } + v = v.Index(int(x)) + case reflect.Map: + if !index.IsValid() { + index = reflect.Zero(v.Type().Key()) + } + if !index.Type().AssignableTo(v.Type().Key()) { + return nil, fmt.Errorf("%s is not index type for %s", index.Type(), v.Type()) + } + if x := v.MapIndex(index); x.IsValid() { + v = x + } else { + v = reflect.Zero(v.Type().Elem()) + } + default: + return nil, fmt.Errorf("can't index item of type %s", v.Type()) + } + } + return v.Interface(), nil +} + +// Length + +// length returns the length of the item, with an error if it has no defined length. +func length(item interface{}) (int, error) { + v, isNil := indirect(reflect.ValueOf(item)) + if isNil { + return 0, fmt.Errorf("len of nil pointer") + } + switch v.Kind() { + case reflect.Array, reflect.Chan, reflect.Map, reflect.Slice, reflect.String: + return v.Len(), nil + } + return 0, fmt.Errorf("len of type %s", v.Type()) +} + +// Function invocation + +// call returns the result of evaluating the first argument as a function. +// The function must return 1 result, or 2 results, the second of which is an error. +func call(fn interface{}, args ...interface{}) (interface{}, error) { + v := reflect.ValueOf(fn) + typ := v.Type() + if typ.Kind() != reflect.Func { + return nil, fmt.Errorf("non-function of type %s", typ) + } + if !goodFunc(typ) { + return nil, fmt.Errorf("function called with %d args; should be 1 or 2", typ.NumOut()) + } + numIn := typ.NumIn() + var dddType reflect.Type + if typ.IsVariadic() { + if len(args) < numIn-1 { + return nil, fmt.Errorf("wrong number of args: got %d want at least %d", len(args), numIn-1) + } + dddType = typ.In(numIn - 1).Elem() + } else { + if len(args) != numIn { + return nil, fmt.Errorf("wrong number of args: got %d want %d", len(args), numIn) + } + } + argv := make([]reflect.Value, len(args)) + for i, arg := range args { + value := reflect.ValueOf(arg) + // Compute the expected type. Clumsy because of variadics. + var argType reflect.Type + if !typ.IsVariadic() || i < numIn-1 { + argType = typ.In(i) + } else { + argType = dddType + } + if !value.IsValid() && canBeNil(argType) { + value = reflect.Zero(argType) + } + if !value.Type().AssignableTo(argType) { + return nil, fmt.Errorf("arg %d has type %s; should be %s", i, value.Type(), argType) + } + argv[i] = value + } + result := v.Call(argv) + if len(result) == 2 && !result[1].IsNil() { + return result[0].Interface(), result[1].Interface().(error) + } + return result[0].Interface(), nil +} + +// Boolean logic. + +func truth(a interface{}) bool { + t, _ := isTrue(reflect.ValueOf(a)) + return t +} + +// and computes the Boolean AND of its arguments, returning +// the first false argument it encounters, or the last argument. +func and(arg0 interface{}, args ...interface{}) interface{} { + if !truth(arg0) { + return arg0 + } + for i := range args { + arg0 = args[i] + if !truth(arg0) { + break + } + } + return arg0 +} + +// or computes the Boolean OR of its arguments, returning +// the first true argument it encounters, or the last argument. +func or(arg0 interface{}, args ...interface{}) interface{} { + if truth(arg0) { + return arg0 + } + for i := range args { + arg0 = args[i] + if truth(arg0) { + break + } + } + return arg0 +} + +// not returns the Boolean negation of its argument. +func not(arg interface{}) (truth bool) { + truth, _ = isTrue(reflect.ValueOf(arg)) + return !truth +} + +// Comparison. + +// TODO: Perhaps allow comparison between signed and unsigned integers. + +var ( + errBadComparisonType = errors.New("invalid type for comparison") + errBadComparison = errors.New("incompatible types for comparison") + errNoComparison = errors.New("missing argument for comparison") +) + +type kind int + +const ( + invalidKind kind = iota + boolKind + complexKind + intKind + floatKind + integerKind + stringKind + uintKind +) + +func basicKind(v reflect.Value) (kind, error) { + switch v.Kind() { + case reflect.Bool: + return boolKind, nil + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return intKind, nil + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + return uintKind, nil + case reflect.Float32, reflect.Float64: + return floatKind, nil + case reflect.Complex64, reflect.Complex128: + return complexKind, nil + case reflect.String: + return stringKind, nil + } + return invalidKind, errBadComparisonType +} + +// eq evaluates the comparison a == b || a == c || ... +func eq(arg1 interface{}, arg2 ...interface{}) (bool, error) { + v1 := reflect.ValueOf(arg1) + k1, err := basicKind(v1) + if err != nil { + return false, err + } + if len(arg2) == 0 { + return false, errNoComparison + } + for _, arg := range arg2 { + v2 := reflect.ValueOf(arg) + k2, err := basicKind(v2) + if err != nil { + return false, err + } + truth := false + if k1 != k2 { + // Special case: Can compare integer values regardless of type's sign. + switch { + case k1 == intKind && k2 == uintKind: + truth = v1.Int() >= 0 && uint64(v1.Int()) == v2.Uint() + case k1 == uintKind && k2 == intKind: + truth = v2.Int() >= 0 && v1.Uint() == uint64(v2.Int()) + default: + return false, errBadComparison + } + } else { + switch k1 { + case boolKind: + truth = v1.Bool() == v2.Bool() + case complexKind: + truth = v1.Complex() == v2.Complex() + case floatKind: + truth = v1.Float() == v2.Float() + case intKind: + truth = v1.Int() == v2.Int() + case stringKind: + truth = v1.String() == v2.String() + case uintKind: + truth = v1.Uint() == v2.Uint() + default: + panic("invalid kind") + } + } + if truth { + return true, nil + } + } + return false, nil +} + +// ne evaluates the comparison a != b. +func ne(arg1, arg2 interface{}) (bool, error) { + // != is the inverse of ==. + equal, err := eq(arg1, arg2) + return !equal, err +} + +// lt evaluates the comparison a < b. +func lt(arg1, arg2 interface{}) (bool, error) { + v1 := reflect.ValueOf(arg1) + k1, err := basicKind(v1) + if err != nil { + return false, err + } + v2 := reflect.ValueOf(arg2) + k2, err := basicKind(v2) + if err != nil { + return false, err + } + truth := false + if k1 != k2 { + // Special case: Can compare integer values regardless of type's sign. + switch { + case k1 == intKind && k2 == uintKind: + truth = v1.Int() < 0 || uint64(v1.Int()) < v2.Uint() + case k1 == uintKind && k2 == intKind: + truth = v2.Int() >= 0 && v1.Uint() < uint64(v2.Int()) + default: + return false, errBadComparison + } + } else { + switch k1 { + case boolKind, complexKind: + return false, errBadComparisonType + case floatKind: + truth = v1.Float() < v2.Float() + case intKind: + truth = v1.Int() < v2.Int() + case stringKind: + truth = v1.String() < v2.String() + case uintKind: + truth = v1.Uint() < v2.Uint() + default: + panic("invalid kind") + } + } + return truth, nil +} + +// le evaluates the comparison <= b. +func le(arg1, arg2 interface{}) (bool, error) { + // <= is < or ==. + lessThan, err := lt(arg1, arg2) + if lessThan || err != nil { + return lessThan, err + } + return eq(arg1, arg2) +} + +// gt evaluates the comparison a > b. +func gt(arg1, arg2 interface{}) (bool, error) { + // > is the inverse of <=. + lessOrEqual, err := le(arg1, arg2) + if err != nil { + return false, err + } + return !lessOrEqual, nil +} + +// ge evaluates the comparison a >= b. +func ge(arg1, arg2 interface{}) (bool, error) { + // >= is the inverse of <. + lessThan, err := lt(arg1, arg2) + if err != nil { + return false, err + } + return !lessThan, nil +} + +// HTML escaping. + +var ( + htmlQuot = []byte(""") // shorter than """ + htmlApos = []byte("'") // shorter than "'" and apos was not in HTML until HTML5 + htmlAmp = []byte("&") + htmlLt = []byte("<") + htmlGt = []byte(">") +) + +// HTMLEscape writes to w the escaped HTML equivalent of the plain text data b. +func HTMLEscape(w io.Writer, b []byte) { + last := 0 + for i, c := range b { + var html []byte + switch c { + case '"': + html = htmlQuot + case '\'': + html = htmlApos + case '&': + html = htmlAmp + case '<': + html = htmlLt + case '>': + html = htmlGt + default: + continue + } + w.Write(b[last:i]) + w.Write(html) + last = i + 1 + } + w.Write(b[last:]) +} + +// HTMLEscapeString returns the escaped HTML equivalent of the plain text data s. +func HTMLEscapeString(s string) string { + // Avoid allocation if we can. + if strings.IndexAny(s, `'"&<>`) < 0 { + return s + } + var b bytes.Buffer + HTMLEscape(&b, []byte(s)) + return b.String() +} + +// HTMLEscaper returns the escaped HTML equivalent of the textual +// representation of its arguments. +func HTMLEscaper(args ...interface{}) string { + return HTMLEscapeString(evalArgs(args)) +} + +// JavaScript escaping. + +var ( + jsLowUni = []byte(`\u00`) + hex = []byte("0123456789ABCDEF") + + jsBackslash = []byte(`\\`) + jsApos = []byte(`\'`) + jsQuot = []byte(`\"`) + jsLt = []byte(`\x3C`) + jsGt = []byte(`\x3E`) +) + +// JSEscape writes to w the escaped JavaScript equivalent of the plain text data b. +func JSEscape(w io.Writer, b []byte) { + last := 0 + for i := 0; i < len(b); i++ { + c := b[i] + + if !jsIsSpecial(rune(c)) { + // fast path: nothing to do + continue + } + w.Write(b[last:i]) + + if c < utf8.RuneSelf { + // Quotes, slashes and angle brackets get quoted. + // Control characters get written as \u00XX. + switch c { + case '\\': + w.Write(jsBackslash) + case '\'': + w.Write(jsApos) + case '"': + w.Write(jsQuot) + case '<': + w.Write(jsLt) + case '>': + w.Write(jsGt) + default: + w.Write(jsLowUni) + t, b := c>>4, c&0x0f + w.Write(hex[t : t+1]) + w.Write(hex[b : b+1]) + } + } else { + // Unicode rune. + r, size := utf8.DecodeRune(b[i:]) + if unicode.IsPrint(r) { + w.Write(b[i : i+size]) + } else { + fmt.Fprintf(w, "\\u%04X", r) + } + i += size - 1 + } + last = i + 1 + } + w.Write(b[last:]) +} + +// JSEscapeString returns the escaped JavaScript equivalent of the plain text data s. +func JSEscapeString(s string) string { + // Avoid allocation if we can. + if strings.IndexFunc(s, jsIsSpecial) < 0 { + return s + } + var b bytes.Buffer + JSEscape(&b, []byte(s)) + return b.String() +} + +func jsIsSpecial(r rune) bool { + switch r { + case '\\', '\'', '"', '<', '>': + return true + } + return r < ' ' || utf8.RuneSelf <= r +} + +// JSEscaper returns the escaped JavaScript equivalent of the textual +// representation of its arguments. +func JSEscaper(args ...interface{}) string { + return JSEscapeString(evalArgs(args)) +} + +// URLQueryEscaper returns the escaped value of the textual representation of +// its arguments in a form suitable for embedding in a URL query. +func URLQueryEscaper(args ...interface{}) string { + return url.QueryEscape(evalArgs(args)) +} + +// evalArgs formats the list of arguments into a string. It is therefore equivalent to +// fmt.Sprint(args...) +// except that each argument is indirected (if a pointer), as required, +// using the same rules as the default string evaluation during template +// execution. +func evalArgs(args []interface{}) string { + ok := false + var s string + // Fast path for simple common case. + if len(args) == 1 { + s, ok = args[0].(string) + } + if !ok { + for i, arg := range args { + a, ok := printableValue(reflect.ValueOf(arg)) + if ok { + args[i] = a + } // else left fmt do its thing + } + s = fmt.Sprint(args...) + } + return s +} diff --git a/vendor/github.com/alecthomas/template/go.mod b/vendor/github.com/alecthomas/template/go.mod new file mode 100644 index 00000000..a70670ae --- /dev/null +++ b/vendor/github.com/alecthomas/template/go.mod @@ -0,0 +1 @@ +module github.com/alecthomas/template diff --git a/vendor/github.com/alecthomas/template/helper.go b/vendor/github.com/alecthomas/template/helper.go new file mode 100644 index 00000000..3636fb54 --- /dev/null +++ b/vendor/github.com/alecthomas/template/helper.go @@ -0,0 +1,108 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Helper functions to make constructing templates easier. + +package template + +import ( + "fmt" + "io/ioutil" + "path/filepath" +) + +// Functions and methods to parse templates. + +// Must is a helper that wraps a call to a function returning (*Template, error) +// and panics if the error is non-nil. It is intended for use in variable +// initializations such as +// var t = template.Must(template.New("name").Parse("text")) +func Must(t *Template, err error) *Template { + if err != nil { + panic(err) + } + return t +} + +// ParseFiles creates a new Template and parses the template definitions from +// the named files. The returned template's name will have the (base) name and +// (parsed) contents of the first file. There must be at least one file. +// If an error occurs, parsing stops and the returned *Template is nil. +func ParseFiles(filenames ...string) (*Template, error) { + return parseFiles(nil, filenames...) +} + +// ParseFiles parses the named files and associates the resulting templates with +// t. If an error occurs, parsing stops and the returned template is nil; +// otherwise it is t. There must be at least one file. +func (t *Template) ParseFiles(filenames ...string) (*Template, error) { + return parseFiles(t, filenames...) +} + +// parseFiles is the helper for the method and function. If the argument +// template is nil, it is created from the first file. +func parseFiles(t *Template, filenames ...string) (*Template, error) { + if len(filenames) == 0 { + // Not really a problem, but be consistent. + return nil, fmt.Errorf("template: no files named in call to ParseFiles") + } + for _, filename := range filenames { + b, err := ioutil.ReadFile(filename) + if err != nil { + return nil, err + } + s := string(b) + name := filepath.Base(filename) + // First template becomes return value if not already defined, + // and we use that one for subsequent New calls to associate + // all the templates together. Also, if this file has the same name + // as t, this file becomes the contents of t, so + // t, err := New(name).Funcs(xxx).ParseFiles(name) + // works. Otherwise we create a new template associated with t. + var tmpl *Template + if t == nil { + t = New(name) + } + if name == t.Name() { + tmpl = t + } else { + tmpl = t.New(name) + } + _, err = tmpl.Parse(s) + if err != nil { + return nil, err + } + } + return t, nil +} + +// ParseGlob creates a new Template and parses the template definitions from the +// files identified by the pattern, which must match at least one file. The +// returned template will have the (base) name and (parsed) contents of the +// first file matched by the pattern. ParseGlob is equivalent to calling +// ParseFiles with the list of files matched by the pattern. +func ParseGlob(pattern string) (*Template, error) { + return parseGlob(nil, pattern) +} + +// ParseGlob parses the template definitions in the files identified by the +// pattern and associates the resulting templates with t. The pattern is +// processed by filepath.Glob and must match at least one file. ParseGlob is +// equivalent to calling t.ParseFiles with the list of files matched by the +// pattern. +func (t *Template) ParseGlob(pattern string) (*Template, error) { + return parseGlob(t, pattern) +} + +// parseGlob is the implementation of the function and method ParseGlob. +func parseGlob(t *Template, pattern string) (*Template, error) { + filenames, err := filepath.Glob(pattern) + if err != nil { + return nil, err + } + if len(filenames) == 0 { + return nil, fmt.Errorf("template: pattern matches no files: %#q", pattern) + } + return parseFiles(t, filenames...) +} diff --git a/vendor/github.com/alecthomas/template/parse/lex.go b/vendor/github.com/alecthomas/template/parse/lex.go new file mode 100644 index 00000000..55f1c051 --- /dev/null +++ b/vendor/github.com/alecthomas/template/parse/lex.go @@ -0,0 +1,556 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package parse + +import ( + "fmt" + "strings" + "unicode" + "unicode/utf8" +) + +// item represents a token or text string returned from the scanner. +type item struct { + typ itemType // The type of this item. + pos Pos // The starting position, in bytes, of this item in the input string. + val string // The value of this item. +} + +func (i item) String() string { + switch { + case i.typ == itemEOF: + return "EOF" + case i.typ == itemError: + return i.val + case i.typ > itemKeyword: + return fmt.Sprintf("<%s>", i.val) + case len(i.val) > 10: + return fmt.Sprintf("%.10q...", i.val) + } + return fmt.Sprintf("%q", i.val) +} + +// itemType identifies the type of lex items. +type itemType int + +const ( + itemError itemType = iota // error occurred; value is text of error + itemBool // boolean constant + itemChar // printable ASCII character; grab bag for comma etc. + itemCharConstant // character constant + itemComplex // complex constant (1+2i); imaginary is just a number + itemColonEquals // colon-equals (':=') introducing a declaration + itemEOF + itemField // alphanumeric identifier starting with '.' + itemIdentifier // alphanumeric identifier not starting with '.' + itemLeftDelim // left action delimiter + itemLeftParen // '(' inside action + itemNumber // simple number, including imaginary + itemPipe // pipe symbol + itemRawString // raw quoted string (includes quotes) + itemRightDelim // right action delimiter + itemElideNewline // elide newline after right delim + itemRightParen // ')' inside action + itemSpace // run of spaces separating arguments + itemString // quoted string (includes quotes) + itemText // plain text + itemVariable // variable starting with '$', such as '$' or '$1' or '$hello' + // Keywords appear after all the rest. + itemKeyword // used only to delimit the keywords + itemDot // the cursor, spelled '.' + itemDefine // define keyword + itemElse // else keyword + itemEnd // end keyword + itemIf // if keyword + itemNil // the untyped nil constant, easiest to treat as a keyword + itemRange // range keyword + itemTemplate // template keyword + itemWith // with keyword +) + +var key = map[string]itemType{ + ".": itemDot, + "define": itemDefine, + "else": itemElse, + "end": itemEnd, + "if": itemIf, + "range": itemRange, + "nil": itemNil, + "template": itemTemplate, + "with": itemWith, +} + +const eof = -1 + +// stateFn represents the state of the scanner as a function that returns the next state. +type stateFn func(*lexer) stateFn + +// lexer holds the state of the scanner. +type lexer struct { + name string // the name of the input; used only for error reports + input string // the string being scanned + leftDelim string // start of action + rightDelim string // end of action + state stateFn // the next lexing function to enter + pos Pos // current position in the input + start Pos // start position of this item + width Pos // width of last rune read from input + lastPos Pos // position of most recent item returned by nextItem + items chan item // channel of scanned items + parenDepth int // nesting depth of ( ) exprs +} + +// next returns the next rune in the input. +func (l *lexer) next() rune { + if int(l.pos) >= len(l.input) { + l.width = 0 + return eof + } + r, w := utf8.DecodeRuneInString(l.input[l.pos:]) + l.width = Pos(w) + l.pos += l.width + return r +} + +// peek returns but does not consume the next rune in the input. +func (l *lexer) peek() rune { + r := l.next() + l.backup() + return r +} + +// backup steps back one rune. Can only be called once per call of next. +func (l *lexer) backup() { + l.pos -= l.width +} + +// emit passes an item back to the client. +func (l *lexer) emit(t itemType) { + l.items <- item{t, l.start, l.input[l.start:l.pos]} + l.start = l.pos +} + +// ignore skips over the pending input before this point. +func (l *lexer) ignore() { + l.start = l.pos +} + +// accept consumes the next rune if it's from the valid set. +func (l *lexer) accept(valid string) bool { + if strings.IndexRune(valid, l.next()) >= 0 { + return true + } + l.backup() + return false +} + +// acceptRun consumes a run of runes from the valid set. +func (l *lexer) acceptRun(valid string) { + for strings.IndexRune(valid, l.next()) >= 0 { + } + l.backup() +} + +// lineNumber reports which line we're on, based on the position of +// the previous item returned by nextItem. Doing it this way +// means we don't have to worry about peek double counting. +func (l *lexer) lineNumber() int { + return 1 + strings.Count(l.input[:l.lastPos], "\n") +} + +// errorf returns an error token and terminates the scan by passing +// back a nil pointer that will be the next state, terminating l.nextItem. +func (l *lexer) errorf(format string, args ...interface{}) stateFn { + l.items <- item{itemError, l.start, fmt.Sprintf(format, args...)} + return nil +} + +// nextItem returns the next item from the input. +func (l *lexer) nextItem() item { + item := <-l.items + l.lastPos = item.pos + return item +} + +// lex creates a new scanner for the input string. +func lex(name, input, left, right string) *lexer { + if left == "" { + left = leftDelim + } + if right == "" { + right = rightDelim + } + l := &lexer{ + name: name, + input: input, + leftDelim: left, + rightDelim: right, + items: make(chan item), + } + go l.run() + return l +} + +// run runs the state machine for the lexer. +func (l *lexer) run() { + for l.state = lexText; l.state != nil; { + l.state = l.state(l) + } +} + +// state functions + +const ( + leftDelim = "{{" + rightDelim = "}}" + leftComment = "/*" + rightComment = "*/" +) + +// lexText scans until an opening action delimiter, "{{". +func lexText(l *lexer) stateFn { + for { + if strings.HasPrefix(l.input[l.pos:], l.leftDelim) { + if l.pos > l.start { + l.emit(itemText) + } + return lexLeftDelim + } + if l.next() == eof { + break + } + } + // Correctly reached EOF. + if l.pos > l.start { + l.emit(itemText) + } + l.emit(itemEOF) + return nil +} + +// lexLeftDelim scans the left delimiter, which is known to be present. +func lexLeftDelim(l *lexer) stateFn { + l.pos += Pos(len(l.leftDelim)) + if strings.HasPrefix(l.input[l.pos:], leftComment) { + return lexComment + } + l.emit(itemLeftDelim) + l.parenDepth = 0 + return lexInsideAction +} + +// lexComment scans a comment. The left comment marker is known to be present. +func lexComment(l *lexer) stateFn { + l.pos += Pos(len(leftComment)) + i := strings.Index(l.input[l.pos:], rightComment) + if i < 0 { + return l.errorf("unclosed comment") + } + l.pos += Pos(i + len(rightComment)) + if !strings.HasPrefix(l.input[l.pos:], l.rightDelim) { + return l.errorf("comment ends before closing delimiter") + + } + l.pos += Pos(len(l.rightDelim)) + l.ignore() + return lexText +} + +// lexRightDelim scans the right delimiter, which is known to be present. +func lexRightDelim(l *lexer) stateFn { + l.pos += Pos(len(l.rightDelim)) + l.emit(itemRightDelim) + if l.peek() == '\\' { + l.pos++ + l.emit(itemElideNewline) + } + return lexText +} + +// lexInsideAction scans the elements inside action delimiters. +func lexInsideAction(l *lexer) stateFn { + // Either number, quoted string, or identifier. + // Spaces separate arguments; runs of spaces turn into itemSpace. + // Pipe symbols separate and are emitted. + if strings.HasPrefix(l.input[l.pos:], l.rightDelim+"\\") || strings.HasPrefix(l.input[l.pos:], l.rightDelim) { + if l.parenDepth == 0 { + return lexRightDelim + } + return l.errorf("unclosed left paren") + } + switch r := l.next(); { + case r == eof || isEndOfLine(r): + return l.errorf("unclosed action") + case isSpace(r): + return lexSpace + case r == ':': + if l.next() != '=' { + return l.errorf("expected :=") + } + l.emit(itemColonEquals) + case r == '|': + l.emit(itemPipe) + case r == '"': + return lexQuote + case r == '`': + return lexRawQuote + case r == '$': + return lexVariable + case r == '\'': + return lexChar + case r == '.': + // special look-ahead for ".field" so we don't break l.backup(). + if l.pos < Pos(len(l.input)) { + r := l.input[l.pos] + if r < '0' || '9' < r { + return lexField + } + } + fallthrough // '.' can start a number. + case r == '+' || r == '-' || ('0' <= r && r <= '9'): + l.backup() + return lexNumber + case isAlphaNumeric(r): + l.backup() + return lexIdentifier + case r == '(': + l.emit(itemLeftParen) + l.parenDepth++ + return lexInsideAction + case r == ')': + l.emit(itemRightParen) + l.parenDepth-- + if l.parenDepth < 0 { + return l.errorf("unexpected right paren %#U", r) + } + return lexInsideAction + case r <= unicode.MaxASCII && unicode.IsPrint(r): + l.emit(itemChar) + return lexInsideAction + default: + return l.errorf("unrecognized character in action: %#U", r) + } + return lexInsideAction +} + +// lexSpace scans a run of space characters. +// One space has already been seen. +func lexSpace(l *lexer) stateFn { + for isSpace(l.peek()) { + l.next() + } + l.emit(itemSpace) + return lexInsideAction +} + +// lexIdentifier scans an alphanumeric. +func lexIdentifier(l *lexer) stateFn { +Loop: + for { + switch r := l.next(); { + case isAlphaNumeric(r): + // absorb. + default: + l.backup() + word := l.input[l.start:l.pos] + if !l.atTerminator() { + return l.errorf("bad character %#U", r) + } + switch { + case key[word] > itemKeyword: + l.emit(key[word]) + case word[0] == '.': + l.emit(itemField) + case word == "true", word == "false": + l.emit(itemBool) + default: + l.emit(itemIdentifier) + } + break Loop + } + } + return lexInsideAction +} + +// lexField scans a field: .Alphanumeric. +// The . has been scanned. +func lexField(l *lexer) stateFn { + return lexFieldOrVariable(l, itemField) +} + +// lexVariable scans a Variable: $Alphanumeric. +// The $ has been scanned. +func lexVariable(l *lexer) stateFn { + if l.atTerminator() { // Nothing interesting follows -> "$". + l.emit(itemVariable) + return lexInsideAction + } + return lexFieldOrVariable(l, itemVariable) +} + +// lexVariable scans a field or variable: [.$]Alphanumeric. +// The . or $ has been scanned. +func lexFieldOrVariable(l *lexer, typ itemType) stateFn { + if l.atTerminator() { // Nothing interesting follows -> "." or "$". + if typ == itemVariable { + l.emit(itemVariable) + } else { + l.emit(itemDot) + } + return lexInsideAction + } + var r rune + for { + r = l.next() + if !isAlphaNumeric(r) { + l.backup() + break + } + } + if !l.atTerminator() { + return l.errorf("bad character %#U", r) + } + l.emit(typ) + return lexInsideAction +} + +// atTerminator reports whether the input is at valid termination character to +// appear after an identifier. Breaks .X.Y into two pieces. Also catches cases +// like "$x+2" not being acceptable without a space, in case we decide one +// day to implement arithmetic. +func (l *lexer) atTerminator() bool { + r := l.peek() + if isSpace(r) || isEndOfLine(r) { + return true + } + switch r { + case eof, '.', ',', '|', ':', ')', '(': + return true + } + // Does r start the delimiter? This can be ambiguous (with delim=="//", $x/2 will + // succeed but should fail) but only in extremely rare cases caused by willfully + // bad choice of delimiter. + if rd, _ := utf8.DecodeRuneInString(l.rightDelim); rd == r { + return true + } + return false +} + +// lexChar scans a character constant. The initial quote is already +// scanned. Syntax checking is done by the parser. +func lexChar(l *lexer) stateFn { +Loop: + for { + switch l.next() { + case '\\': + if r := l.next(); r != eof && r != '\n' { + break + } + fallthrough + case eof, '\n': + return l.errorf("unterminated character constant") + case '\'': + break Loop + } + } + l.emit(itemCharConstant) + return lexInsideAction +} + +// lexNumber scans a number: decimal, octal, hex, float, or imaginary. This +// isn't a perfect number scanner - for instance it accepts "." and "0x0.2" +// and "089" - but when it's wrong the input is invalid and the parser (via +// strconv) will notice. +func lexNumber(l *lexer) stateFn { + if !l.scanNumber() { + return l.errorf("bad number syntax: %q", l.input[l.start:l.pos]) + } + if sign := l.peek(); sign == '+' || sign == '-' { + // Complex: 1+2i. No spaces, must end in 'i'. + if !l.scanNumber() || l.input[l.pos-1] != 'i' { + return l.errorf("bad number syntax: %q", l.input[l.start:l.pos]) + } + l.emit(itemComplex) + } else { + l.emit(itemNumber) + } + return lexInsideAction +} + +func (l *lexer) scanNumber() bool { + // Optional leading sign. + l.accept("+-") + // Is it hex? + digits := "0123456789" + if l.accept("0") && l.accept("xX") { + digits = "0123456789abcdefABCDEF" + } + l.acceptRun(digits) + if l.accept(".") { + l.acceptRun(digits) + } + if l.accept("eE") { + l.accept("+-") + l.acceptRun("0123456789") + } + // Is it imaginary? + l.accept("i") + // Next thing mustn't be alphanumeric. + if isAlphaNumeric(l.peek()) { + l.next() + return false + } + return true +} + +// lexQuote scans a quoted string. +func lexQuote(l *lexer) stateFn { +Loop: + for { + switch l.next() { + case '\\': + if r := l.next(); r != eof && r != '\n' { + break + } + fallthrough + case eof, '\n': + return l.errorf("unterminated quoted string") + case '"': + break Loop + } + } + l.emit(itemString) + return lexInsideAction +} + +// lexRawQuote scans a raw quoted string. +func lexRawQuote(l *lexer) stateFn { +Loop: + for { + switch l.next() { + case eof, '\n': + return l.errorf("unterminated raw quoted string") + case '`': + break Loop + } + } + l.emit(itemRawString) + return lexInsideAction +} + +// isSpace reports whether r is a space character. +func isSpace(r rune) bool { + return r == ' ' || r == '\t' +} + +// isEndOfLine reports whether r is an end-of-line character. +func isEndOfLine(r rune) bool { + return r == '\r' || r == '\n' +} + +// isAlphaNumeric reports whether r is an alphabetic, digit, or underscore. +func isAlphaNumeric(r rune) bool { + return r == '_' || unicode.IsLetter(r) || unicode.IsDigit(r) +} diff --git a/vendor/github.com/alecthomas/template/parse/node.go b/vendor/github.com/alecthomas/template/parse/node.go new file mode 100644 index 00000000..55c37f6d --- /dev/null +++ b/vendor/github.com/alecthomas/template/parse/node.go @@ -0,0 +1,834 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Parse nodes. + +package parse + +import ( + "bytes" + "fmt" + "strconv" + "strings" +) + +var textFormat = "%s" // Changed to "%q" in tests for better error messages. + +// A Node is an element in the parse tree. The interface is trivial. +// The interface contains an unexported method so that only +// types local to this package can satisfy it. +type Node interface { + Type() NodeType + String() string + // Copy does a deep copy of the Node and all its components. + // To avoid type assertions, some XxxNodes also have specialized + // CopyXxx methods that return *XxxNode. + Copy() Node + Position() Pos // byte position of start of node in full original input string + // tree returns the containing *Tree. + // It is unexported so all implementations of Node are in this package. + tree() *Tree +} + +// NodeType identifies the type of a parse tree node. +type NodeType int + +// Pos represents a byte position in the original input text from which +// this template was parsed. +type Pos int + +func (p Pos) Position() Pos { + return p +} + +// Type returns itself and provides an easy default implementation +// for embedding in a Node. Embedded in all non-trivial Nodes. +func (t NodeType) Type() NodeType { + return t +} + +const ( + NodeText NodeType = iota // Plain text. + NodeAction // A non-control action such as a field evaluation. + NodeBool // A boolean constant. + NodeChain // A sequence of field accesses. + NodeCommand // An element of a pipeline. + NodeDot // The cursor, dot. + nodeElse // An else action. Not added to tree. + nodeEnd // An end action. Not added to tree. + NodeField // A field or method name. + NodeIdentifier // An identifier; always a function name. + NodeIf // An if action. + NodeList // A list of Nodes. + NodeNil // An untyped nil constant. + NodeNumber // A numerical constant. + NodePipe // A pipeline of commands. + NodeRange // A range action. + NodeString // A string constant. + NodeTemplate // A template invocation action. + NodeVariable // A $ variable. + NodeWith // A with action. +) + +// Nodes. + +// ListNode holds a sequence of nodes. +type ListNode struct { + NodeType + Pos + tr *Tree + Nodes []Node // The element nodes in lexical order. +} + +func (t *Tree) newList(pos Pos) *ListNode { + return &ListNode{tr: t, NodeType: NodeList, Pos: pos} +} + +func (l *ListNode) append(n Node) { + l.Nodes = append(l.Nodes, n) +} + +func (l *ListNode) tree() *Tree { + return l.tr +} + +func (l *ListNode) String() string { + b := new(bytes.Buffer) + for _, n := range l.Nodes { + fmt.Fprint(b, n) + } + return b.String() +} + +func (l *ListNode) CopyList() *ListNode { + if l == nil { + return l + } + n := l.tr.newList(l.Pos) + for _, elem := range l.Nodes { + n.append(elem.Copy()) + } + return n +} + +func (l *ListNode) Copy() Node { + return l.CopyList() +} + +// TextNode holds plain text. +type TextNode struct { + NodeType + Pos + tr *Tree + Text []byte // The text; may span newlines. +} + +func (t *Tree) newText(pos Pos, text string) *TextNode { + return &TextNode{tr: t, NodeType: NodeText, Pos: pos, Text: []byte(text)} +} + +func (t *TextNode) String() string { + return fmt.Sprintf(textFormat, t.Text) +} + +func (t *TextNode) tree() *Tree { + return t.tr +} + +func (t *TextNode) Copy() Node { + return &TextNode{tr: t.tr, NodeType: NodeText, Pos: t.Pos, Text: append([]byte{}, t.Text...)} +} + +// PipeNode holds a pipeline with optional declaration +type PipeNode struct { + NodeType + Pos + tr *Tree + Line int // The line number in the input (deprecated; kept for compatibility) + Decl []*VariableNode // Variable declarations in lexical order. + Cmds []*CommandNode // The commands in lexical order. +} + +func (t *Tree) newPipeline(pos Pos, line int, decl []*VariableNode) *PipeNode { + return &PipeNode{tr: t, NodeType: NodePipe, Pos: pos, Line: line, Decl: decl} +} + +func (p *PipeNode) append(command *CommandNode) { + p.Cmds = append(p.Cmds, command) +} + +func (p *PipeNode) String() string { + s := "" + if len(p.Decl) > 0 { + for i, v := range p.Decl { + if i > 0 { + s += ", " + } + s += v.String() + } + s += " := " + } + for i, c := range p.Cmds { + if i > 0 { + s += " | " + } + s += c.String() + } + return s +} + +func (p *PipeNode) tree() *Tree { + return p.tr +} + +func (p *PipeNode) CopyPipe() *PipeNode { + if p == nil { + return p + } + var decl []*VariableNode + for _, d := range p.Decl { + decl = append(decl, d.Copy().(*VariableNode)) + } + n := p.tr.newPipeline(p.Pos, p.Line, decl) + for _, c := range p.Cmds { + n.append(c.Copy().(*CommandNode)) + } + return n +} + +func (p *PipeNode) Copy() Node { + return p.CopyPipe() +} + +// ActionNode holds an action (something bounded by delimiters). +// Control actions have their own nodes; ActionNode represents simple +// ones such as field evaluations and parenthesized pipelines. +type ActionNode struct { + NodeType + Pos + tr *Tree + Line int // The line number in the input (deprecated; kept for compatibility) + Pipe *PipeNode // The pipeline in the action. +} + +func (t *Tree) newAction(pos Pos, line int, pipe *PipeNode) *ActionNode { + return &ActionNode{tr: t, NodeType: NodeAction, Pos: pos, Line: line, Pipe: pipe} +} + +func (a *ActionNode) String() string { + return fmt.Sprintf("{{%s}}", a.Pipe) + +} + +func (a *ActionNode) tree() *Tree { + return a.tr +} + +func (a *ActionNode) Copy() Node { + return a.tr.newAction(a.Pos, a.Line, a.Pipe.CopyPipe()) + +} + +// CommandNode holds a command (a pipeline inside an evaluating action). +type CommandNode struct { + NodeType + Pos + tr *Tree + Args []Node // Arguments in lexical order: Identifier, field, or constant. +} + +func (t *Tree) newCommand(pos Pos) *CommandNode { + return &CommandNode{tr: t, NodeType: NodeCommand, Pos: pos} +} + +func (c *CommandNode) append(arg Node) { + c.Args = append(c.Args, arg) +} + +func (c *CommandNode) String() string { + s := "" + for i, arg := range c.Args { + if i > 0 { + s += " " + } + if arg, ok := arg.(*PipeNode); ok { + s += "(" + arg.String() + ")" + continue + } + s += arg.String() + } + return s +} + +func (c *CommandNode) tree() *Tree { + return c.tr +} + +func (c *CommandNode) Copy() Node { + if c == nil { + return c + } + n := c.tr.newCommand(c.Pos) + for _, c := range c.Args { + n.append(c.Copy()) + } + return n +} + +// IdentifierNode holds an identifier. +type IdentifierNode struct { + NodeType + Pos + tr *Tree + Ident string // The identifier's name. +} + +// NewIdentifier returns a new IdentifierNode with the given identifier name. +func NewIdentifier(ident string) *IdentifierNode { + return &IdentifierNode{NodeType: NodeIdentifier, Ident: ident} +} + +// SetPos sets the position. NewIdentifier is a public method so we can't modify its signature. +// Chained for convenience. +// TODO: fix one day? +func (i *IdentifierNode) SetPos(pos Pos) *IdentifierNode { + i.Pos = pos + return i +} + +// SetTree sets the parent tree for the node. NewIdentifier is a public method so we can't modify its signature. +// Chained for convenience. +// TODO: fix one day? +func (i *IdentifierNode) SetTree(t *Tree) *IdentifierNode { + i.tr = t + return i +} + +func (i *IdentifierNode) String() string { + return i.Ident +} + +func (i *IdentifierNode) tree() *Tree { + return i.tr +} + +func (i *IdentifierNode) Copy() Node { + return NewIdentifier(i.Ident).SetTree(i.tr).SetPos(i.Pos) +} + +// VariableNode holds a list of variable names, possibly with chained field +// accesses. The dollar sign is part of the (first) name. +type VariableNode struct { + NodeType + Pos + tr *Tree + Ident []string // Variable name and fields in lexical order. +} + +func (t *Tree) newVariable(pos Pos, ident string) *VariableNode { + return &VariableNode{tr: t, NodeType: NodeVariable, Pos: pos, Ident: strings.Split(ident, ".")} +} + +func (v *VariableNode) String() string { + s := "" + for i, id := range v.Ident { + if i > 0 { + s += "." + } + s += id + } + return s +} + +func (v *VariableNode) tree() *Tree { + return v.tr +} + +func (v *VariableNode) Copy() Node { + return &VariableNode{tr: v.tr, NodeType: NodeVariable, Pos: v.Pos, Ident: append([]string{}, v.Ident...)} +} + +// DotNode holds the special identifier '.'. +type DotNode struct { + NodeType + Pos + tr *Tree +} + +func (t *Tree) newDot(pos Pos) *DotNode { + return &DotNode{tr: t, NodeType: NodeDot, Pos: pos} +} + +func (d *DotNode) Type() NodeType { + // Override method on embedded NodeType for API compatibility. + // TODO: Not really a problem; could change API without effect but + // api tool complains. + return NodeDot +} + +func (d *DotNode) String() string { + return "." +} + +func (d *DotNode) tree() *Tree { + return d.tr +} + +func (d *DotNode) Copy() Node { + return d.tr.newDot(d.Pos) +} + +// NilNode holds the special identifier 'nil' representing an untyped nil constant. +type NilNode struct { + NodeType + Pos + tr *Tree +} + +func (t *Tree) newNil(pos Pos) *NilNode { + return &NilNode{tr: t, NodeType: NodeNil, Pos: pos} +} + +func (n *NilNode) Type() NodeType { + // Override method on embedded NodeType for API compatibility. + // TODO: Not really a problem; could change API without effect but + // api tool complains. + return NodeNil +} + +func (n *NilNode) String() string { + return "nil" +} + +func (n *NilNode) tree() *Tree { + return n.tr +} + +func (n *NilNode) Copy() Node { + return n.tr.newNil(n.Pos) +} + +// FieldNode holds a field (identifier starting with '.'). +// The names may be chained ('.x.y'). +// The period is dropped from each ident. +type FieldNode struct { + NodeType + Pos + tr *Tree + Ident []string // The identifiers in lexical order. +} + +func (t *Tree) newField(pos Pos, ident string) *FieldNode { + return &FieldNode{tr: t, NodeType: NodeField, Pos: pos, Ident: strings.Split(ident[1:], ".")} // [1:] to drop leading period +} + +func (f *FieldNode) String() string { + s := "" + for _, id := range f.Ident { + s += "." + id + } + return s +} + +func (f *FieldNode) tree() *Tree { + return f.tr +} + +func (f *FieldNode) Copy() Node { + return &FieldNode{tr: f.tr, NodeType: NodeField, Pos: f.Pos, Ident: append([]string{}, f.Ident...)} +} + +// ChainNode holds a term followed by a chain of field accesses (identifier starting with '.'). +// The names may be chained ('.x.y'). +// The periods are dropped from each ident. +type ChainNode struct { + NodeType + Pos + tr *Tree + Node Node + Field []string // The identifiers in lexical order. +} + +func (t *Tree) newChain(pos Pos, node Node) *ChainNode { + return &ChainNode{tr: t, NodeType: NodeChain, Pos: pos, Node: node} +} + +// Add adds the named field (which should start with a period) to the end of the chain. +func (c *ChainNode) Add(field string) { + if len(field) == 0 || field[0] != '.' { + panic("no dot in field") + } + field = field[1:] // Remove leading dot. + if field == "" { + panic("empty field") + } + c.Field = append(c.Field, field) +} + +func (c *ChainNode) String() string { + s := c.Node.String() + if _, ok := c.Node.(*PipeNode); ok { + s = "(" + s + ")" + } + for _, field := range c.Field { + s += "." + field + } + return s +} + +func (c *ChainNode) tree() *Tree { + return c.tr +} + +func (c *ChainNode) Copy() Node { + return &ChainNode{tr: c.tr, NodeType: NodeChain, Pos: c.Pos, Node: c.Node, Field: append([]string{}, c.Field...)} +} + +// BoolNode holds a boolean constant. +type BoolNode struct { + NodeType + Pos + tr *Tree + True bool // The value of the boolean constant. +} + +func (t *Tree) newBool(pos Pos, true bool) *BoolNode { + return &BoolNode{tr: t, NodeType: NodeBool, Pos: pos, True: true} +} + +func (b *BoolNode) String() string { + if b.True { + return "true" + } + return "false" +} + +func (b *BoolNode) tree() *Tree { + return b.tr +} + +func (b *BoolNode) Copy() Node { + return b.tr.newBool(b.Pos, b.True) +} + +// NumberNode holds a number: signed or unsigned integer, float, or complex. +// The value is parsed and stored under all the types that can represent the value. +// This simulates in a small amount of code the behavior of Go's ideal constants. +type NumberNode struct { + NodeType + Pos + tr *Tree + IsInt bool // Number has an integral value. + IsUint bool // Number has an unsigned integral value. + IsFloat bool // Number has a floating-point value. + IsComplex bool // Number is complex. + Int64 int64 // The signed integer value. + Uint64 uint64 // The unsigned integer value. + Float64 float64 // The floating-point value. + Complex128 complex128 // The complex value. + Text string // The original textual representation from the input. +} + +func (t *Tree) newNumber(pos Pos, text string, typ itemType) (*NumberNode, error) { + n := &NumberNode{tr: t, NodeType: NodeNumber, Pos: pos, Text: text} + switch typ { + case itemCharConstant: + rune, _, tail, err := strconv.UnquoteChar(text[1:], text[0]) + if err != nil { + return nil, err + } + if tail != "'" { + return nil, fmt.Errorf("malformed character constant: %s", text) + } + n.Int64 = int64(rune) + n.IsInt = true + n.Uint64 = uint64(rune) + n.IsUint = true + n.Float64 = float64(rune) // odd but those are the rules. + n.IsFloat = true + return n, nil + case itemComplex: + // fmt.Sscan can parse the pair, so let it do the work. + if _, err := fmt.Sscan(text, &n.Complex128); err != nil { + return nil, err + } + n.IsComplex = true + n.simplifyComplex() + return n, nil + } + // Imaginary constants can only be complex unless they are zero. + if len(text) > 0 && text[len(text)-1] == 'i' { + f, err := strconv.ParseFloat(text[:len(text)-1], 64) + if err == nil { + n.IsComplex = true + n.Complex128 = complex(0, f) + n.simplifyComplex() + return n, nil + } + } + // Do integer test first so we get 0x123 etc. + u, err := strconv.ParseUint(text, 0, 64) // will fail for -0; fixed below. + if err == nil { + n.IsUint = true + n.Uint64 = u + } + i, err := strconv.ParseInt(text, 0, 64) + if err == nil { + n.IsInt = true + n.Int64 = i + if i == 0 { + n.IsUint = true // in case of -0. + n.Uint64 = u + } + } + // If an integer extraction succeeded, promote the float. + if n.IsInt { + n.IsFloat = true + n.Float64 = float64(n.Int64) + } else if n.IsUint { + n.IsFloat = true + n.Float64 = float64(n.Uint64) + } else { + f, err := strconv.ParseFloat(text, 64) + if err == nil { + n.IsFloat = true + n.Float64 = f + // If a floating-point extraction succeeded, extract the int if needed. + if !n.IsInt && float64(int64(f)) == f { + n.IsInt = true + n.Int64 = int64(f) + } + if !n.IsUint && float64(uint64(f)) == f { + n.IsUint = true + n.Uint64 = uint64(f) + } + } + } + if !n.IsInt && !n.IsUint && !n.IsFloat { + return nil, fmt.Errorf("illegal number syntax: %q", text) + } + return n, nil +} + +// simplifyComplex pulls out any other types that are represented by the complex number. +// These all require that the imaginary part be zero. +func (n *NumberNode) simplifyComplex() { + n.IsFloat = imag(n.Complex128) == 0 + if n.IsFloat { + n.Float64 = real(n.Complex128) + n.IsInt = float64(int64(n.Float64)) == n.Float64 + if n.IsInt { + n.Int64 = int64(n.Float64) + } + n.IsUint = float64(uint64(n.Float64)) == n.Float64 + if n.IsUint { + n.Uint64 = uint64(n.Float64) + } + } +} + +func (n *NumberNode) String() string { + return n.Text +} + +func (n *NumberNode) tree() *Tree { + return n.tr +} + +func (n *NumberNode) Copy() Node { + nn := new(NumberNode) + *nn = *n // Easy, fast, correct. + return nn +} + +// StringNode holds a string constant. The value has been "unquoted". +type StringNode struct { + NodeType + Pos + tr *Tree + Quoted string // The original text of the string, with quotes. + Text string // The string, after quote processing. +} + +func (t *Tree) newString(pos Pos, orig, text string) *StringNode { + return &StringNode{tr: t, NodeType: NodeString, Pos: pos, Quoted: orig, Text: text} +} + +func (s *StringNode) String() string { + return s.Quoted +} + +func (s *StringNode) tree() *Tree { + return s.tr +} + +func (s *StringNode) Copy() Node { + return s.tr.newString(s.Pos, s.Quoted, s.Text) +} + +// endNode represents an {{end}} action. +// It does not appear in the final parse tree. +type endNode struct { + NodeType + Pos + tr *Tree +} + +func (t *Tree) newEnd(pos Pos) *endNode { + return &endNode{tr: t, NodeType: nodeEnd, Pos: pos} +} + +func (e *endNode) String() string { + return "{{end}}" +} + +func (e *endNode) tree() *Tree { + return e.tr +} + +func (e *endNode) Copy() Node { + return e.tr.newEnd(e.Pos) +} + +// elseNode represents an {{else}} action. Does not appear in the final tree. +type elseNode struct { + NodeType + Pos + tr *Tree + Line int // The line number in the input (deprecated; kept for compatibility) +} + +func (t *Tree) newElse(pos Pos, line int) *elseNode { + return &elseNode{tr: t, NodeType: nodeElse, Pos: pos, Line: line} +} + +func (e *elseNode) Type() NodeType { + return nodeElse +} + +func (e *elseNode) String() string { + return "{{else}}" +} + +func (e *elseNode) tree() *Tree { + return e.tr +} + +func (e *elseNode) Copy() Node { + return e.tr.newElse(e.Pos, e.Line) +} + +// BranchNode is the common representation of if, range, and with. +type BranchNode struct { + NodeType + Pos + tr *Tree + Line int // The line number in the input (deprecated; kept for compatibility) + Pipe *PipeNode // The pipeline to be evaluated. + List *ListNode // What to execute if the value is non-empty. + ElseList *ListNode // What to execute if the value is empty (nil if absent). +} + +func (b *BranchNode) String() string { + name := "" + switch b.NodeType { + case NodeIf: + name = "if" + case NodeRange: + name = "range" + case NodeWith: + name = "with" + default: + panic("unknown branch type") + } + if b.ElseList != nil { + return fmt.Sprintf("{{%s %s}}%s{{else}}%s{{end}}", name, b.Pipe, b.List, b.ElseList) + } + return fmt.Sprintf("{{%s %s}}%s{{end}}", name, b.Pipe, b.List) +} + +func (b *BranchNode) tree() *Tree { + return b.tr +} + +func (b *BranchNode) Copy() Node { + switch b.NodeType { + case NodeIf: + return b.tr.newIf(b.Pos, b.Line, b.Pipe, b.List, b.ElseList) + case NodeRange: + return b.tr.newRange(b.Pos, b.Line, b.Pipe, b.List, b.ElseList) + case NodeWith: + return b.tr.newWith(b.Pos, b.Line, b.Pipe, b.List, b.ElseList) + default: + panic("unknown branch type") + } +} + +// IfNode represents an {{if}} action and its commands. +type IfNode struct { + BranchNode +} + +func (t *Tree) newIf(pos Pos, line int, pipe *PipeNode, list, elseList *ListNode) *IfNode { + return &IfNode{BranchNode{tr: t, NodeType: NodeIf, Pos: pos, Line: line, Pipe: pipe, List: list, ElseList: elseList}} +} + +func (i *IfNode) Copy() Node { + return i.tr.newIf(i.Pos, i.Line, i.Pipe.CopyPipe(), i.List.CopyList(), i.ElseList.CopyList()) +} + +// RangeNode represents a {{range}} action and its commands. +type RangeNode struct { + BranchNode +} + +func (t *Tree) newRange(pos Pos, line int, pipe *PipeNode, list, elseList *ListNode) *RangeNode { + return &RangeNode{BranchNode{tr: t, NodeType: NodeRange, Pos: pos, Line: line, Pipe: pipe, List: list, ElseList: elseList}} +} + +func (r *RangeNode) Copy() Node { + return r.tr.newRange(r.Pos, r.Line, r.Pipe.CopyPipe(), r.List.CopyList(), r.ElseList.CopyList()) +} + +// WithNode represents a {{with}} action and its commands. +type WithNode struct { + BranchNode +} + +func (t *Tree) newWith(pos Pos, line int, pipe *PipeNode, list, elseList *ListNode) *WithNode { + return &WithNode{BranchNode{tr: t, NodeType: NodeWith, Pos: pos, Line: line, Pipe: pipe, List: list, ElseList: elseList}} +} + +func (w *WithNode) Copy() Node { + return w.tr.newWith(w.Pos, w.Line, w.Pipe.CopyPipe(), w.List.CopyList(), w.ElseList.CopyList()) +} + +// TemplateNode represents a {{template}} action. +type TemplateNode struct { + NodeType + Pos + tr *Tree + Line int // The line number in the input (deprecated; kept for compatibility) + Name string // The name of the template (unquoted). + Pipe *PipeNode // The command to evaluate as dot for the template. +} + +func (t *Tree) newTemplate(pos Pos, line int, name string, pipe *PipeNode) *TemplateNode { + return &TemplateNode{tr: t, NodeType: NodeTemplate, Pos: pos, Line: line, Name: name, Pipe: pipe} +} + +func (t *TemplateNode) String() string { + if t.Pipe == nil { + return fmt.Sprintf("{{template %q}}", t.Name) + } + return fmt.Sprintf("{{template %q %s}}", t.Name, t.Pipe) +} + +func (t *TemplateNode) tree() *Tree { + return t.tr +} + +func (t *TemplateNode) Copy() Node { + return t.tr.newTemplate(t.Pos, t.Line, t.Name, t.Pipe.CopyPipe()) +} diff --git a/vendor/github.com/alecthomas/template/parse/parse.go b/vendor/github.com/alecthomas/template/parse/parse.go new file mode 100644 index 00000000..0d77ade8 --- /dev/null +++ b/vendor/github.com/alecthomas/template/parse/parse.go @@ -0,0 +1,700 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package parse builds parse trees for templates as defined by text/template +// and html/template. Clients should use those packages to construct templates +// rather than this one, which provides shared internal data structures not +// intended for general use. +package parse + +import ( + "bytes" + "fmt" + "runtime" + "strconv" + "strings" +) + +// Tree is the representation of a single parsed template. +type Tree struct { + Name string // name of the template represented by the tree. + ParseName string // name of the top-level template during parsing, for error messages. + Root *ListNode // top-level root of the tree. + text string // text parsed to create the template (or its parent) + // Parsing only; cleared after parse. + funcs []map[string]interface{} + lex *lexer + token [3]item // three-token lookahead for parser. + peekCount int + vars []string // variables defined at the moment. +} + +// Copy returns a copy of the Tree. Any parsing state is discarded. +func (t *Tree) Copy() *Tree { + if t == nil { + return nil + } + return &Tree{ + Name: t.Name, + ParseName: t.ParseName, + Root: t.Root.CopyList(), + text: t.text, + } +} + +// Parse returns a map from template name to parse.Tree, created by parsing the +// templates described in the argument string. The top-level template will be +// given the specified name. If an error is encountered, parsing stops and an +// empty map is returned with the error. +func Parse(name, text, leftDelim, rightDelim string, funcs ...map[string]interface{}) (treeSet map[string]*Tree, err error) { + treeSet = make(map[string]*Tree) + t := New(name) + t.text = text + _, err = t.Parse(text, leftDelim, rightDelim, treeSet, funcs...) + return +} + +// next returns the next token. +func (t *Tree) next() item { + if t.peekCount > 0 { + t.peekCount-- + } else { + t.token[0] = t.lex.nextItem() + } + return t.token[t.peekCount] +} + +// backup backs the input stream up one token. +func (t *Tree) backup() { + t.peekCount++ +} + +// backup2 backs the input stream up two tokens. +// The zeroth token is already there. +func (t *Tree) backup2(t1 item) { + t.token[1] = t1 + t.peekCount = 2 +} + +// backup3 backs the input stream up three tokens +// The zeroth token is already there. +func (t *Tree) backup3(t2, t1 item) { // Reverse order: we're pushing back. + t.token[1] = t1 + t.token[2] = t2 + t.peekCount = 3 +} + +// peek returns but does not consume the next token. +func (t *Tree) peek() item { + if t.peekCount > 0 { + return t.token[t.peekCount-1] + } + t.peekCount = 1 + t.token[0] = t.lex.nextItem() + return t.token[0] +} + +// nextNonSpace returns the next non-space token. +func (t *Tree) nextNonSpace() (token item) { + for { + token = t.next() + if token.typ != itemSpace { + break + } + } + return token +} + +// peekNonSpace returns but does not consume the next non-space token. +func (t *Tree) peekNonSpace() (token item) { + for { + token = t.next() + if token.typ != itemSpace { + break + } + } + t.backup() + return token +} + +// Parsing. + +// New allocates a new parse tree with the given name. +func New(name string, funcs ...map[string]interface{}) *Tree { + return &Tree{ + Name: name, + funcs: funcs, + } +} + +// ErrorContext returns a textual representation of the location of the node in the input text. +// The receiver is only used when the node does not have a pointer to the tree inside, +// which can occur in old code. +func (t *Tree) ErrorContext(n Node) (location, context string) { + pos := int(n.Position()) + tree := n.tree() + if tree == nil { + tree = t + } + text := tree.text[:pos] + byteNum := strings.LastIndex(text, "\n") + if byteNum == -1 { + byteNum = pos // On first line. + } else { + byteNum++ // After the newline. + byteNum = pos - byteNum + } + lineNum := 1 + strings.Count(text, "\n") + context = n.String() + if len(context) > 20 { + context = fmt.Sprintf("%.20s...", context) + } + return fmt.Sprintf("%s:%d:%d", tree.ParseName, lineNum, byteNum), context +} + +// errorf formats the error and terminates processing. +func (t *Tree) errorf(format string, args ...interface{}) { + t.Root = nil + format = fmt.Sprintf("template: %s:%d: %s", t.ParseName, t.lex.lineNumber(), format) + panic(fmt.Errorf(format, args...)) +} + +// error terminates processing. +func (t *Tree) error(err error) { + t.errorf("%s", err) +} + +// expect consumes the next token and guarantees it has the required type. +func (t *Tree) expect(expected itemType, context string) item { + token := t.nextNonSpace() + if token.typ != expected { + t.unexpected(token, context) + } + return token +} + +// expectOneOf consumes the next token and guarantees it has one of the required types. +func (t *Tree) expectOneOf(expected1, expected2 itemType, context string) item { + token := t.nextNonSpace() + if token.typ != expected1 && token.typ != expected2 { + t.unexpected(token, context) + } + return token +} + +// unexpected complains about the token and terminates processing. +func (t *Tree) unexpected(token item, context string) { + t.errorf("unexpected %s in %s", token, context) +} + +// recover is the handler that turns panics into returns from the top level of Parse. +func (t *Tree) recover(errp *error) { + e := recover() + if e != nil { + if _, ok := e.(runtime.Error); ok { + panic(e) + } + if t != nil { + t.stopParse() + } + *errp = e.(error) + } + return +} + +// startParse initializes the parser, using the lexer. +func (t *Tree) startParse(funcs []map[string]interface{}, lex *lexer) { + t.Root = nil + t.lex = lex + t.vars = []string{"$"} + t.funcs = funcs +} + +// stopParse terminates parsing. +func (t *Tree) stopParse() { + t.lex = nil + t.vars = nil + t.funcs = nil +} + +// Parse parses the template definition string to construct a representation of +// the template for execution. If either action delimiter string is empty, the +// default ("{{" or "}}") is used. Embedded template definitions are added to +// the treeSet map. +func (t *Tree) Parse(text, leftDelim, rightDelim string, treeSet map[string]*Tree, funcs ...map[string]interface{}) (tree *Tree, err error) { + defer t.recover(&err) + t.ParseName = t.Name + t.startParse(funcs, lex(t.Name, text, leftDelim, rightDelim)) + t.text = text + t.parse(treeSet) + t.add(treeSet) + t.stopParse() + return t, nil +} + +// add adds tree to the treeSet. +func (t *Tree) add(treeSet map[string]*Tree) { + tree := treeSet[t.Name] + if tree == nil || IsEmptyTree(tree.Root) { + treeSet[t.Name] = t + return + } + if !IsEmptyTree(t.Root) { + t.errorf("template: multiple definition of template %q", t.Name) + } +} + +// IsEmptyTree reports whether this tree (node) is empty of everything but space. +func IsEmptyTree(n Node) bool { + switch n := n.(type) { + case nil: + return true + case *ActionNode: + case *IfNode: + case *ListNode: + for _, node := range n.Nodes { + if !IsEmptyTree(node) { + return false + } + } + return true + case *RangeNode: + case *TemplateNode: + case *TextNode: + return len(bytes.TrimSpace(n.Text)) == 0 + case *WithNode: + default: + panic("unknown node: " + n.String()) + } + return false +} + +// parse is the top-level parser for a template, essentially the same +// as itemList except it also parses {{define}} actions. +// It runs to EOF. +func (t *Tree) parse(treeSet map[string]*Tree) (next Node) { + t.Root = t.newList(t.peek().pos) + for t.peek().typ != itemEOF { + if t.peek().typ == itemLeftDelim { + delim := t.next() + if t.nextNonSpace().typ == itemDefine { + newT := New("definition") // name will be updated once we know it. + newT.text = t.text + newT.ParseName = t.ParseName + newT.startParse(t.funcs, t.lex) + newT.parseDefinition(treeSet) + continue + } + t.backup2(delim) + } + n := t.textOrAction() + if n.Type() == nodeEnd { + t.errorf("unexpected %s", n) + } + t.Root.append(n) + } + return nil +} + +// parseDefinition parses a {{define}} ... {{end}} template definition and +// installs the definition in the treeSet map. The "define" keyword has already +// been scanned. +func (t *Tree) parseDefinition(treeSet map[string]*Tree) { + const context = "define clause" + name := t.expectOneOf(itemString, itemRawString, context) + var err error + t.Name, err = strconv.Unquote(name.val) + if err != nil { + t.error(err) + } + t.expect(itemRightDelim, context) + var end Node + t.Root, end = t.itemList() + if end.Type() != nodeEnd { + t.errorf("unexpected %s in %s", end, context) + } + t.add(treeSet) + t.stopParse() +} + +// itemList: +// textOrAction* +// Terminates at {{end}} or {{else}}, returned separately. +func (t *Tree) itemList() (list *ListNode, next Node) { + list = t.newList(t.peekNonSpace().pos) + for t.peekNonSpace().typ != itemEOF { + n := t.textOrAction() + switch n.Type() { + case nodeEnd, nodeElse: + return list, n + } + list.append(n) + } + t.errorf("unexpected EOF") + return +} + +// textOrAction: +// text | action +func (t *Tree) textOrAction() Node { + switch token := t.nextNonSpace(); token.typ { + case itemElideNewline: + return t.elideNewline() + case itemText: + return t.newText(token.pos, token.val) + case itemLeftDelim: + return t.action() + default: + t.unexpected(token, "input") + } + return nil +} + +// elideNewline: +// Remove newlines trailing rightDelim if \\ is present. +func (t *Tree) elideNewline() Node { + token := t.peek() + if token.typ != itemText { + t.unexpected(token, "input") + return nil + } + + t.next() + stripped := strings.TrimLeft(token.val, "\n\r") + diff := len(token.val) - len(stripped) + if diff > 0 { + // This is a bit nasty. We mutate the token in-place to remove + // preceding newlines. + token.pos += Pos(diff) + token.val = stripped + } + return t.newText(token.pos, token.val) +} + +// Action: +// control +// command ("|" command)* +// Left delim is past. Now get actions. +// First word could be a keyword such as range. +func (t *Tree) action() (n Node) { + switch token := t.nextNonSpace(); token.typ { + case itemElse: + return t.elseControl() + case itemEnd: + return t.endControl() + case itemIf: + return t.ifControl() + case itemRange: + return t.rangeControl() + case itemTemplate: + return t.templateControl() + case itemWith: + return t.withControl() + } + t.backup() + // Do not pop variables; they persist until "end". + return t.newAction(t.peek().pos, t.lex.lineNumber(), t.pipeline("command")) +} + +// Pipeline: +// declarations? command ('|' command)* +func (t *Tree) pipeline(context string) (pipe *PipeNode) { + var decl []*VariableNode + pos := t.peekNonSpace().pos + // Are there declarations? + for { + if v := t.peekNonSpace(); v.typ == itemVariable { + t.next() + // Since space is a token, we need 3-token look-ahead here in the worst case: + // in "$x foo" we need to read "foo" (as opposed to ":=") to know that $x is an + // argument variable rather than a declaration. So remember the token + // adjacent to the variable so we can push it back if necessary. + tokenAfterVariable := t.peek() + if next := t.peekNonSpace(); next.typ == itemColonEquals || (next.typ == itemChar && next.val == ",") { + t.nextNonSpace() + variable := t.newVariable(v.pos, v.val) + decl = append(decl, variable) + t.vars = append(t.vars, v.val) + if next.typ == itemChar && next.val == "," { + if context == "range" && len(decl) < 2 { + continue + } + t.errorf("too many declarations in %s", context) + } + } else if tokenAfterVariable.typ == itemSpace { + t.backup3(v, tokenAfterVariable) + } else { + t.backup2(v) + } + } + break + } + pipe = t.newPipeline(pos, t.lex.lineNumber(), decl) + for { + switch token := t.nextNonSpace(); token.typ { + case itemRightDelim, itemRightParen: + if len(pipe.Cmds) == 0 { + t.errorf("missing value for %s", context) + } + if token.typ == itemRightParen { + t.backup() + } + return + case itemBool, itemCharConstant, itemComplex, itemDot, itemField, itemIdentifier, + itemNumber, itemNil, itemRawString, itemString, itemVariable, itemLeftParen: + t.backup() + pipe.append(t.command()) + default: + t.unexpected(token, context) + } + } +} + +func (t *Tree) parseControl(allowElseIf bool, context string) (pos Pos, line int, pipe *PipeNode, list, elseList *ListNode) { + defer t.popVars(len(t.vars)) + line = t.lex.lineNumber() + pipe = t.pipeline(context) + var next Node + list, next = t.itemList() + switch next.Type() { + case nodeEnd: //done + case nodeElse: + if allowElseIf { + // Special case for "else if". If the "else" is followed immediately by an "if", + // the elseControl will have left the "if" token pending. Treat + // {{if a}}_{{else if b}}_{{end}} + // as + // {{if a}}_{{else}}{{if b}}_{{end}}{{end}}. + // To do this, parse the if as usual and stop at it {{end}}; the subsequent{{end}} + // is assumed. This technique works even for long if-else-if chains. + // TODO: Should we allow else-if in with and range? + if t.peek().typ == itemIf { + t.next() // Consume the "if" token. + elseList = t.newList(next.Position()) + elseList.append(t.ifControl()) + // Do not consume the next item - only one {{end}} required. + break + } + } + elseList, next = t.itemList() + if next.Type() != nodeEnd { + t.errorf("expected end; found %s", next) + } + } + return pipe.Position(), line, pipe, list, elseList +} + +// If: +// {{if pipeline}} itemList {{end}} +// {{if pipeline}} itemList {{else}} itemList {{end}} +// If keyword is past. +func (t *Tree) ifControl() Node { + return t.newIf(t.parseControl(true, "if")) +} + +// Range: +// {{range pipeline}} itemList {{end}} +// {{range pipeline}} itemList {{else}} itemList {{end}} +// Range keyword is past. +func (t *Tree) rangeControl() Node { + return t.newRange(t.parseControl(false, "range")) +} + +// With: +// {{with pipeline}} itemList {{end}} +// {{with pipeline}} itemList {{else}} itemList {{end}} +// If keyword is past. +func (t *Tree) withControl() Node { + return t.newWith(t.parseControl(false, "with")) +} + +// End: +// {{end}} +// End keyword is past. +func (t *Tree) endControl() Node { + return t.newEnd(t.expect(itemRightDelim, "end").pos) +} + +// Else: +// {{else}} +// Else keyword is past. +func (t *Tree) elseControl() Node { + // Special case for "else if". + peek := t.peekNonSpace() + if peek.typ == itemIf { + // We see "{{else if ... " but in effect rewrite it to {{else}}{{if ... ". + return t.newElse(peek.pos, t.lex.lineNumber()) + } + return t.newElse(t.expect(itemRightDelim, "else").pos, t.lex.lineNumber()) +} + +// Template: +// {{template stringValue pipeline}} +// Template keyword is past. The name must be something that can evaluate +// to a string. +func (t *Tree) templateControl() Node { + var name string + token := t.nextNonSpace() + switch token.typ { + case itemString, itemRawString: + s, err := strconv.Unquote(token.val) + if err != nil { + t.error(err) + } + name = s + default: + t.unexpected(token, "template invocation") + } + var pipe *PipeNode + if t.nextNonSpace().typ != itemRightDelim { + t.backup() + // Do not pop variables; they persist until "end". + pipe = t.pipeline("template") + } + return t.newTemplate(token.pos, t.lex.lineNumber(), name, pipe) +} + +// command: +// operand (space operand)* +// space-separated arguments up to a pipeline character or right delimiter. +// we consume the pipe character but leave the right delim to terminate the action. +func (t *Tree) command() *CommandNode { + cmd := t.newCommand(t.peekNonSpace().pos) + for { + t.peekNonSpace() // skip leading spaces. + operand := t.operand() + if operand != nil { + cmd.append(operand) + } + switch token := t.next(); token.typ { + case itemSpace: + continue + case itemError: + t.errorf("%s", token.val) + case itemRightDelim, itemRightParen: + t.backup() + case itemPipe: + default: + t.errorf("unexpected %s in operand; missing space?", token) + } + break + } + if len(cmd.Args) == 0 { + t.errorf("empty command") + } + return cmd +} + +// operand: +// term .Field* +// An operand is a space-separated component of a command, +// a term possibly followed by field accesses. +// A nil return means the next item is not an operand. +func (t *Tree) operand() Node { + node := t.term() + if node == nil { + return nil + } + if t.peek().typ == itemField { + chain := t.newChain(t.peek().pos, node) + for t.peek().typ == itemField { + chain.Add(t.next().val) + } + // Compatibility with original API: If the term is of type NodeField + // or NodeVariable, just put more fields on the original. + // Otherwise, keep the Chain node. + // TODO: Switch to Chains always when we can. + switch node.Type() { + case NodeField: + node = t.newField(chain.Position(), chain.String()) + case NodeVariable: + node = t.newVariable(chain.Position(), chain.String()) + default: + node = chain + } + } + return node +} + +// term: +// literal (number, string, nil, boolean) +// function (identifier) +// . +// .Field +// $ +// '(' pipeline ')' +// A term is a simple "expression". +// A nil return means the next item is not a term. +func (t *Tree) term() Node { + switch token := t.nextNonSpace(); token.typ { + case itemError: + t.errorf("%s", token.val) + case itemIdentifier: + if !t.hasFunction(token.val) { + t.errorf("function %q not defined", token.val) + } + return NewIdentifier(token.val).SetTree(t).SetPos(token.pos) + case itemDot: + return t.newDot(token.pos) + case itemNil: + return t.newNil(token.pos) + case itemVariable: + return t.useVar(token.pos, token.val) + case itemField: + return t.newField(token.pos, token.val) + case itemBool: + return t.newBool(token.pos, token.val == "true") + case itemCharConstant, itemComplex, itemNumber: + number, err := t.newNumber(token.pos, token.val, token.typ) + if err != nil { + t.error(err) + } + return number + case itemLeftParen: + pipe := t.pipeline("parenthesized pipeline") + if token := t.next(); token.typ != itemRightParen { + t.errorf("unclosed right paren: unexpected %s", token) + } + return pipe + case itemString, itemRawString: + s, err := strconv.Unquote(token.val) + if err != nil { + t.error(err) + } + return t.newString(token.pos, token.val, s) + } + t.backup() + return nil +} + +// hasFunction reports if a function name exists in the Tree's maps. +func (t *Tree) hasFunction(name string) bool { + for _, funcMap := range t.funcs { + if funcMap == nil { + continue + } + if funcMap[name] != nil { + return true + } + } + return false +} + +// popVars trims the variable list to the specified length +func (t *Tree) popVars(n int) { + t.vars = t.vars[:n] +} + +// useVar returns a node for a variable reference. It errors if the +// variable is not defined. +func (t *Tree) useVar(pos Pos, name string) Node { + v := t.newVariable(pos, name) + for _, varName := range t.vars { + if varName == v.Ident[0] { + return v + } + } + t.errorf("undefined variable %q", v.Ident[0]) + return nil +} diff --git a/vendor/github.com/alecthomas/template/template.go b/vendor/github.com/alecthomas/template/template.go new file mode 100644 index 00000000..447ed2ab --- /dev/null +++ b/vendor/github.com/alecthomas/template/template.go @@ -0,0 +1,218 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package template + +import ( + "fmt" + "reflect" + + "github.com/alecthomas/template/parse" +) + +// common holds the information shared by related templates. +type common struct { + tmpl map[string]*Template + // We use two maps, one for parsing and one for execution. + // This separation makes the API cleaner since it doesn't + // expose reflection to the client. + parseFuncs FuncMap + execFuncs map[string]reflect.Value +} + +// Template is the representation of a parsed template. The *parse.Tree +// field is exported only for use by html/template and should be treated +// as unexported by all other clients. +type Template struct { + name string + *parse.Tree + *common + leftDelim string + rightDelim string +} + +// New allocates a new template with the given name. +func New(name string) *Template { + return &Template{ + name: name, + } +} + +// Name returns the name of the template. +func (t *Template) Name() string { + return t.name +} + +// New allocates a new template associated with the given one and with the same +// delimiters. The association, which is transitive, allows one template to +// invoke another with a {{template}} action. +func (t *Template) New(name string) *Template { + t.init() + return &Template{ + name: name, + common: t.common, + leftDelim: t.leftDelim, + rightDelim: t.rightDelim, + } +} + +func (t *Template) init() { + if t.common == nil { + t.common = new(common) + t.tmpl = make(map[string]*Template) + t.parseFuncs = make(FuncMap) + t.execFuncs = make(map[string]reflect.Value) + } +} + +// Clone returns a duplicate of the template, including all associated +// templates. The actual representation is not copied, but the name space of +// associated templates is, so further calls to Parse in the copy will add +// templates to the copy but not to the original. Clone can be used to prepare +// common templates and use them with variant definitions for other templates +// by adding the variants after the clone is made. +func (t *Template) Clone() (*Template, error) { + nt := t.copy(nil) + nt.init() + nt.tmpl[t.name] = nt + for k, v := range t.tmpl { + if k == t.name { // Already installed. + continue + } + // The associated templates share nt's common structure. + tmpl := v.copy(nt.common) + nt.tmpl[k] = tmpl + } + for k, v := range t.parseFuncs { + nt.parseFuncs[k] = v + } + for k, v := range t.execFuncs { + nt.execFuncs[k] = v + } + return nt, nil +} + +// copy returns a shallow copy of t, with common set to the argument. +func (t *Template) copy(c *common) *Template { + nt := New(t.name) + nt.Tree = t.Tree + nt.common = c + nt.leftDelim = t.leftDelim + nt.rightDelim = t.rightDelim + return nt +} + +// AddParseTree creates a new template with the name and parse tree +// and associates it with t. +func (t *Template) AddParseTree(name string, tree *parse.Tree) (*Template, error) { + if t.common != nil && t.tmpl[name] != nil { + return nil, fmt.Errorf("template: redefinition of template %q", name) + } + nt := t.New(name) + nt.Tree = tree + t.tmpl[name] = nt + return nt, nil +} + +// Templates returns a slice of the templates associated with t, including t +// itself. +func (t *Template) Templates() []*Template { + if t.common == nil { + return nil + } + // Return a slice so we don't expose the map. + m := make([]*Template, 0, len(t.tmpl)) + for _, v := range t.tmpl { + m = append(m, v) + } + return m +} + +// Delims sets the action delimiters to the specified strings, to be used in +// subsequent calls to Parse, ParseFiles, or ParseGlob. Nested template +// definitions will inherit the settings. An empty delimiter stands for the +// corresponding default: {{ or }}. +// The return value is the template, so calls can be chained. +func (t *Template) Delims(left, right string) *Template { + t.leftDelim = left + t.rightDelim = right + return t +} + +// Funcs adds the elements of the argument map to the template's function map. +// It panics if a value in the map is not a function with appropriate return +// type. However, it is legal to overwrite elements of the map. The return +// value is the template, so calls can be chained. +func (t *Template) Funcs(funcMap FuncMap) *Template { + t.init() + addValueFuncs(t.execFuncs, funcMap) + addFuncs(t.parseFuncs, funcMap) + return t +} + +// Lookup returns the template with the given name that is associated with t, +// or nil if there is no such template. +func (t *Template) Lookup(name string) *Template { + if t.common == nil { + return nil + } + return t.tmpl[name] +} + +// Parse parses a string into a template. Nested template definitions will be +// associated with the top-level template t. Parse may be called multiple times +// to parse definitions of templates to associate with t. It is an error if a +// resulting template is non-empty (contains content other than template +// definitions) and would replace a non-empty template with the same name. +// (In multiple calls to Parse with the same receiver template, only one call +// can contain text other than space, comments, and template definitions.) +func (t *Template) Parse(text string) (*Template, error) { + t.init() + trees, err := parse.Parse(t.name, text, t.leftDelim, t.rightDelim, t.parseFuncs, builtins) + if err != nil { + return nil, err + } + // Add the newly parsed trees, including the one for t, into our common structure. + for name, tree := range trees { + // If the name we parsed is the name of this template, overwrite this template. + // The associate method checks it's not a redefinition. + tmpl := t + if name != t.name { + tmpl = t.New(name) + } + // Even if t == tmpl, we need to install it in the common.tmpl map. + if replace, err := t.associate(tmpl, tree); err != nil { + return nil, err + } else if replace { + tmpl.Tree = tree + } + tmpl.leftDelim = t.leftDelim + tmpl.rightDelim = t.rightDelim + } + return t, nil +} + +// associate installs the new template into the group of templates associated +// with t. It is an error to reuse a name except to overwrite an empty +// template. The two are already known to share the common structure. +// The boolean return value reports wither to store this tree as t.Tree. +func (t *Template) associate(new *Template, tree *parse.Tree) (bool, error) { + if new.common != t.common { + panic("internal error: associate not common") + } + name := new.name + if old := t.tmpl[name]; old != nil { + oldIsEmpty := parse.IsEmptyTree(old.Root) + newIsEmpty := parse.IsEmptyTree(tree.Root) + if newIsEmpty { + // Whether old is empty or not, new is empty; no reason to replace old. + return false, nil + } + if !oldIsEmpty { + return false, fmt.Errorf("template: redefinition of template %q", name) + } + } + t.tmpl[name] = new + return true, nil +} diff --git a/vendor/github.com/alecthomas/units/COPYING b/vendor/github.com/alecthomas/units/COPYING new file mode 100644 index 00000000..2993ec08 --- /dev/null +++ b/vendor/github.com/alecthomas/units/COPYING @@ -0,0 +1,19 @@ +Copyright (C) 2014 Alec Thomas + +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. diff --git a/vendor/github.com/alecthomas/units/README.md b/vendor/github.com/alecthomas/units/README.md new file mode 100644 index 00000000..bee884e3 --- /dev/null +++ b/vendor/github.com/alecthomas/units/README.md @@ -0,0 +1,11 @@ +# Units - Helpful unit multipliers and functions for Go + +The goal of this package is to have functionality similar to the [time](http://golang.org/pkg/time/) package. + +It allows for code like this: + +```go +n, err := ParseBase2Bytes("1KB") +// n == 1024 +n = units.Mebibyte * 512 +``` diff --git a/vendor/github.com/alecthomas/units/bytes.go b/vendor/github.com/alecthomas/units/bytes.go new file mode 100644 index 00000000..61d0ca47 --- /dev/null +++ b/vendor/github.com/alecthomas/units/bytes.go @@ -0,0 +1,85 @@ +package units + +// Base2Bytes is the old non-SI power-of-2 byte scale (1024 bytes in a kilobyte, +// etc.). +type Base2Bytes int64 + +// Base-2 byte units. +const ( + Kibibyte Base2Bytes = 1024 + KiB = Kibibyte + Mebibyte = Kibibyte * 1024 + MiB = Mebibyte + Gibibyte = Mebibyte * 1024 + GiB = Gibibyte + Tebibyte = Gibibyte * 1024 + TiB = Tebibyte + Pebibyte = Tebibyte * 1024 + PiB = Pebibyte + Exbibyte = Pebibyte * 1024 + EiB = Exbibyte +) + +var ( + bytesUnitMap = MakeUnitMap("iB", "B", 1024) + oldBytesUnitMap = MakeUnitMap("B", "B", 1024) +) + +// ParseBase2Bytes supports both iB and B in base-2 multipliers. That is, KB +// and KiB are both 1024. +// However "kB", which is the correct SI spelling of 1000 Bytes, is rejected. +func ParseBase2Bytes(s string) (Base2Bytes, error) { + n, err := ParseUnit(s, bytesUnitMap) + if err != nil { + n, err = ParseUnit(s, oldBytesUnitMap) + } + return Base2Bytes(n), err +} + +func (b Base2Bytes) String() string { + return ToString(int64(b), 1024, "iB", "B") +} + +var ( + metricBytesUnitMap = MakeUnitMap("B", "B", 1000) +) + +// MetricBytes are SI byte units (1000 bytes in a kilobyte). +type MetricBytes SI + +// SI base-10 byte units. +const ( + Kilobyte MetricBytes = 1000 + KB = Kilobyte + Megabyte = Kilobyte * 1000 + MB = Megabyte + Gigabyte = Megabyte * 1000 + GB = Gigabyte + Terabyte = Gigabyte * 1000 + TB = Terabyte + Petabyte = Terabyte * 1000 + PB = Petabyte + Exabyte = Petabyte * 1000 + EB = Exabyte +) + +// ParseMetricBytes parses base-10 metric byte units. That is, KB is 1000 bytes. +func ParseMetricBytes(s string) (MetricBytes, error) { + n, err := ParseUnit(s, metricBytesUnitMap) + return MetricBytes(n), err +} + +// TODO: represents 1000B as uppercase "KB", while SI standard requires "kB". +func (m MetricBytes) String() string { + return ToString(int64(m), 1000, "B", "B") +} + +// ParseStrictBytes supports both iB and B suffixes for base 2 and metric, +// respectively. That is, KiB represents 1024 and kB, KB represent 1000. +func ParseStrictBytes(s string) (int64, error) { + n, err := ParseUnit(s, bytesUnitMap) + if err != nil { + n, err = ParseUnit(s, metricBytesUnitMap) + } + return int64(n), err +} diff --git a/vendor/github.com/alecthomas/units/doc.go b/vendor/github.com/alecthomas/units/doc.go new file mode 100644 index 00000000..156ae386 --- /dev/null +++ b/vendor/github.com/alecthomas/units/doc.go @@ -0,0 +1,13 @@ +// Package units provides helpful unit multipliers and functions for Go. +// +// The goal of this package is to have functionality similar to the time [1] package. +// +// +// [1] http://golang.org/pkg/time/ +// +// It allows for code like this: +// +// n, err := ParseBase2Bytes("1KB") +// // n == 1024 +// n = units.Mebibyte * 512 +package units diff --git a/vendor/github.com/alecthomas/units/go.mod b/vendor/github.com/alecthomas/units/go.mod new file mode 100644 index 00000000..c7fb91f2 --- /dev/null +++ b/vendor/github.com/alecthomas/units/go.mod @@ -0,0 +1,3 @@ +module github.com/alecthomas/units + +require github.com/stretchr/testify v1.4.0 diff --git a/vendor/github.com/alecthomas/units/go.sum b/vendor/github.com/alecthomas/units/go.sum new file mode 100644 index 00000000..8fdee585 --- /dev/null +++ b/vendor/github.com/alecthomas/units/go.sum @@ -0,0 +1,11 @@ +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +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/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/vendor/github.com/alecthomas/units/si.go b/vendor/github.com/alecthomas/units/si.go new file mode 100644 index 00000000..99b2fa4f --- /dev/null +++ b/vendor/github.com/alecthomas/units/si.go @@ -0,0 +1,50 @@ +package units + +// SI units. +type SI int64 + +// SI unit multiples. +const ( + Kilo SI = 1000 + Mega = Kilo * 1000 + Giga = Mega * 1000 + Tera = Giga * 1000 + Peta = Tera * 1000 + Exa = Peta * 1000 +) + +func MakeUnitMap(suffix, shortSuffix string, scale int64) map[string]float64 { + res := map[string]float64{ + shortSuffix: 1, + // see below for "k" / "K" + "M" + suffix: float64(scale * scale), + "G" + suffix: float64(scale * scale * scale), + "T" + suffix: float64(scale * scale * scale * scale), + "P" + suffix: float64(scale * scale * scale * scale * scale), + "E" + suffix: float64(scale * scale * scale * scale * scale * scale), + } + + // Standard SI prefixes use lowercase "k" for kilo = 1000. + // For compatibility, and to be fool-proof, we accept both "k" and "K" in metric mode. + // + // However, official binary prefixes are always capitalized - "KiB" - + // and we specifically never parse "kB" as 1024B because: + // + // (1) people pedantic enough to use lowercase according to SI unlikely to abuse "k" to mean 1024 :-) + // + // (2) Use of capital K for 1024 was an informal tradition predating IEC prefixes: + // "The binary meaning of the kilobyte for 1024 bytes typically uses the symbol KB, with an + // uppercase letter K." + // -- https://en.wikipedia.org/wiki/Kilobyte#Base_2_(1024_bytes) + // "Capitalization of the letter K became the de facto standard for binary notation, although this + // could not be extended to higher powers, and use of the lowercase k did persist.[13][14][15]" + // -- https://en.wikipedia.org/wiki/Binary_prefix#History + // See also the extensive https://en.wikipedia.org/wiki/Timeline_of_binary_prefixes. + if scale == 1024 { + res["K"+suffix] = float64(scale) + } else { + res["k"+suffix] = float64(scale) + res["K"+suffix] = float64(scale) + } + return res +} diff --git a/vendor/github.com/alecthomas/units/util.go b/vendor/github.com/alecthomas/units/util.go new file mode 100644 index 00000000..6527e92d --- /dev/null +++ b/vendor/github.com/alecthomas/units/util.go @@ -0,0 +1,138 @@ +package units + +import ( + "errors" + "fmt" + "strings" +) + +var ( + siUnits = []string{"", "K", "M", "G", "T", "P", "E"} +) + +func ToString(n int64, scale int64, suffix, baseSuffix string) string { + mn := len(siUnits) + out := make([]string, mn) + for i, m := range siUnits { + if n%scale != 0 || i == 0 && n == 0 { + s := suffix + if i == 0 { + s = baseSuffix + } + out[mn-1-i] = fmt.Sprintf("%d%s%s", n%scale, m, s) + } + n /= scale + if n == 0 { + break + } + } + return strings.Join(out, "") +} + +// Below code ripped straight from http://golang.org/src/pkg/time/format.go?s=33392:33438#L1123 +var errLeadingInt = errors.New("units: bad [0-9]*") // never printed + +// leadingInt consumes the leading [0-9]* from s. +func leadingInt(s string) (x int64, rem string, err error) { + i := 0 + for ; i < len(s); i++ { + c := s[i] + if c < '0' || c > '9' { + break + } + if x >= (1<<63-10)/10 { + // overflow + return 0, "", errLeadingInt + } + x = x*10 + int64(c) - '0' + } + return x, s[i:], nil +} + +func ParseUnit(s string, unitMap map[string]float64) (int64, error) { + // [-+]?([0-9]*(\.[0-9]*)?[a-z]+)+ + orig := s + f := float64(0) + neg := false + + // Consume [-+]? + if s != "" { + c := s[0] + if c == '-' || c == '+' { + neg = c == '-' + s = s[1:] + } + } + // Special case: if all that is left is "0", this is zero. + if s == "0" { + return 0, nil + } + if s == "" { + return 0, errors.New("units: invalid " + orig) + } + for s != "" { + g := float64(0) // this element of the sequence + + var x int64 + var err error + + // The next character must be [0-9.] + if !(s[0] == '.' || ('0' <= s[0] && s[0] <= '9')) { + return 0, errors.New("units: invalid " + orig) + } + // Consume [0-9]* + pl := len(s) + x, s, err = leadingInt(s) + if err != nil { + return 0, errors.New("units: invalid " + orig) + } + g = float64(x) + pre := pl != len(s) // whether we consumed anything before a period + + // Consume (\.[0-9]*)? + post := false + if s != "" && s[0] == '.' { + s = s[1:] + pl := len(s) + x, s, err = leadingInt(s) + if err != nil { + return 0, errors.New("units: invalid " + orig) + } + scale := 1.0 + for n := pl - len(s); n > 0; n-- { + scale *= 10 + } + g += float64(x) / scale + post = pl != len(s) + } + if !pre && !post { + // no digits (e.g. ".s" or "-.s") + return 0, errors.New("units: invalid " + orig) + } + + // Consume unit. + i := 0 + for ; i < len(s); i++ { + c := s[i] + if c == '.' || ('0' <= c && c <= '9') { + break + } + } + u := s[:i] + s = s[i:] + unit, ok := unitMap[u] + if !ok { + return 0, errors.New("units: unknown unit " + u + " in " + orig) + } + + f += g * unit + } + + if neg { + f = -f + } + if f < float64(-1<<63) || f > float64(1<<63-1) { + return 0, errors.New("units: overflow parsing unit") + } + return int64(f), nil +} diff --git a/vendor/github.com/konsorten/go-windows-terminal-sequences/LICENSE b/vendor/github.com/konsorten/go-windows-terminal-sequences/LICENSE new file mode 100644 index 00000000..14127cd8 --- /dev/null +++ b/vendor/github.com/konsorten/go-windows-terminal-sequences/LICENSE @@ -0,0 +1,9 @@ +(The MIT License) + +Copyright (c) 2017 marvin + konsorten GmbH (open-source@konsorten.de) + +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. diff --git a/vendor/github.com/konsorten/go-windows-terminal-sequences/README.md b/vendor/github.com/konsorten/go-windows-terminal-sequences/README.md new file mode 100644 index 00000000..09a4a35c --- /dev/null +++ b/vendor/github.com/konsorten/go-windows-terminal-sequences/README.md @@ -0,0 +1,42 @@ +# Windows Terminal Sequences + +This library allow for enabling Windows terminal color support for Go. + +See [Console Virtual Terminal Sequences](https://docs.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences) for details. + +## Usage + +```go +import ( + "syscall" + + sequences "github.com/konsorten/go-windows-terminal-sequences" +) + +func main() { + sequences.EnableVirtualTerminalProcessing(syscall.Stdout, true) +} + +``` + +## Authors + +The tool is sponsored by the [marvin + konsorten GmbH](http://www.konsorten.de). + +We thank all the authors who provided code to this library: + +* Felix Kollmann +* Nicolas Perraut +* @dirty49374 + +## License + +(The MIT License) + +Copyright (c) 2018 marvin + konsorten GmbH (open-source@konsorten.de) + +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. diff --git a/vendor/github.com/konsorten/go-windows-terminal-sequences/go.mod b/vendor/github.com/konsorten/go-windows-terminal-sequences/go.mod new file mode 100644 index 00000000..716c6131 --- /dev/null +++ b/vendor/github.com/konsorten/go-windows-terminal-sequences/go.mod @@ -0,0 +1 @@ +module github.com/konsorten/go-windows-terminal-sequences diff --git a/vendor/github.com/konsorten/go-windows-terminal-sequences/sequences.go b/vendor/github.com/konsorten/go-windows-terminal-sequences/sequences.go new file mode 100644 index 00000000..57f530ae --- /dev/null +++ b/vendor/github.com/konsorten/go-windows-terminal-sequences/sequences.go @@ -0,0 +1,35 @@ +// +build windows + +package sequences + +import ( + "syscall" +) + +var ( + kernel32Dll *syscall.LazyDLL = syscall.NewLazyDLL("Kernel32.dll") + setConsoleMode *syscall.LazyProc = kernel32Dll.NewProc("SetConsoleMode") +) + +func EnableVirtualTerminalProcessing(stream syscall.Handle, enable bool) error { + const ENABLE_VIRTUAL_TERMINAL_PROCESSING uint32 = 0x4 + + var mode uint32 + err := syscall.GetConsoleMode(syscall.Stdout, &mode) + if err != nil { + return err + } + + if enable { + mode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING + } else { + mode &^= ENABLE_VIRTUAL_TERMINAL_PROCESSING + } + + ret, _, err := setConsoleMode.Call(uintptr(stream), uintptr(mode)) + if ret == 0 { + return err + } + + return nil +} diff --git a/vendor/github.com/konsorten/go-windows-terminal-sequences/sequences_dummy.go b/vendor/github.com/konsorten/go-windows-terminal-sequences/sequences_dummy.go new file mode 100644 index 00000000..df61a6f2 --- /dev/null +++ b/vendor/github.com/konsorten/go-windows-terminal-sequences/sequences_dummy.go @@ -0,0 +1,11 @@ +// +build linux darwin + +package sequences + +import ( + "fmt" +) + +func EnableVirtualTerminalProcessing(stream uintptr, enable bool) error { + return fmt.Errorf("windows only package") +} diff --git a/vendor/github.com/prometheus/common/log/eventlog_formatter.go b/vendor/github.com/prometheus/common/log/eventlog_formatter.go new file mode 100644 index 00000000..bcf68e6f --- /dev/null +++ b/vendor/github.com/prometheus/common/log/eventlog_formatter.go @@ -0,0 +1,89 @@ +// Copyright 2015 The Prometheus Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// +build windows + +package log + +import ( + "fmt" + "os" + + "golang.org/x/sys/windows/svc/eventlog" + + "github.com/sirupsen/logrus" +) + +func init() { + setEventlogFormatter = func(l logger, name string, debugAsInfo bool) error { + if name == "" { + return fmt.Errorf("missing name parameter") + } + + fmter, err := newEventlogger(name, debugAsInfo, l.entry.Logger.Formatter) + if err != nil { + fmt.Fprintf(os.Stderr, "error creating eventlog formatter: %v\n", err) + l.Errorf("can't connect logger to eventlog: %v", err) + return err + } + l.entry.Logger.Formatter = fmter + return nil + } +} + +type eventlogger struct { + log *eventlog.Log + debugAsInfo bool + wrap logrus.Formatter +} + +func newEventlogger(name string, debugAsInfo bool, fmter logrus.Formatter) (*eventlogger, error) { + logHandle, err := eventlog.Open(name) + if err != nil { + return nil, err + } + return &eventlogger{log: logHandle, debugAsInfo: debugAsInfo, wrap: fmter}, nil +} + +func (s *eventlogger) Format(e *logrus.Entry) ([]byte, error) { + data, err := s.wrap.Format(e) + if err != nil { + fmt.Fprintf(os.Stderr, "eventlogger: can't format entry: %v\n", err) + return data, err + } + + switch e.Level { + case logrus.PanicLevel: + fallthrough + case logrus.FatalLevel: + fallthrough + case logrus.ErrorLevel: + err = s.log.Error(102, e.Message) + case logrus.WarnLevel: + err = s.log.Warning(101, e.Message) + case logrus.InfoLevel: + err = s.log.Info(100, e.Message) + case logrus.DebugLevel: + if s.debugAsInfo { + err = s.log.Info(100, e.Message) + } + default: + err = s.log.Info(100, e.Message) + } + + if err != nil { + fmt.Fprintf(os.Stderr, "eventlogger: can't send log to eventlog: %v\n", err) + } + + return data, err +} diff --git a/vendor/github.com/prometheus/common/log/log.go b/vendor/github.com/prometheus/common/log/log.go new file mode 100644 index 00000000..b6adbce1 --- /dev/null +++ b/vendor/github.com/prometheus/common/log/log.go @@ -0,0 +1,368 @@ +// Copyright 2015 The Prometheus Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package log implements logging via logrus. +// +// Deprecated: This package has been replaced with github.com/prometheus/common/promlog. + +package log + +import ( + "fmt" + "io" + "io/ioutil" + "log" + "net/url" + "os" + "runtime" + "strconv" + "strings" + + "github.com/sirupsen/logrus" + "gopkg.in/alecthomas/kingpin.v2" +) + +// setSyslogFormatter is nil if the target architecture does not support syslog. +var setSyslogFormatter func(logger, string, string) error + +// setEventlogFormatter is nil if the target OS does not support Eventlog (i.e., is not Windows). +var setEventlogFormatter func(logger, string, bool) error + +func setJSONFormatter() { + origLogger.Formatter = &logrus.JSONFormatter{} +} + +type loggerSettings struct { + level string + format string +} + +func (s *loggerSettings) apply(ctx *kingpin.ParseContext) error { + err := baseLogger.SetLevel(s.level) + if err != nil { + return err + } + err = baseLogger.SetFormat(s.format) + return err +} + +// AddFlags adds the flags used by this package to the Kingpin application. +// To use the default Kingpin application, call AddFlags(kingpin.CommandLine) +func AddFlags(a *kingpin.Application) { + s := loggerSettings{} + a.Flag("log.level", "Only log messages with the given severity or above. Valid levels: [debug, info, warn, error, fatal]"). + Default(origLogger.Level.String()). + StringVar(&s.level) + defaultFormat := url.URL{Scheme: "logger", Opaque: "stderr"} + a.Flag("log.format", `Set the log target and format. Example: "logger:syslog?appname=bob&local=7" or "logger:stdout?json=true"`). + Default(defaultFormat.String()). + StringVar(&s.format) + a.Action(s.apply) +} + +// Logger is the interface for loggers used in the Prometheus components. +type Logger interface { + Debug(...interface{}) + Debugln(...interface{}) + Debugf(string, ...interface{}) + + Info(...interface{}) + Infoln(...interface{}) + Infof(string, ...interface{}) + + Warn(...interface{}) + Warnln(...interface{}) + Warnf(string, ...interface{}) + + Error(...interface{}) + Errorln(...interface{}) + Errorf(string, ...interface{}) + + Fatal(...interface{}) + Fatalln(...interface{}) + Fatalf(string, ...interface{}) + + With(key string, value interface{}) Logger + + SetFormat(string) error + SetLevel(string) error +} + +type logger struct { + entry *logrus.Entry +} + +func (l logger) With(key string, value interface{}) Logger { + return logger{l.entry.WithField(key, value)} +} + +// Debug logs a message at level Debug on the standard logger. +func (l logger) Debug(args ...interface{}) { + l.sourced().Debug(args...) +} + +// Debug logs a message at level Debug on the standard logger. +func (l logger) Debugln(args ...interface{}) { + l.sourced().Debugln(args...) +} + +// Debugf logs a message at level Debug on the standard logger. +func (l logger) Debugf(format string, args ...interface{}) { + l.sourced().Debugf(format, args...) +} + +// Info logs a message at level Info on the standard logger. +func (l logger) Info(args ...interface{}) { + l.sourced().Info(args...) +} + +// Info logs a message at level Info on the standard logger. +func (l logger) Infoln(args ...interface{}) { + l.sourced().Infoln(args...) +} + +// Infof logs a message at level Info on the standard logger. +func (l logger) Infof(format string, args ...interface{}) { + l.sourced().Infof(format, args...) +} + +// Warn logs a message at level Warn on the standard logger. +func (l logger) Warn(args ...interface{}) { + l.sourced().Warn(args...) +} + +// Warn logs a message at level Warn on the standard logger. +func (l logger) Warnln(args ...interface{}) { + l.sourced().Warnln(args...) +} + +// Warnf logs a message at level Warn on the standard logger. +func (l logger) Warnf(format string, args ...interface{}) { + l.sourced().Warnf(format, args...) +} + +// Error logs a message at level Error on the standard logger. +func (l logger) Error(args ...interface{}) { + l.sourced().Error(args...) +} + +// Error logs a message at level Error on the standard logger. +func (l logger) Errorln(args ...interface{}) { + l.sourced().Errorln(args...) +} + +// Errorf logs a message at level Error on the standard logger. +func (l logger) Errorf(format string, args ...interface{}) { + l.sourced().Errorf(format, args...) +} + +// Fatal logs a message at level Fatal on the standard logger. +func (l logger) Fatal(args ...interface{}) { + l.sourced().Fatal(args...) +} + +// Fatal logs a message at level Fatal on the standard logger. +func (l logger) Fatalln(args ...interface{}) { + l.sourced().Fatalln(args...) +} + +// Fatalf logs a message at level Fatal on the standard logger. +func (l logger) Fatalf(format string, args ...interface{}) { + l.sourced().Fatalf(format, args...) +} + +func (l logger) SetLevel(level string) error { + lvl, err := logrus.ParseLevel(level) + if err != nil { + return err + } + + l.entry.Logger.Level = lvl + return nil +} + +func (l logger) SetFormat(format string) error { + u, err := url.Parse(format) + if err != nil { + return err + } + if u.Scheme != "logger" { + return fmt.Errorf("invalid scheme %s", u.Scheme) + } + jsonq := u.Query().Get("json") + if jsonq == "true" { + setJSONFormatter() + } + + switch u.Opaque { + case "syslog": + if setSyslogFormatter == nil { + return fmt.Errorf("system does not support syslog") + } + appname := u.Query().Get("appname") + facility := u.Query().Get("local") + return setSyslogFormatter(l, appname, facility) + case "eventlog": + if setEventlogFormatter == nil { + return fmt.Errorf("system does not support eventlog") + } + name := u.Query().Get("name") + debugAsInfo := false + debugAsInfoRaw := u.Query().Get("debugAsInfo") + if parsedDebugAsInfo, err := strconv.ParseBool(debugAsInfoRaw); err == nil { + debugAsInfo = parsedDebugAsInfo + } + return setEventlogFormatter(l, name, debugAsInfo) + case "stdout": + l.entry.Logger.Out = os.Stdout + case "stderr": + l.entry.Logger.Out = os.Stderr + default: + return fmt.Errorf("unsupported logger %q", u.Opaque) + } + return nil +} + +// sourced adds a source field to the logger that contains +// the file name and line where the logging happened. +func (l logger) sourced() *logrus.Entry { + _, file, line, ok := runtime.Caller(2) + if !ok { + file = "" + line = 1 + } else { + slash := strings.LastIndex(file, "/") + file = file[slash+1:] + } + return l.entry.WithField("source", fmt.Sprintf("%s:%d", file, line)) +} + +var origLogger = logrus.New() +var baseLogger = logger{entry: logrus.NewEntry(origLogger)} + +// Base returns the default Logger logging to +func Base() Logger { + return baseLogger +} + +// NewLogger returns a new Logger logging to out. +func NewLogger(w io.Writer) Logger { + l := logrus.New() + l.Out = w + return logger{entry: logrus.NewEntry(l)} +} + +// NewNopLogger returns a logger that discards all log messages. +func NewNopLogger() Logger { + l := logrus.New() + l.Out = ioutil.Discard + return logger{entry: logrus.NewEntry(l)} +} + +// With adds a field to the logger. +func With(key string, value interface{}) Logger { + return baseLogger.With(key, value) +} + +// Debug logs a message at level Debug on the standard logger. +func Debug(args ...interface{}) { + baseLogger.sourced().Debug(args...) +} + +// Debugln logs a message at level Debug on the standard logger. +func Debugln(args ...interface{}) { + baseLogger.sourced().Debugln(args...) +} + +// Debugf logs a message at level Debug on the standard logger. +func Debugf(format string, args ...interface{}) { + baseLogger.sourced().Debugf(format, args...) +} + +// Info logs a message at level Info on the standard logger. +func Info(args ...interface{}) { + baseLogger.sourced().Info(args...) +} + +// Infoln logs a message at level Info on the standard logger. +func Infoln(args ...interface{}) { + baseLogger.sourced().Infoln(args...) +} + +// Infof logs a message at level Info on the standard logger. +func Infof(format string, args ...interface{}) { + baseLogger.sourced().Infof(format, args...) +} + +// Warn logs a message at level Warn on the standard logger. +func Warn(args ...interface{}) { + baseLogger.sourced().Warn(args...) +} + +// Warnln logs a message at level Warn on the standard logger. +func Warnln(args ...interface{}) { + baseLogger.sourced().Warnln(args...) +} + +// Warnf logs a message at level Warn on the standard logger. +func Warnf(format string, args ...interface{}) { + baseLogger.sourced().Warnf(format, args...) +} + +// Error logs a message at level Error on the standard logger. +func Error(args ...interface{}) { + baseLogger.sourced().Error(args...) +} + +// Errorln logs a message at level Error on the standard logger. +func Errorln(args ...interface{}) { + baseLogger.sourced().Errorln(args...) +} + +// Errorf logs a message at level Error on the standard logger. +func Errorf(format string, args ...interface{}) { + baseLogger.sourced().Errorf(format, args...) +} + +// Fatal logs a message at level Fatal on the standard logger. +func Fatal(args ...interface{}) { + baseLogger.sourced().Fatal(args...) +} + +// Fatalln logs a message at level Fatal on the standard logger. +func Fatalln(args ...interface{}) { + baseLogger.sourced().Fatalln(args...) +} + +// Fatalf logs a message at level Fatal on the standard logger. +func Fatalf(format string, args ...interface{}) { + baseLogger.sourced().Fatalf(format, args...) +} + +// AddHook adds hook to Prometheus' original logger. +func AddHook(hook logrus.Hook) { + origLogger.Hooks.Add(hook) +} + +type errorLogWriter struct{} + +func (errorLogWriter) Write(b []byte) (int, error) { + baseLogger.sourced().Error(string(b)) + return len(b), nil +} + +// NewErrorLogger returns a log.Logger that is meant to be used +// in the ErrorLog field of an http.Server to log HTTP server errors. +func NewErrorLogger() *log.Logger { + return log.New(&errorLogWriter{}, "", 0) +} diff --git a/vendor/github.com/prometheus/common/log/syslog_formatter.go b/vendor/github.com/prometheus/common/log/syslog_formatter.go new file mode 100644 index 00000000..f882f2f8 --- /dev/null +++ b/vendor/github.com/prometheus/common/log/syslog_formatter.go @@ -0,0 +1,126 @@ +// Copyright 2015 The Prometheus Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// +build !windows,!nacl,!plan9 + +package log + +import ( + "fmt" + "log/syslog" + "os" + + "github.com/sirupsen/logrus" +) + +var _ logrus.Formatter = (*syslogger)(nil) + +func init() { + setSyslogFormatter = func(l logger, appname, local string) error { + if appname == "" { + return fmt.Errorf("missing appname parameter") + } + if local == "" { + return fmt.Errorf("missing local parameter") + } + + fmter, err := newSyslogger(appname, local, l.entry.Logger.Formatter) + if err != nil { + fmt.Fprintf(os.Stderr, "error creating syslog formatter: %v\n", err) + l.entry.Errorf("can't connect logger to syslog: %v", err) + return err + } + l.entry.Logger.Formatter = fmter + return nil + } +} + +var prefixTag []byte + +type syslogger struct { + wrap logrus.Formatter + out *syslog.Writer +} + +func newSyslogger(appname string, facility string, fmter logrus.Formatter) (*syslogger, error) { + priority, err := getFacility(facility) + if err != nil { + return nil, err + } + out, err := syslog.New(priority, appname) + _, isJSON := fmter.(*logrus.JSONFormatter) + if isJSON { + // add cee tag to json formatted syslogs + prefixTag = []byte("@cee:") + } + return &syslogger{ + out: out, + wrap: fmter, + }, err +} + +func getFacility(facility string) (syslog.Priority, error) { + switch facility { + case "0": + return syslog.LOG_LOCAL0, nil + case "1": + return syslog.LOG_LOCAL1, nil + case "2": + return syslog.LOG_LOCAL2, nil + case "3": + return syslog.LOG_LOCAL3, nil + case "4": + return syslog.LOG_LOCAL4, nil + case "5": + return syslog.LOG_LOCAL5, nil + case "6": + return syslog.LOG_LOCAL6, nil + case "7": + return syslog.LOG_LOCAL7, nil + } + return syslog.LOG_LOCAL0, fmt.Errorf("invalid local(%s) for syslog", facility) +} + +func (s *syslogger) Format(e *logrus.Entry) ([]byte, error) { + data, err := s.wrap.Format(e) + if err != nil { + fmt.Fprintf(os.Stderr, "syslogger: can't format entry: %v\n", err) + return data, err + } + // only append tag to data sent to syslog (line), not to what + // is returned + line := string(append(prefixTag, data...)) + + switch e.Level { + case logrus.PanicLevel: + err = s.out.Crit(line) + case logrus.FatalLevel: + err = s.out.Crit(line) + case logrus.ErrorLevel: + err = s.out.Err(line) + case logrus.WarnLevel: + err = s.out.Warning(line) + case logrus.InfoLevel: + err = s.out.Info(line) + case logrus.DebugLevel: + err = s.out.Debug(line) + default: + err = s.out.Notice(line) + } + + if err != nil { + fmt.Fprintf(os.Stderr, "syslogger: can't send log to syslog: %v\n", err) + } + + return data, err +} diff --git a/vendor/github.com/prometheus/statsd_exporter/LICENSE b/vendor/github.com/prometheus/statsd_exporter/LICENSE new file mode 100644 index 00000000..261eeb9e --- /dev/null +++ b/vendor/github.com/prometheus/statsd_exporter/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/github.com/prometheus/statsd_exporter/NOTICE b/vendor/github.com/prometheus/statsd_exporter/NOTICE new file mode 100644 index 00000000..33179a98 --- /dev/null +++ b/vendor/github.com/prometheus/statsd_exporter/NOTICE @@ -0,0 +1,5 @@ +StatsD-to-Prometheus exporter +Copyright 2013-2015 The Prometheus Authors + +This product includes software developed at +SoundCloud Ltd. (http://soundcloud.com/). diff --git a/vendor/github.com/prometheus/statsd_exporter/pkg/mapper/action.go b/vendor/github.com/prometheus/statsd_exporter/pkg/mapper/action.go new file mode 100644 index 00000000..b8c09771 --- /dev/null +++ b/vendor/github.com/prometheus/statsd_exporter/pkg/mapper/action.go @@ -0,0 +1,42 @@ +// Copyright 2018 The Prometheus Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package mapper + +import "fmt" + +type ActionType string + +const ( + ActionTypeMap ActionType = "map" + ActionTypeDrop ActionType = "drop" + ActionTypeDefault ActionType = "" +) + +func (t *ActionType) UnmarshalYAML(unmarshal func(interface{}) error) error { + var v string + + if err := unmarshal(&v); err != nil { + return err + } + + switch ActionType(v) { + case ActionTypeDrop: + *t = ActionTypeDrop + case ActionTypeMap, ActionTypeDefault: + *t = ActionTypeMap + default: + return fmt.Errorf("invalid action type %q", v) + } + return nil +} diff --git a/vendor/github.com/prometheus/statsd_exporter/pkg/mapper/escape.go b/vendor/github.com/prometheus/statsd_exporter/pkg/mapper/escape.go new file mode 100644 index 00000000..fc8d194f --- /dev/null +++ b/vendor/github.com/prometheus/statsd_exporter/pkg/mapper/escape.go @@ -0,0 +1,74 @@ +// Copyright 2020 The Prometheus Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package mapper + +import ( + "strings" + "unicode/utf8" +) + +// EscapeMetricName replaces invalid characters in the metric name with "_" +// Valid characters are a-z, A-Z, 0-9, and _ +func EscapeMetricName(metricName string) string { + metricLen := len(metricName) + if metricLen == 0 { + return "" + } + + escaped := false + var sb strings.Builder + // If a metric starts with a digit, allocate the memory and prepend an + // underscore. + if metricName[0] >= '0' && metricName[0] <= '9' { + escaped = true + sb.Grow(metricLen + 1) + sb.WriteByte('_') + } + + // This is an character replacement method optimized for this limited + // use case. It is much faster than using a regex. + offset := 0 + for i, c := range metricName { + // Seek forward, skipping valid characters until we find one that needs + // to be replaced, then add all the characters we've seen so far to the + // string.Builder. + if (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || + (c >= '0' && c <= '9') || (c == '_') { + // Character is valid, so skip over it without doing anything. + } else { + if !escaped { + // Up until now we've been lazy and avoided actually allocating + // memory. Unfortunately we've now determined this string needs + // escaping, so allocate the buffer for the whole string. + escaped = true + sb.Grow(metricLen) + } + sb.WriteString(metricName[offset:i]) + offset = i + utf8.RuneLen(c) + sb.WriteByte('_') + } + } + + if !escaped { + // This is the happy path where nothing had to be escaped, so we can + // avoid doing anything. + return metricName + } + + if offset < metricLen { + sb.WriteString(metricName[offset:]) + } + + return sb.String() +} diff --git a/vendor/github.com/prometheus/statsd_exporter/pkg/mapper/fsm/README.md b/vendor/github.com/prometheus/statsd_exporter/pkg/mapper/fsm/README.md new file mode 100644 index 00000000..722ee210 --- /dev/null +++ b/vendor/github.com/prometheus/statsd_exporter/pkg/mapper/fsm/README.md @@ -0,0 +1,132 @@ +# FSM Mapping + +## Overview + +This package implements a fast and efficient algorithm for generic glob style +string matching using a finite state machine (FSM). + +### Source Hierachy + +``` + '-- fsm + '-- dump.go // functionality to dump the FSM to Dot file + '-- formatter.go // format glob templates using captured * groups + '-- fsm.go // manipulating and searching of FSM + '-- minmax.go // min() max() function for interger +``` + +## FSM Explained + +Per [Wikipedia](https://en.wikipedia.org/wiki/Finite-state_machine): + +> A finite-state machine (FSM) or finite-state automaton (FSA, plural: automata), +> finite automaton, or simply a state machine, is a mathematical model of +> computation. It is an abstract machine that can be in exactly one of a finite +> number of states at any given time. The FSM can change from one state to +> another in response to some external inputs; the change from one state to +> another is called a transition. An FSM is defined by a list of its states, its +> initial state, and the conditions for each transition. + +In our use case, each *state* is a substring after the input StatsD metric name is splitted by `.`. + +### Add state to FSM + +`func (f *FSM) AddState(match string, matchMetricType string, +maxPossibleTransitions int, result interface{}) int` + +At first, the FSM only contains three states, representing three possible metric types: + + ____ [gauge] + / + (start)---- [counter] + \ + '--- [ timer ] + + +Adding a rule `client.*.request.count` with type `counter` will make the FSM to be: + + + ____ [gauge] + / + (start)---- [counter] -- [client] -- [*] -- [request] -- [count] -- {R1} + \ + '--- [timer] + +`{R1}` is short for result 1, which is the match result for `client.*.request.count`. + +Adding a rule `client.*.*.size` with type `counter` will make the FSM to be: + + ____ [gauge] __ [request] -- [count] -- {R1} + / / + (start)---- [counter] -- [client] -- [*] + \ \__ [*] -- [size] -- {R2} + '--- [timer] + + +### Finding a result state in FSM + +`func (f *FSM) GetMapping(statsdMetric string, statsdMetricType string) +(*mappingState, []string)` + +For example, when mapping `client.aaa.request.count` with `counter` type in the +FSM, the `^1` to `^7` symbols indicate how FSM will traversal in its tree: + + + ____ [gauge] __ [request] -- [count] -- {R1} + / / ^5 ^6 ^7 + (start)---- [counter] -- [client] -- [*] + ^1 \ ^2 ^3 \__ [*] -- [size] -- {R2} + '--- [timer] ^4 + + +To map `client.bbb.request.size`, FSM will do a backtracking: + + + ____ [gauge] __ [request] -- [count] -- {R1} + / / ^5 ^6 + (start)---- [counter] -- [client] -- [*] + ^1 \ ^2 ^3 \__ [*] -- [size] -- {R2} + '--- [timer] ^4 + ^7 ^8 ^9 + + +## Debugging + +To see all the states of the current FSM, use `func (f *FSM) DumpFSM(w io.Writer)` +to dump into a Dot file. The Dot file can be further renderer into image using: + +```shell +$ dot -Tpng dump.dot > dump.png +``` + +In StatsD exporter, one could use the following: + +```shell +$ statsd_exporter --statsd.mapping-config=statsd.rules --debug.dump-fsm=dump.dot +$ dot -Tpng dump.dot > dump.png +``` + +For example, the following rules: + +```yaml +mappings: +- match: client.*.request.count + name: request_count + match_metric_type: counter + labels: + client: $1 + +- match: client.*.*.size + name: sizes + match_metric_type: counter + labels: + client: $1 + direction: $2 +``` + +will be rendered as: + +![FSM](fsm.png) + +The `dot` program is part of [Graphviz](https://www.graphviz.org/) and is +available in most of popular operating systems. diff --git a/vendor/github.com/prometheus/statsd_exporter/pkg/mapper/fsm/dump.go b/vendor/github.com/prometheus/statsd_exporter/pkg/mapper/fsm/dump.go new file mode 100644 index 00000000..d91e2cf3 --- /dev/null +++ b/vendor/github.com/prometheus/statsd_exporter/pkg/mapper/fsm/dump.go @@ -0,0 +1,48 @@ +// Copyright 2018 The Prometheus Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package fsm + +import ( + "fmt" + "io" +) + +// DumpFSM accepts a io.writer and write the current FSM into dot file format. +func (f *FSM) DumpFSM(w io.Writer) { + idx := 0 + states := make(map[int]*mappingState) + states[idx] = f.root + + w.Write([]byte("digraph g {\n")) + w.Write([]byte("rankdir=LR\n")) // make it vertical + w.Write([]byte("node [ label=\"\",style=filled,fillcolor=white,shape=circle ]\n")) // remove label of node + + for idx < len(states) { + for field, transition := range states[idx].transitions { + states[len(states)] = transition + w.Write([]byte(fmt.Sprintf("%d -> %d [label = \"%s\"];\n", idx, len(states)-1, field))) + if idx == 0 { + // color for metric types + w.Write([]byte(fmt.Sprintf("%d [color=\"#D6B656\",fillcolor=\"#FFF2CC\"];\n", len(states)-1))) + } else if transition.transitions == nil || len(transition.transitions) == 0 { + // color for end state + w.Write([]byte(fmt.Sprintf("%d [color=\"#82B366\",fillcolor=\"#D5E8D4\"];\n", len(states)-1))) + } + } + idx++ + } + // color for start state + w.Write([]byte(fmt.Sprintf("0 [color=\"#a94442\",fillcolor=\"#f2dede\"];\n"))) + w.Write([]byte("}")) +} diff --git a/vendor/github.com/prometheus/statsd_exporter/pkg/mapper/fsm/formatter.go b/vendor/github.com/prometheus/statsd_exporter/pkg/mapper/fsm/formatter.go new file mode 100644 index 00000000..567bbc28 --- /dev/null +++ b/vendor/github.com/prometheus/statsd_exporter/pkg/mapper/fsm/formatter.go @@ -0,0 +1,76 @@ +// Copyright 2018 The Prometheus Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package fsm + +import ( + "fmt" + "regexp" + "strconv" + "strings" +) + +var ( + templateReplaceCaptureRE = regexp.MustCompile(`\$\{?([a-zA-Z0-9_\$]+)\}?`) +) + +type TemplateFormatter struct { + captureIndexes []int + captureCount int + fmtString string +} + +// NewTemplateFormatter instantiates a TemplateFormatter +// from given template string and the maximum amount of captures. +func NewTemplateFormatter(template string, captureCount int) *TemplateFormatter { + matches := templateReplaceCaptureRE.FindAllStringSubmatch(template, -1) + if len(matches) == 0 { + // if no regex reference found, keep it as it is + return &TemplateFormatter{captureCount: 0, fmtString: template} + } + + var indexes []int + valueFormatter := template + for _, match := range matches { + idx, err := strconv.Atoi(match[len(match)-1]) + if err != nil || idx > captureCount || idx < 1 { + // if index larger than captured count or using unsupported named capture group, + // replace with empty string + valueFormatter = strings.Replace(valueFormatter, match[0], "", -1) + } else { + valueFormatter = strings.Replace(valueFormatter, match[0], "%s", -1) + // note: the regex reference variable $? starts from 1 + indexes = append(indexes, idx-1) + } + } + return &TemplateFormatter{ + captureIndexes: indexes, + captureCount: len(indexes), + fmtString: valueFormatter, + } +} + +// Format accepts a list containing captured strings and returns the formatted +// string using the template stored in current TemplateFormatter. +func (formatter *TemplateFormatter) Format(captures []string) string { + if formatter.captureCount == 0 { + // no label substitution, keep as it is + return formatter.fmtString + } + indexes := formatter.captureIndexes + vargs := make([]interface{}, formatter.captureCount) + for i, idx := range indexes { + vargs[i] = captures[idx] + } + return fmt.Sprintf(formatter.fmtString, vargs...) +} diff --git a/vendor/github.com/prometheus/statsd_exporter/pkg/mapper/fsm/fsm.go b/vendor/github.com/prometheus/statsd_exporter/pkg/mapper/fsm/fsm.go new file mode 100644 index 00000000..cf8c00ec --- /dev/null +++ b/vendor/github.com/prometheus/statsd_exporter/pkg/mapper/fsm/fsm.go @@ -0,0 +1,326 @@ +// Copyright 2018 The Prometheus Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package fsm + +import ( + "regexp" + "strings" + + "github.com/prometheus/common/log" +) + +type mappingState struct { + transitions map[string]*mappingState + minRemainingLength int + maxRemainingLength int + // result* members are nil unless there's a metric ends with this state + Result interface{} + ResultPriority int +} + +type fsmBacktrackStackCursor struct { + fieldIndex int + captureIndex int + currentCapture string + state *mappingState + prev *fsmBacktrackStackCursor + next *fsmBacktrackStackCursor +} + +type FSM struct { + root *mappingState + metricTypes []string + statesCount int + BacktrackingNeeded bool + OrderingDisabled bool +} + +// NewFSM creates a new FSM instance +func NewFSM(metricTypes []string, maxPossibleTransitions int, orderingDisabled bool) *FSM { + fsm := FSM{} + root := &mappingState{} + root.transitions = make(map[string]*mappingState, len(metricTypes)) + + for _, field := range metricTypes { + state := &mappingState{} + (*state).transitions = make(map[string]*mappingState, maxPossibleTransitions) + root.transitions[string(field)] = state + } + fsm.OrderingDisabled = orderingDisabled + fsm.metricTypes = metricTypes + fsm.statesCount = 0 + fsm.root = root + return &fsm +} + +// AddState adds a mapping rule into the existing FSM. +// The maxPossibleTransitions parameter sets the expected count of transitions left. +// The result parameter sets the generic type to be returned when fsm found a match in GetMapping. +func (f *FSM) AddState(match string, matchMetricType string, maxPossibleTransitions int, result interface{}) int { + // first split by "." + matchFields := strings.Split(match, ".") + // fill into our FSM + roots := []*mappingState{} + // first state is the metric type + if matchMetricType == "" { + // if metricType not specified, connect the start state from all three types + for _, metricType := range f.metricTypes { + roots = append(roots, f.root.transitions[string(metricType)]) + } + } else { + roots = append(roots, f.root.transitions[matchMetricType]) + } + var captureCount int + var finalStates []*mappingState + // iterating over different start state (different metric types) + for _, root := range roots { + captureCount = 0 + // for each start state, connect from start state to end state + for i, field := range matchFields { + state, prs := root.transitions[field] + if !prs { + // create a state if it's not exist in the fsm + state = &mappingState{} + (*state).transitions = make(map[string]*mappingState, maxPossibleTransitions) + (*state).maxRemainingLength = len(matchFields) - i - 1 + (*state).minRemainingLength = len(matchFields) - i - 1 + root.transitions[field] = state + // if this is last field, set result to currentMapping instance + if i == len(matchFields)-1 { + root.transitions[field].Result = result + } + } else { + (*state).maxRemainingLength = max(len(matchFields)-i-1, (*state).maxRemainingLength) + (*state).minRemainingLength = min(len(matchFields)-i-1, (*state).minRemainingLength) + } + if field == "*" { + captureCount++ + } + + // goto next state + root = state + } + finalStates = append(finalStates, root) + } + + for _, state := range finalStates { + state.ResultPriority = f.statesCount + } + + f.statesCount++ + + return captureCount +} + +// GetMapping using the fsm to find matching rules according to given statsdMetric and statsdMetricType. +// If it finds a match, the final state and the captured strings are returned; +// if there's no match found, nil and a empty list will be returned. +func (f *FSM) GetMapping(statsdMetric string, statsdMetricType string) (*mappingState, []string) { + matchFields := strings.Split(statsdMetric, ".") + currentState := f.root.transitions[statsdMetricType] + + // the cursor/pointer in the backtrack stack implemented as a double-linked list + var backtrackCursor *fsmBacktrackStackCursor + resumeFromBacktrack := false + + // the return variable + var finalState *mappingState + + captures := make([]string, len(matchFields)) + finalCaptures := make([]string, len(matchFields)) + // keep track of captured group so we don't need to do append() on captures + captureIdx := 0 + filedsCount := len(matchFields) + i := 0 + var state *mappingState + for { // the loop for backtracking + for { // the loop for a single "depth only" search + var present bool + // if we resume from backtrack, we should skip this branch in this case + // since the state that were saved at the end of this branch + if !resumeFromBacktrack { + if len(currentState.transitions) > 0 { + field := matchFields[i] + state, present = currentState.transitions[field] + fieldsLeft := filedsCount - i - 1 + // also compare length upfront to avoid unnecessary loop or backtrack + if !present || fieldsLeft > state.maxRemainingLength || fieldsLeft < state.minRemainingLength { + state, present = currentState.transitions["*"] + if !present || fieldsLeft > state.maxRemainingLength || fieldsLeft < state.minRemainingLength { + break + } else { + captures[captureIdx] = field + captureIdx++ + } + } else if f.BacktrackingNeeded { + // if backtracking is needed, also check for alternative transition, i.e. * + altState, present := currentState.transitions["*"] + if !present || fieldsLeft > altState.maxRemainingLength || fieldsLeft < altState.minRemainingLength { + } else { + // push to backtracking stack + newCursor := fsmBacktrackStackCursor{prev: backtrackCursor, state: altState, + fieldIndex: i, + captureIndex: captureIdx, currentCapture: field, + } + // if this is not the first time, connect to the previous cursor + if backtrackCursor != nil { + backtrackCursor.next = &newCursor + } + backtrackCursor = &newCursor + } + } + } else { + // no more transitions for this state + break + } + } // backtrack will resume from here + + // do we reach a final state? + if state.Result != nil && i == filedsCount-1 { + if f.OrderingDisabled { + finalState = state + return finalState, captures + } else if finalState == nil || finalState.ResultPriority > state.ResultPriority { + // if we care about ordering, try to find a result with highest prioity + finalState = state + // do a deep copy to preserve current captures + copy(finalCaptures, captures) + } + break + } + + i++ + if i >= filedsCount { + break + } + + resumeFromBacktrack = false + currentState = state + } + if backtrackCursor == nil { + // if we are not doing backtracking or all path has been travesaled + break + } else { + // pop one from stack + state = backtrackCursor.state + currentState = state + i = backtrackCursor.fieldIndex + captureIdx = backtrackCursor.captureIndex + 1 + // put the * capture back + captures[captureIdx-1] = backtrackCursor.currentCapture + backtrackCursor = backtrackCursor.prev + if backtrackCursor != nil { + // deref for GC + backtrackCursor.next = nil + } + resumeFromBacktrack = true + } + } + return finalState, finalCaptures +} + +// TestIfNeedBacktracking tests if backtrack is needed for given list of mappings +// and whether ordering is disabled. +func TestIfNeedBacktracking(mappings []string, orderingDisabled bool) bool { + backtrackingNeeded := false + // A has * in rules, but there's other transisitions at the same state, + // this makes A the cause of backtracking + ruleByLength := make(map[int][]string) + ruleREByLength := make(map[int][]*regexp.Regexp) + + // first sort rules by length + for _, mapping := range mappings { + l := len(strings.Split(mapping, ".")) + ruleByLength[l] = append(ruleByLength[l], mapping) + + metricRe := strings.Replace(mapping, ".", "\\.", -1) + metricRe = strings.Replace(metricRe, "*", "([^.]*)", -1) + regex, err := regexp.Compile("^" + metricRe + "$") + if err != nil { + log.Warnf("invalid match %s. cannot compile regex in mapping: %v", mapping, err) + } + // put into array no matter there's error or not, we will skip later if regex is nil + ruleREByLength[l] = append(ruleREByLength[l], regex) + } + + for l, rules := range ruleByLength { + if len(rules) == 1 { + continue + } + rulesRE := ruleREByLength[l] + for i1, r1 := range rules { + currentRuleNeedBacktrack := false + re1 := rulesRE[i1] + if re1 == nil || !strings.Contains(r1, "*") { + continue + } + // if rule r1 is A.B.C.*.E.*, is there a rule r2 is A.B.C.D.x.x or A.B.C.*.E.F ? (x is any string or *) + // if such r2 exists, then to match r1 we will need backtracking + for index := 0; index < len(r1); index++ { + if r1[index] != '*' { + continue + } + // translate the substring of r1 from 0 to the index of current * into regex + // A.B.C.*.E.* will becomes ^A\.B\.C\. and ^A\.B\.C\.\*\.E\. + reStr := strings.Replace(r1[:index], ".", "\\.", -1) + reStr = strings.Replace(reStr, "*", "\\*", -1) + re := regexp.MustCompile("^" + reStr) + for i2, r2 := range rules { + if i2 == i1 { + continue + } + if len(re.FindStringSubmatchIndex(r2)) > 0 { + currentRuleNeedBacktrack = true + break + } + } + } + + for i2, r2 := range rules { + if i2 != i1 && len(re1.FindStringSubmatchIndex(r2)) > 0 { + // log if we care about ordering and the superset occurs before + if !orderingDisabled && i1 < i2 { + log.Warnf("match \"%s\" is a super set of match \"%s\" but in a lower order, "+ + "the first will never be matched", r1, r2) + } + currentRuleNeedBacktrack = false + } + } + for i2, re2 := range rulesRE { + if i2 == i1 || re2 == nil { + continue + } + // if r1 is a subset of other rule, we don't need backtrack + // because either we turned on ordering + // or we disabled ordering and can't match it even with backtrack + if len(re2.FindStringSubmatchIndex(r1)) > 0 { + currentRuleNeedBacktrack = false + } + } + + if currentRuleNeedBacktrack { + log.Warnf("backtracking required because of match \"%s\", "+ + "matching performance may be degraded", r1) + backtrackingNeeded = true + } + } + } + + // backtracking will always be needed if ordering of rules is not disabled + // since transistions are stored in (unordered) map + // note: don't move this branch to the beginning of this function + // since we need logs for superset rules + + return !orderingDisabled || backtrackingNeeded +} diff --git a/vendor/github.com/prometheus/statsd_exporter/pkg/mapper/fsm/fsm.png b/vendor/github.com/prometheus/statsd_exporter/pkg/mapper/fsm/fsm.png new file mode 100644 index 0000000000000000000000000000000000000000..ef87d92a472eda969178cd71c6cff3fc4ac3a19f GIT binary patch literal 33685 zcmZsDbyQVr_q8Ahihzie(nxp1p&RK2=?>{QbV^F2G>4Y%mhKRc?uJ8mcYK?B->d${ z`2JA`Zufp_tu@!2^YM$Ej2QBZw=bSNd4l{wTv*}B6WBH2&wGf^fM2oCk?MfoVC)sd z1fLWS;BGy6!vExhuz->?%x*G*`tUU+2HsN=s9C1K;}j#rc8&!Ff2Z zn^0%)-4}f<0h%EALT_*J16@Kte!|9!h8rzhO~GEWX{K)wD7}@V)A|C}!^d)y0munn z1eMV%bXY-tL_`=OLO4I~SFd0NTeRR*-0IAY6IB2HDe#DoFI)#@ob2bvhyMHv8)g{W z-g$ReL)YJL_~$Q|Xc7Iq!OxY_-_zq{!RJx@lob&%qobk@mQ((SB1`Y(?26DrPWYlp zz;YSuHL7(p;tqa(q2}*Tnoa;8v!2MQxx&*lazQ&jrxjYbU!&-s3VLxi*WQlKd%t{m zj;t{7gPxnGQBFlWAUIedI>s=wp_1SC5GhnZ*TLGa6Geny_+lMFEL{BedrN6xP~~;X zc5BV$kxW>)p$9HK(prV#^)Cd_z97Ch@NOyPxP-5SjW*CuQjU#FCs%L7tMo-YSQLurfJBMa%zGDgYj*GhuLQ{Rs`bdd@C4#AOGhRFQMDb-r+ft?Qr3_GX`{NJ>2g}lS9 zX1Wp(tw)hUrILpAL1CgsE<`965B;Hi-&p^w)Gt48jHY0&i3DK2)48V;aPL|v*LK9LJ>26O_h+Tx)~m&{b|t*s>&xCzVr;1N!Xnj z(igusP=$n0rgnTljrpT>xSfqqW_`WZ{g5_9Z_m|np;qQA&X9zg2vf=*Bi zeQK;3UzR0h?O+J<28jRmcf~{y))?OTrqeoumd2Uu8Xui(T<*o15O^va)iu*9@3x}H8!6tv=1t20K!e?W_a2|UdX>|F&wpHP z@El0*+c4BLiLeUt+HkU|zTHb|OiYQt2RHN;nC`;Y!KQcN^Xv?wgX-$hTLIHayhdj7 znC`y%NxYGvf7Tok<{dDV*_KG1WZ9NJc45jAxb%(;8J7=w*>=6NAcE-rKhIWDJnm2K zsAae>a?qw<#@riwnrxVYTpD_T53QbXeqgH>?ee_e;~<3FCI^D^F4PbS0Ua@v`z<_Y zJsx-dNg(wY)@xBG+>4z$yWBb7B&t9(Q)eqrdRm(G(7{#t43D- zUZ%wHpGnBzerHb@vDd&Mj$$#QsYkr@ckuSIaa+Zm(w#gvp32QH$937#K%4OVffGw& z{->rU;=mjjJ967`kP%0wrbbj&R=)Le+*9)(o1bO;H0H{E*7NJ|l;z?~Z@?!kw~#{2 zbk~-Qm_`!qceYAIdMq7%2@a#S?l*fim(U2lN@%0gf%fW_hUWTM+~NQ4lOLC z;l4d=KRiAb6cG_ge&5^IcXor1kN@Mim-?tfD~Hf+(p|WQ;0b-IH|PSqku-fXkygm@ zt_YN}HDJ^;_xq&rGm|Z)6UXkl(yjBu_Qle+pA7OvBy%~fC0uiA`Gz~;B-FzNnYl9_NlRr zQZZY1b141VNi?H&v84GvKl2yFWGGD_C?rJ4-JLrlBjeOj$NdQYvR&!x{oM_8fa^e5 zO)Xw1JU*OlW46XRRSP;y)O^(S?x+ok{z<;kUAT_5fSekQetl?_xtFT7lDvu^9`&N2 z^DRZ?zGGQIPTbY+o%e|q(5= zhcK3=4NQv5I!-@?6aAp+hHk;@!J`p*@q0Uu%@V&mFc-CE&&GxO;`;p&85W({2A2xY zum`T4!ivLjRUNayn?&>CR^aW$8;y78vt?HEpvL*Ti^UT9SC_naw`QZ+%kc)`0`l_1 z>9&c-NQw%ZtIP^k5%V4w^Ug<|SOOb!?R}`0uA1Sym1jMJQ&xlfbc@CZ=Lbt$ctM*3 z=vurrQWU?Bb0sbiTU?i#%=y2PP|?Yx_??|OHswaU!to05Y(VXN54RQJhBud&X<`xh zn=@6^v2yH{`C_|45}?%`U`RwH+Ib|R}k-$-YCZH-N`RG?A>yAd1~Hjd`J z?!ug5r6wQ%0|IaRK_Ix$EHa+#?Ck9xCvov0x?Pszt^!n$$F;+p`^m3yorysMSM4t= zyL;jb9Pgfs>7)^lo^f5WqO7)H;nd3BxS?g#Cr5Y(?W5p+40!!Je*Ida;NB~EzW+wn z$nVBaEuC}$YHXpRT7?Pm@wo?2G##gn zu2BTejBT$#QL2*ly8@Y-Y$Z_1N-Sfs&WRyCSq4TwG z5;T67BvT@wQaBxcX}VZ&0~;nrd?S+{8KAH)&SMiaSiPRJAME@Lk5pB`^Xh&ubHtK8;fm6XqXEBDgY=z0k~S`PP`4}gyP`f#50?OtKjG@ zlVd1srj1;J@Q%O9^5R5AMP0Ls)>(?!yv&(l>+BFm$}21!yE*jodbndB;JvYEI2quI z2Z8ftLKu&GubPf-fCzJ%x>Ryzw41Nh^5{Z(p5G}q?~nCF6Kh-(o7RMvljNL5bTLmr z#$cKJqV`h%21C-_cZOJRGxJkXinuxtl1Q!Fsao-s4#tSnT{uq?HW7h;wMTZY8EOji zWIuhyU#KU+TbvklxvOK_i7}(sxK};f!h!)RbpI|io3Bjm4va&WN-~Mf^04cj^=20r zP1pBfy@R>hih--}FJE9z2Cwk5P#DUFQLI%!vfH3$I$4~&P+a|R1nY~!r)+?YoRje~^S)oJGAj=?191C}?}(lvRRH&dpe{L2LTI3xfA6 z8zP@^AkW0Jo#X&X`ow9zKV_VA7mkVP+8a!R1)Fx{y3LbV4Xa^FA|1~wQjo)D zTC#0^Omu9l{^{m0h;%*0ZGSkNHFH#}&OxBr^Uk5N;o2}X#q0iR=5mL5{cGVwCKQ!8} zTQGkeu*8`*F6PLbR&v&hhIHNKK5KuI7>>tP3_#0wK#bMb?lVgHbkP2qh)A_pXO|4DU5)+~Fsx zycRhz@%ATu&ic4-55Ot9y}-9;VYh;va{ zME`8I{jaf@No}{iQoT*@RJ&eEkb>3DDM7V-75Nyq*Y#G;8Sn0Z%T7_vN`67{2qoh< z8+dkfhM#q$xdqH3$Vtm+%{75$cTEXybeYh zS$g-%KgiFv7l$BgTxZTvm#OTpAod;~kQ}r0aYin(%@1RENX#P+<)zoCmg>XsJRKH8 zNpEYD1g%TS>#;y2C};{b%k=$Y#SKY_+;HtMA?5Al^w{w)+6OY`CKvW0D4f__2zqFy zyTjjxvTxRUW@}!$+{8g!iaT{*1P9vnc-)aJt-XVjr6<6+J69+VHj>!NR?4ddsE5cbY#ZdqDnw|K9aTEc6T+E?EQ1lVKVJ_Wk)$6l_l{k7r0NzZ;m9P;6#p$jyOz0FijX;f1x@M`s&tb-v$BsDZ&k+pM3wZx3zq)Em9 zYl9&}u=dx~;NTI>EU>m;ey`mc^`PAPzj&=16Df8i z0J;fHWUiTu6m*#2mQr2TRQiF2{03-~N z;D^}dtgyB&^;>&wWyMt{zDRaj4`w#uCL(HuEBHqGCo=)xq=5aZxS+}Rtl!9MJ;8** zeqD;Tmcyv`P4bLjkptszVEq^{_GpoUvi8XlWF_lB+>czXRG18ky1O^ftu_Fla!1zJ6B%iW4W1^{dor3mH;_u>|-S4psAGG$h#uMzfr{wtoB zV1an9e^Q>AquWZB^bEW=gRzn7)ignsBg&b%mMIqDcmbMpiW zGju)4T2x9(${^sI$N$+7DN$fUz9qk!T`QHdpQ$u+K4`png@)$icF>3gRANC>)7(Ad zqJGEhzkCT-hK z^3=j{rK#Hu0E~?s2LMq|+d;%W{N-!bU;EzD`UnUZh5}Gjm6iQ#?Kkufms&ZtzJKHY z*nZIbfFBy0>b75dcp9xZCYN2__}9k@MftgT*^|r4%b6Y#F^}tM3+D#J*Kuz~Z@S!w zA!E%-Jywbu?ym({dIlV^ef>>;e}CtbU+hP~Zt5>KdHgtRLjvzRU+m9oHM~yvn^+KR zp?MtLa6(USZ(ARIb72VERy>g3#?=kD>F5STs3Xn(dOaar|KoutPsI^!2L{bK@AJ*_ zz`Ef`bk1Q^Iw&~U?zo?+;jan!c`H9QN#B`8&fa_qZ3bY0x`F%lSRU~5_wPGCF!z@J zj>N~atU?O(-oAr#*JnFbU6tNr3 z)#J53M$cR4)BX8(<`adj^#@Hzj;n?UGPXT%K$q6>NbfP0yY1nsefSem>9r1T_cVk-?J~e`=$#8>aX@hQP9(Oqw?LR zfx8B|Po$ikoyW@K8fg9=8zEfy`=vRE-FOj`$%f1-8952P5xYcrg}k6GXh+T z*d1K8RoE+1B<$EJG#E0~_W+LS*qyF80@~D_H!-6!T*#8KG_8?DLgDYJOMZCsV@Mo- zl~<1p{(7r)R@*CLB!ZSt)wjI0hAM0A*_E;MZVc0t=;-h~#HKaT@Wo&GgazW=o_u_~ zqW}H&8tI%yJM_+6C9^Q?34Xq^yRHMvtzi2S#!|@$s6qY*AM#xFCO=5R&i=I5ceYw2 z>Hg`fl8nLO#v7JfQ%EHO$ufcaAxsL;q9l{^0$pY@^FmI9%f#k)A@=#wm^G3Am559R z0}`q+dsI}}%gey_S$1(wXw%{8so+N!94aaad6f)0I7y^a+e`Sa+(Es6*=au(bEf&9 zG4TH-0Mk}D?)+%ZmCKWh-PUzk-mYR1Bza|dpWKwjAqjAv#dsC&x21cG6=bFu0DRC zpcD5k2~HDxA1Wk#OHox|U3Am??ypm91LHF;?bsWloYPxn-Q<(Zcki7xVHs&NLeYVO z|1!0uMYM#^%bH5T^8v43xnq8G5{rkv2`;%iHH&0F48IbF3$p<6KFPZ2OHQii+nbAn z$*%!UiyrK+U%!6K5NxrH2)jo5ajMxR)~+kAg}oGmcov1afm$)8%aCuF=7TKJ4+23%O>*J z9Cu?XI)5zg`$6+4ikJ8e$IB+DjIhJjNc7WU`$a*FGOxX=Nr%x?Ac1Sz|6=_C1oUvR zRz2wA8+)=4q4^{%?$j|_gFEaHNDo^uqb~mo?!}e}MYI4!=5^)QJGvxu=#hmA0GQOz|H7AA*0I?n`?g0Saw)B?Y=$ zjaT4GgNNxT^pV>Eou=Fp{}s7duS|a^YDcGp>+IK|XXSHL4319{mKTH^7btJvoL?>K z-l*ziK=TZSy?UjkWlVnl`qe2(-}t@WiplFX;GI=VQO!cIsd6;cIzGV7|6h?{OxcMt zKeEZ3am0yUL^yDGhSSMVX@Q5uNagqDv=GC+U60<#$zI8swN|FWYQlCN{dI`y)Yze! zxE))&{%cZs;hGWK?%`>eU4KI2@k$8&Cld%$pklUE>o4}j(4HRg5J=5>trVs`8NC~} z_Ciz80joe(5nSyp{|QqyA-Inn@e5AqWA}807e@y@_KkY}r>+;gDqW;B5!h&>WTzsH ze)6*JDwYhxjx9ze_LENYN*2~DJ`0X#dEEAHv>IFAn}Mq040I7^bnNpE-yFNz+uQf> z1gq^|`y8)G?P`KBc$;8?c5G$7z2Ho$>u7@*?k+IA)7uLm$)2o|#*;b6g8dyg{2p*B zvoX0EA5{W$4jRR}Un1mHMrBhJjMWgkEtr&q@FI&@H#XVxcwI(5kQ4E2ntRv@IfljgyY?cu3>HW`a9OceQ+ zNIWbbH1NNAdTx1{`sw&vc(@0>DoSlLWh7k9b|@R3tnf^11RD0RbRu%XN?{N2e|48d z2lmX>-z|=Iv@=Fb#}NlMvtRiM4xhVUtllm3C%MCh9F%1k$7{(4p|bdrx}+h)2-#_W z?n|CxPFoDhRhvK3(ULFf+dhS4sYVB}A?YNxFGBaIF%6Y#Ju+9%ExA%(ST{!s74{!W z`i%&qe%b!aQ=p@OTqe<9`u?Nu?^-@2^?YnAT9rcNYqL_Y_-8O`CCE97jroK|% zY2q34qJZVVCD7*wM!uQ^xz{`HwfSRd=c+fV;&z2y?G!gKa9G%V<4Mm~dk z88xnqm&ug>s2%ilmX8^FY$*NbG%yp%r~AJIA&(kfZ6QrqF@JyO@tj6`nC7v&cI z1{~;!V~L8|@(Q16EBJ&0zlpXEcEU#?^Q4>$1!i=|=`3aBPB?E@3~J(Ilr$~q#yIhr zjl7=&;3_tY=_|U%Qw6yE5Ck^T>%(?bd2Q|D`><)|y4n5P&2=Y7aneC-AyW4-xtZ0l6F-@)#*7yq4j;GMwKu-JL3166wjyNeKp? z*2vk{^t@VgEzArDy%^KW8Ev*+E5WphK zHe`WL6_t^v#H^>dyy*7^27sycoWkLbwyNfAi za1hZ=SQu`K=r?c1enYwy$vE_+Q9Hvfk>UcvrK z;NO$Eazc8=rR!}rrn1fTyzD80-bqd>;YZHud8ueC1Bw#tor{wbX3z16e((m{-b@wO zSVOL2j?ByllT)ByrUm%M`}=!ci5Il=^r-e51Mv1^09{w4V{i|;$jXCwk*&g z;Bl>7sPVWtzpll(kE^&L_Y-w;s=rNdqydyYU9S<@p80w;@GbZF!n`5Z@9MyhI3<$S z1v%!9S#Np9vlou1KS^jj)uSkqXn^oicm#DZNslvdLT;Xt?^KkhS;=XkmNRq9C_gu* z8oZ-&=iK}?&+rt&2XMG;0118O=CFkPQzXAB{-?yTV>$;PJRm^+sw@OeuKX4i<3Kgv z*#FcAq9JTpR?ymw3Qb^1Z%Fv=OcA3&on`98-8GTw2Leo^cyj5?G8q?-R|%Oi2CkA| zY8o10b#?W#IO!AahnBa_H_slGaC4_V-~>55tllD`UY{qwQS=$yPD zbHmAEITs8^RKoydk~#1njS$?>!YtxQ7DbCbE@I2+cp#6yf8i zO|{`ZIrK8uScp3348(FN&=U~&b#;>{Vkp+lhPv-xoowukececLlcE103aW zXql-pKikLBa)_cEYq(qqIXgjq$L38%^$t)hbL9~*taIb-&ZkX$@%WJ~PtJBG%}wIN zx$tZys7Ca>Hq!%b0NpGTIKs}C%YjFaI*W$`%0193>aKKr34}QCr2F1?<2ns=8d!|y zW6&uk>v#bV3W0XVbM&^w{71+xkb(;DV@XqP;6*bk3CCZX$B6uqoF|Bu(laUp!I?i7 znvfS|>+sw0U0w=E%nW|2Hn?Y>s@O#hbvy8=p-;|xuOzvZmiQJ&ByK=W$sycYK@5`je!!u} zCA6RRV78{D5BIq4#|TjlW8)gYx3|lUPkm7cwXo=*;Nx;0X=u1R%N)PoSi|$~Ef*}H z)tLCxqQ^tLozBi;3Mvs3Yf@R0$*f7?fAIf=_jKN`9Y>W;;fnN*dZ#lP8Q>00;c;WI zoMGXTG|RPbq;i~XxnW^qYGqW4Wc2?OIbef zTpH5|8r-Ko%9{BhvAksl^#QA_XtM?Y>P2BI`mAC^WD4Dw&PvmsxEUGcDd?50YrXH| zkM;j=M@@a;!X0{*W!S=8rkeyN?0fs7Uw=_PSx#!-7_{jCh;(uY-#8MF3j~7-zGJqp z+b&S^UrjtLiKfQ+W9jdDqN@%o#o&Efleost8dcIcx_)hb>q}Sg33BK#eGI0Nx`IbmC55C`p}Hbn<#lc3#&2NZ?eZ8sZq(hq ze8jL$OF@y2IWDO48NB8FYvHH&ay>^)`kC=(<#%4^mJKnN&&u~K=rtDmV^DcUtdUYsPDOY#*!I%ej~J%^H)0yW zG|`*}>1NL!rSi}D5A`A_&?TmdYD;B1@S156OMNFql8}r{)al?QGP32Nuuqg&CZUOw z?}hyZivP7>#tYyFY|?fYGlrhOOGwMiW2Q5BhAf)5{W_r*9Fi4o^^mWOC@s#Uq$(_mQTW&+T}fL%q== zs-zN4!g9_bS*VIh`MZ3mg|wh;Q+FMscSC~D4jMzg`}1Y&BtXOBeqH6_4lCPlEU}}~ zhaR0VZ@kqi&G$#bTA9@G>J&odS^Yen$R^@^^DVMz&)#tV4oTitD3L9G^rHA54?qFZ zbNa=AjUg@%7bc&7Pbsb_AuB7!BM%siL0xLphiS3cgf~4s-P6}MI_vM}N65{Mn+yUA z)>zFmm<|y>%D8pY^LkHzelwO#E6;~@EB{(5nU#&-GaKS?_D$!j?`sSini2~Ch`5qZ z8Cwy$4-CSA9mJp?w4loEV8`X>hljbW+4JwUt3Yeoo}Qa*dc9xy^J2C=n1F{`n}PVOMG>)`T!&!33Dy3U4JWyTb|?egc!Y>n0d=w zxZ~76Zm?CR+*s57yor^9dO)-w+$7P~rJU^FBO?a~Kxto8-peNdS;-5S4G?s6bWRo9 zFy`x>x=Z!i`NtXB>C@~}5)!^x&Q_as1MIAhn3)-+>)Ez{2z`^v5ktwMGc-!Gu z8NcjfD)s~S%kGBi@5;nOGiFi)+X-EA9Rk%!Rj9@g99orC30dgyJxXdP^uB@Y2(t@s z9UnZXEREj0rfWbP&5`X!qHlO&ZEc;ywb2(x->qTY=nsJC9j~5fcwE*`W9jYGbaY)n zK?~)(zkCW9I5y|&+3!zMAJU3GSyc*ai9a&#bz1`+U(js_I5(C6(zj!Kyx{DcNH7ZoE*3aJsE^%#c8(zROlI_t-FbavdDAC zTUVsdZTe`lMuinQ=3|!_Mj}&X$FzRk5rR8bSnQu(PJj3})kDO*qYlYaoCUd7mEgE8 z=ImP!pwMtG6pK$L5l39EM=PDz_2+<+D_~_+jN&j4kYOAWhOv7BrrVa-7&?t^&oQla zym#9iFs`gZeyGe3fmrYbJkJr5Pn5msXvuNvnQI71yy%I2)uGO7&8JpQp(Gg_1v{b5 zZg~a$_v|vQ3mAP-=aUkOu#1PSH-~b_7Oq;J@We`^nSlhJ*j!Hg9mwo+-jDp&b1FNT zjqx8wYq`G8fHYf0;sIzVD?oGB4JiCJfZt=sYYgJ6y5tbve0Q#Kd(r&R3259Q0P!lv z_3JiJHqdRU#b=4n6Vz5&zh8%>w;c|rlbI=-#Ap!USh_Kg9HJ=03EzBsjP&Bgi$lQC z(h?MmfPk=L`Gd#0)mHlG=tx{bVuW@dss-NaxwpsOw;BO??` zHk%mKfy6FVyiAnS8pAIA2^|*+)-czmRNn3|*@}lM7dN2w; z%{V$syf~bpN7Yjm9mPP}ap53Qpfx|tY=R}p?6OrI&u2*75sf$amvHcS z>Z;(4Tc8UZ8Mrct;K?rWM75mi(Qyvh1$6O_7**{rfQ?3*{Z}m@%q(whePK19*p%PW z9i7fTaj)DC?WJ~Po7Iz<#Zc8uxBhmvISehu066e8Yt}F@XcXE_(7)AuvtM5YmC9)X z?@WNaAWtjJMn%PzuiW(YiA6)P({<_rY5w$JQp+j12~qdG4;y$}>8@wiy*-dX30)8F z&N>FRgHO;TWvh-QERxL^ZNc-sLXB659XO;%DmJE0L5s#hsJ`g|hMutF-x}P43#TU3 ziOA-w1?Uma*es^fo=Qu7G|RVr>6pj1kIO9ARqF}h(rgskoBBxpgt~VRo!d1>%jH;T z-yvJWjr1Z9C?sfe-aXD*eaJ@<9o`+^qU}8?-IWga8?y$Nld38lB&4)%#?Q1HWjLMo zxy+b8D_M>AV(Bo1y_KxHM)+|AjTk!lDme@=LT@9=>g;&|@U{oxJMJNouG?NC@Nv(Q zum`Lgc!_aE>n@}`KvzC)pC=(!Z(v{m?wFa{1bi#}k#)|xC`o8|>YY6;M8pN3+D-QdWNm_0AvQRaEiQ3c4cXKvlWS)Lj&DC0eoZ8)2S_9)FazcX-j(Mh4=F`RUlr#s! z#fZt|H1Jk{2NDYI085P72IQd>r`9U>C>YXZYZ*2`XZdh{H$z!`FZS7GzdM%;wX$e8 zeg^L3_&DFzx}B08D025VM}XqEovNH)`v&KE^06Xk!C$Gd?NPolF=2IP65EZ;9uZP$V8DAM6_zY(*iFTI z33x7-d_fzxj|L9-$mdiWw>}8?L!3+JD8_U626T>KgG$JQ_m+3g_wU~M6xGH`_RMP? zIK~`nqI7tp^7$I+b-5Cfk_GqW8mVptv;0TCIT?lDpVUuyga((x))bPh%v|acZ%Z<8QV=pemiQF z1Ic_=pcE)i-&A;P-dJ{e)E!w{we?}ou|y@y&*EX-ywLRw<9%*%ve3TQ6Z|l#3SCB0 zJX=h^1>0`C>+QG!&Vs@`6@^qrG06FX+rbtKrhyx#9+OUzOnuZlnRy!xgR#Ud3is4- zu3y#O9Y3zu3DN@MmqXue(TFC#c<^Qvw8HK-A~LnJD?j>bhVSvOk7#=$jr^<1prHId zK2~wji>-Rk?iA_J$Oyq%Ud6FFd^(6u_P?expBY33)@T&QPV|@XWi!>_^&P3CbVAse zzWCQv#`v&iwUn36SA8HRPpOsQ2aDtJ%;*MP>g1 zzRWG5fS1Ee{xm-fv)HM-5)Ndb1Oz0#GUIO&i%zT>;-aF`7n$vV7A& z$>H`>7NWg>q71H`2~%9zKfKWS>ooVnN5KjJJZfHn)Agwp)tNT!KzYw^t^oj%h>5g8 zt?hh*xsIkvF5WRi7AtQq)btE_f8oGJcD!FFm1MP;Uoe+B)D4Y-H!anSj zG@ru5fIB()Cff^~7oNv6=)q2nhwj{7ZKR&@MhmZ&F$UfOK3TYA=Ybml&Y1J4h96iN zgmcbLyIsMN7;K>SOgH26HnkoiS!Bho{}@yiaf!0Kjw1x`xR2*C@0E9uR%9}s5>0a1v>M@GtdDBkh%g;uQvF)K(T+vlebwdPz)fT3;=kugjH7P<3Y?gBig7KfoNJF>EhX^K@u^jV zu-?D-g$hoGCn;qf^-VOiqnzA!M@YLhSwK1FKhO)~`QeRyi#N=5ebnoq>P2&74t&pz za=zzxaN6A1lzGN#Nd=Sq1Ycm90aHbsy;`#g=X&uqL4Xq&Q$g5x3c z!M*fE$>LtyfvIvFgy?Fg{o-WAP^&OHljJZND%y@4f~;2tbgDe%YjohQD*=NVDQ%cz z-^n5kc2!z|^{YUBGXW~5`;;8WN#}M96`aU>VsR~>X91n9^!y@2Jf8+T@&V_5*;g^D z|M|d_J>yEK+JeNfb0A0ZpC+?+W_rK;&pO-vS{Fbm2Mvmgq{fCbpRG}9ezR0$K%bWhc_f^7MV1z^4=o?m-HKl%c{^}@%)AjMA6EdLbd3V zqUQ~Uo04zRqt!qvE3K4}S&p^-2rsHIGAV*jG7;4DZ`U3whS6aSmXQTR*zQVMr{=3rzpBZ5EJMFN&Z0fqUoIr zWxad`OKYF9QiI_90;1CFRW1Q&hiLl+9Zgv#9kFFcw9KM^&G$C2fJr)wK{PUOn7j^AZm+U z#Vd@Aj5E{g^B$s=W(`nT^2A_^UAlqFx$C;%lRLnsmJSFeApf8nX81T1zHu9882%gx z3WuV>N9t}e$zmqQUCr|g!SVB^h&(*I}TV`OWn* zm_&bSRO$OP*M^&*Sa3~>q8n@X&Rn02JPZ(>_Rjn3m*N!HLHF;6#_^4b;G2n%ylUD+qYl^0WcGJ0-750eH_93#z$+Hc@@yOETMN%**e8 z?1nZRFpkC9j2IoCj0+Q-`jgE7d^baLFf@AQ0T_9~7gh^;X%<9bRMd~w%l#6;EDXoP z>|)N$4H~H|_-wCwONfqBO=7~_4UILHgk(tUUHQG82iDHYg_(_zFMVx ziu>8yWYUSq|Hs?=is0?UePZF+l0 zi*}28Jc?tBvqZ`T{4?0@zBPf%so2VGka`NRKyaF=;QUs9z=-b)_7|!+BUCtEWEX(A zJUU~aX3hX+qB4&yzz##n>3(?#N>DJE3rb-goa^r1C}rIBXd8HvnJIx`L@9 zRUzkBtFQlGu=VqM*Y;X;?!Ta>ps@K^;A(=4deW#`q@Jb77#~i=2WDQnDjs|(g07x-2)$y29DP zBEh3)4IoP)LYykaS_%FCn;C8L6Ggg@?|TrlsQU*8Q43qk^BK!$(}fW3?kvX*Y}V>N ziuZ!*uS3mP9R}Ll+HB4{jt+Mr{?E||N3eL#_ROm?1`vGE=YhII7kLm$NlZ(@@!Ghcu?RdGlASUwqiB_po z(T^et+F_|R4`>BnPh&_Te>}kVQXNG=*MB9Q5eVPg8Xfg0%JTRFm$feFTcv14FLWnV43iG<+DzgI)f-A}yDG{H7uv$*HnkCmc1?$Vnb*n- z9H85BADDdV?yK|^A2m99BK3OYhcbs#$x1n_v)gq`4{c4dG{jEhE!j;np1`6r6!|?V zsacVIezn|0P+dkqTJz6Xn!bNsHa=d6Ls4nR@L?#Rq}m}O6W6DT7$(0Gv#Js(qx$N< zI|N+44VxK#1Dh5~hcuUa!woQRcPU5B_&g|az6PN8B%9S;JuV*h0I%#egQjmdvbacq znty96x|O#*T<>JfgNq>#5SH>eBY}GjN`p&frJ}(z&;WM8qemM>s+lMQf(FUepY@g#iPGm+DiUe zh7iiVR&EoSZEpcO*o-rdlXA!+(Bu-Cw_jX+|NvDd=9X5KMrpxdPGvvTN5z?uk_sa2lX-3K3Ay`mp_ z`w5S@UjHck;B8$Jpyvx>i~t5->$%9clf+xpton=>Aq|Z^!AAFGcbl0J4z5TicQT-@ z_q0E(_s9FT|3QqjNl$K+OL>F5+6&%|Vj$lDlz_wPxbs51nA^I(!$q>w!thnSLu9JNi-Tc{sxYluca8r=brC1j()C%Ru2ADG*|{fWyVWY>i${H#h;$wn$HGgwiZ1aqg6 z1GvDs7q2K2<#93DlrgS5mJ{Re8i*d(3M<-J=`iZHNgWy`jhe?T}gp*(1Q6`t_F1yZl znnw!uM<9}^C6&p#3;8}lo4Kk{W7P?$d6iw97#jGln?%XoCJ*A}S^w*jVxN$V1C*;z zEXyi(=)FP-M3fanRC()=X-#kB`-imAzBjL-j=ag{pZpJ|%7iWoF99*CU=8E!$-`~L zu)_onV}|EEcybw_+lIqwjo@)CWo2b+%;`WQK<@ooEV}~w_U&60?6YJkss@IdWEcVo z_G99ST&Py;r+k1-ZCz$uI>6&1f^5Dq*J<6qy*{i4(V=??h@(l?w0QE z?(UREka#E8^WOKh_kK1X`0;6Ro@>oD;}~Q7k3gSE_Z$ODrx8EN6f#Z^IPsy-iTuZL zHPH*1=Zq4YVA&(0eq09>otOYmb8JR>I=WeH_>v%LnZ~{U`+l$UK|I{&PL1?);NDEf z914tBa+OcF<_Y29J2<)-DJ(Fsm1JIiHALgH)euRtC@pLFv(m0chLIqL;2Tj}I&oky zohlc>BGRck-Tyvb+w)N|zrosa*N55AusaAPFh*WwT&*RRE7gN-KGT4h(5&iYseTJ& z<{@@)T;6pI_fm4sed)o960|=_OuC#)o{w&UqquHvuSH|GX2WoPs(A9Sk2npUa@Cew zS}pFODo2oHc1!hqzaSh@F9OnE_H^ zQtqZC>?$2Pu_g1XHCJ9D;ED>)vhDjE^gPa(UR6J?bL%w93_LmMW7V)6*s~dVW1P?; zfY>aEb7`buY^OQ(I@jRhehev7d-IFp!@^KaU<0oO`fNUJ&o&AYRrqxmy{iRA%OaPW zAX{;qHm;PH;@OL~NVmVC$m=jxh%l%g<+N<`E+N{IxA?P|7Lz!fob3PP;1rbJX9BiD zn%2|V6{45)r}rRM+xgG&9c7kVNYy49rox|-#7&`5M;^6E>&%g#B)EBIxZp#!df4DZ zB}2$w5>F+(4*P2n(}nGTy`Yb?2Um>uL(@=cB!eAHKZjkh-=ZcQEu)6ZIwUb4`R)5% zYRq$O#26b4BBWa998g_xV8 z%^h2FyN&>Ln|zOk^am~u3}5avznOv13A$!9_nF()g_1A1^XALd@PgdoWkW=BLvddk z7Dm(H=Bjl%AXXM#CbER@BgshiVmgZGT{k+(2pARrewr>%xv~$Yyp^D+4=j0&Bc8yTfRlYNb6e>#N-j?8v(vT%9KegMJ2z{I ztzDsrc|lclpN4rM#c9OP7RQi?+u7guIq-Jzh87qq<&{3vxzJHlf3;aTd$)e#;;g1x zyF%)bi}M5HE(VIHc`zUKlZHmhX=kqcO`XMwP3Vs=Pmj<7-ym^Q@nb0`my5 zYpHstWEB+LFwB_z;io4N9kP|a%S~9x3v>Cjy*D+izYhPnjg!}`j+JrjupO&Cl*b%r zEu-V(Vm!(;#YG_~T{I01eyytTyrkU;qxZJbp%JRX_mY`mX`fmupZH4sKOy6&)H*4T z;J-vgM?yO(^0FDa7CE>ruNEdUA@>W074R+drBaK~0}=woIXTr?0|TV`_i5X{9jsp} z@ipO95s)x4R%Qwg+&m+H5X~E}WcV>k_*@6}yt=yjVSA~Fd?bbG9yf^*6)%M|4`34i&Sgz4{YS9r?A$|-#7pS;YvY+KU93hL}G74m~`QS zI0`df@06m#c8)%w?Nku{3lBDo>}eZrtNycE$?7%p=&@VFPb)k&~>u@I=k)jXIxCy!0zI3V6XU3d*cGTX@EK ztp>+h5yR8`ilZ)gi#KT{1V}}2EDk4CZD!Ci_m|+T)%{{&Z(6RxhRa>YGl@ymH^rn^ z10JOBrS20cBvlaxeU%CHQnkoWyt)cevbJ3vw$mEJg84NBJ356@7r@7m0S@{RIID77 zJHzkT@@^y(NY~bJFi?9Ok8?=8y(v3~88Gs(>oKq{>08UBC zZ#r&$0__uW*1EXa`?I||9(^X$Us&j4HrFS$b z5i=<)X6ig!a6n@}At8yoiqi~12`?T~Qi4YJ9^!ZBIb8rVzCd1Wg(~82`!hVowfY9! zsS+?nm%YZf$#cPy;(%j12)c}t`ZW{o0QH_i=Md2hMa6Sne9NB`-*z^e;V}f^zI#|y zCu(p*s|4>dJ2t{)F2VEqw`T36WKn^g{vlHs&!Gg~nv1z$9(sN=QH|PRU05^N7-+#cvs5jDvRK`{mTM^ATWti~YKyhnLo@+yd z3s#;SJa4J+QZy;Cu{gbE%GRAY^)nztn8uQ>`s+F=7-LN-B($0=C+erdn3Z)8OMg(U zt$B9eNiI-$zP9w_Srq2_Jt|_`YgDrK*!0ujs*m$cnc_uQco9Ep{_+DMja}J~5xf^* z&!hj>1&-CYyz0?{#?lE!8An4_xy6$3j;>Px$YwfkA=)@ANjiyM)kEG`QpVS9~cs&0&DGoP6-WWWGr?ZU-%H10YLDnh>XRM%L zE}(xG|B{udq@fhFlBoV8r4?qu|6qSP4^zaR7p+qF909-R{^q5?NjwI&sKUZwo8w}I zLj)dIvNHJDjVGG7I2V&opG3)Uw@)zT9xdv5$HX;*YuYd~yDFbq6|0G~pcy~g1IOy2 z51n9Bf)kbV*W|ky;G48O!~Dsc!5MFX@6G#{Ll1ZG00EmxXpX6(DP1)Th08G^hh%Wm zNEs(|qa>N zXxW(Ph=*t=B@`Tgaemo!4RIb}1zM`Ei^)*b#awmJ(RVf*hg%6vNho4fNM`*dQV#E{dCc$PfZb3N+zSLTtZviEx|l6N&Ck-5%@% z7EFHJmC`cU$9ez@5SRZhu-wcSOn^qDMk2z_=3AepSg)L#NN+ z8}3E9Gt@-&k&3^Kj&9_x#nvJ4d!Kjsd}6Ecvq3?gkA0^ZGuOdHN98{93_|c>zP~<_eL&WtLWA7PZPkH?ZN?MFcO&n5O1NgnG zzK|;7W0gCMPQ|^k*_C4&6^Hw*r$n!$mas32Tsxyl$v+*Np);lixCzQ?DAL_g!y>99 zhybr6R(^>W6Rra*Q=dZCQN-T=e2rjl8)*!QzSlW4wf=PB&rmFa56(jinS#( z&T}cl&oQ4Ckb)jF`D%cYRf;z1a^QFQJEs)w)RdDyAHM?XZ5}bwj2c#rj$cZzWx>YA zhMLku?6VTBSFtc%rMLsEF}1jW7@hcFsB9kbHXpU#XpnP-Cr9TSVsy-WIDUWyBm>Yl@pWwzD=0{CE?;GoD#>TiV--&!07QhPjS5 zzL=>$$BJcT!z`MBh@`FsHiipWFK%beBJQ1sO=CLF89Ehc6QLL+JdO-{ALpaAddmg) z!T!>n`1f`eRJT2FWO+Ij9-P_E&DkB$sBOboFh=6=O-~b?KqdMaL5r^z7X5u=5lsUuzIip^Q&`vh}i@20d!sHS0<_*fF_SMFVVo`gJsz^QRt#~H4Is!lk&)drJq z1m(rE{VtS|{a@}t{T-h?8{|Spio?Eeq+0KC_Vx7o}QW+3ek`_xO`ZylP}OF>m7&N4>gjW^4!}zVlTAL?6Mf< zr?kB)yWUP!@j=EzF$$jxH-#FVkk67Ld$Gbl6q+;iEriAr?S?hXMGg|QKJ^_;%S%Ju zO&JX*Wa67+L`+6y>vyO>4v(+w*loPWar*EfY35C9t~!rHqaKO=YXy9-Enad%I)0xTc0hlXh8!&;Hll zZ$JHWz1Rt*JcRSL_s^-|7o3*Mtq+uMK%ImBUi2fVG=AfR}d z2r>neH)bbGioMh|C#Ae{iJIhixNBD!Is1s#l1YIRYa%;(v{M5kY!XY;1K^AtYGP&W zaW3l*+_$&4_xDoLZFMw|=sPuZn>jLW7_zs`rz{;w=tZtwF#GE)J5`U2q7J|q^#Zzz zwl*>-J@GeR3he!G`BTQA1zlxFk$t1Y;OK2w#yDpldEU%)Zz0rqXB9MIlP9;3mz}^` zJD8>)V*$P`1C;m7YMx2}ZI&vkj$bT4>y8xpqa!ZF=TRYve0f@%V~|j+;6I?)KGiJY z`kr<;&B{y`gvnBu^egQ9qyYI?DltaEpUEHPtWSnPo5cncde zbzAvhuhGDhgfay!U2z0?+pr)H%|45O+y09e8k4Nb?g}@z>2LuDz8;Rea({@?--Zc# zj&sikiHp%N8hOVgR}PU&s#OQQ2p0zuQLfd8hs!`GQh16K6dNu3^Ge_=n{kT$CvW6U z{zAtT`(v=zi%dDNRwfxsOGJc7kb_WpAc9_J?H$Iuv`kDW_E&YbzbS z(}RI1)#!RCGSYAw>~GWti5A|-^X`AA?d8B@*OUU5(<8%e?u;D6dL zAbCD|LfXEHBl+Q}gU}p{@$FNA+qDlaES(T54&|uiXW@g6!^ePAtKT;ws~(b#b0S>9 zy!udY-yV*rxGS#|91PWD1tb-x*ovGd(zSPr(O@^xUeLYSnb$Dkp|92a!fA)kBcwgW6pNMc7t>G_xp3@xj#;~~_beU)ur8Ca{mlzxhZ5}?m=>4ES!e5b=bFDwWG$|; ztsGHNQE{1?|5dFvj-nOSGgv6v3+kCDQPr`bi}f#H>RxlH_vO6%pF*bc3x;>5+mP?Q z;fai_E!VGECTT1FKZQ5V!kkN~jn+l1<5Vj3mZ@~h2KNI8`RQf36ycrHbnTSWId-~O zcA+Nrzp!o6nG8clNsI*+L8zucU6z@vU;R?yGy@ywvQE~ST6CO=iRp?Y6%$91ZT@3q z5#Cs$yy)nCH$)Npf8ZJ?ZV3lg4aJstMBb}Az3Bc^EC0PWFJBb8_ z*O$8^QwO-$A43QEXxU1s=$l$SRv!7_vRsV4VMW0)wDQTl(M^)6?VqZ2itQczub5Zz z{M3VR>-qkFG`VU8$uHr;giHjA1bd4tki}ogDf+su$293re89JMoX+_PJEL0At;aEH zpHmRgDO#tJUs+5t!>}L3Oc5teZodx>)Q+VtmNi8$xHE@%iBmg(_%t<6ID9{ClV^hm z;9~?|9&8tk}#87@r8 z^a)t;FV466j>fS0*~9+K#TAd*2~}^p=$$jsON8rJOuGE61;*xacbFhjy-VKuJf5no zV~hC)d+J8`BuJ|^WcjknGT~f`=ZNLu)P3j9t421vdABg1DXJ_XUHPy=82g&U$@%AY4x4#GD|E%rqCgw>=2v=) z$jJ;1axgs!ZJCkN*n|u^?I=xmfBx)$<0sjdILMfUqkoT}Z$m|og)5(|6SgrbN{u7i z5y1Pp`yVBV{KE&}qOe9)n?|Kpj7izeRhv_rXl{NZqpGxDl`ASL%DzCfr*}XK7Rvew zM9#hplF|LF7DUe5!V!3i)ItV8C8|Ef8??*?YLicwRCY6sj__70Vjki-R*Cs^WB$3+ zzzY>ZzA!B}sVU3@?1f*-!j3(M6gvG(vvp*lP=%>d`hu8GGVv+0YI*foYKOHaRiH1N&E(2y!i&3|i3TDu1jPMjr$Q8NMrT=BujsWzjMCh(Hw zX`5X`;b4BHRpR~Kd|5-nXS>~x6YA#TKFyKe+Ja91$vG_m9$OCI+b~T!g#!iT;_f4$ z#-N@xWs72}a%o2mZR%f~O0&d(*;P9lzoUG#gPRqj>?4zva?yT<7{{IVvr_`Hwlb}H zM{1mQ+kCEUg-Em+AUH^WDHvPlx8VkHAfWovzAQJ5Nkz#@Ccksk$c= zGg~^HPfef~)-0coCsq$j(=d%A`Q3y-`50xSSgI94`HVRLuBl!34Vn-P|NafE{bZV?ZZtvZ70^m*i*a4V{{lCL_7vg@<_mJUIzt?X!Zsf=qKaK-pZ}j-xR!vJL zdinwg`{C4t<8C5MSzy@ZXu0UYHUqT(xP4M(*HNGgJ5*8>jR7KT z7cRa3@EtJ(z*lgsy>KCQR2G1OBm?X~p&~Zy7!g^se2PB{I=gbTcYUlo$V&~83yRBo z2CDmhmfN?6SRdLK_6x5!juyA&+A zEC%e~nSlhIgwKCOBYB0D0wp9Rd;DO~+Wng1X0gO=4yUQcUtMhHL%x(IUZj4D49yaT zYPVZU8ND;Gb)FL!P#Dt{plNNEwQ9{>@yxs2$qH38a{1^Y=@Op!XHZxP#Ifz2BMU=l zJdIU9g}7eFTyJmw6mFwj81%>1*2_rlXG_0zRg~x1y3%^E*JDO3YCB+SHKp$bin_(DMIlZuhQUm;V_ARPy`L7=>RDa8H6!@&-)71y6!}aI zs(N)(I$?+%^nHi8u8r+i;eXemYZG+DHv5FQY%OGQ?XpAWiMAm&m*H{yY}*CeY6wEW zVbHd}c-YV=KceHBI)^p!PYJ$|a!e!f3?bCjvSooUhwz2T@C*1LcrUKlEf)QV1Oou) ziEq5Ou0d>eW=0px7AlSQRD4f=^fk$%t9;4dtR&B<4X0Y!Er>|}0VbnTgo`?hrNg5h zD5i2JnI9JD393QC7il0OJvWzLd0(RUHSiVY@?>-JtCmgfNUQDu5*itWv~r%c^Dkk? z5jPgm$jgg2heNsO!u>Q#wk`heWMz^-hpTO{4*8xAndr|JlwZjrM}5!>5RrkwWIHS-Q7AfMT%$F*ACC5C1L%j zdlJVFOKVR7aVZ9UG9&^16LId*)vNQpf0 z2!*w}YbZ#{)yG+&6LBH&+BRcH?q7Mt5x-!|wre{$Gdtn5^)~}O_KWZ#EACx-kw?bHyrnuLTs|=ZOO*B{&8k86F%2mDzgKs z@PT?7rIK+}p9nwFvlPn5FZ73PPuKMLMO`Y}U`uR9lDc;?pv#?Ri=V=Yq=lrVOt(n& zh7+LI^R?gbajG^uGZMI;37o*Mj{!t!DYLZTy)BWq={3t!=n8erLAR29(0;&b@BuYX z&mEhH>JI1epjPB^R`xGqD*SBvWC(sjPtj3{Fk8`r0me}>nR2~4CU%~ZX(a0L%3Fm$ z1>LaO>Lwj1Df7=TSxwRc279<+X%S)N(9KWxM}J1FzWahfD*epJh678spMd3fe`Rul zZwYAG6Y@;%4*(Tw8p)E5Cqq7gmS#Gf&QA!!6_9+-4|e&aS#r4E=mHJUTcMd~06GrJ zS23sQzduq<%mpP!%taGf#B6V)6$J%B$4hMiY-R=>PkPdemD>5?yic*fXKFJ@lj}Ba z@eUhZt;&3C?`np>ZKQegHV?(E={jgkhQD}zN%W}`hV!C(I^trO( z=lL<_2DVsT%5rjYVM!r<;X}VPT+vgFSK-|C#>=(&!w7iQa1Y%(x5T6ocuoT<^*_7obs#g6+tI1jdU>@kLStf}4th0xtm)Unwglf)tK}KCPWIfcr z5Cr$Nt|EV7L@y?}hZq-uNJcTv_xPi9=N$bjv3Nh?XTot={pO zIC)jVRRMdk{p^hsOK(}ZW|I=`K74#8JR!n`fbUyfVA$ch_2Oq|-Ce-n+ceQqcD&?GXADo3E+~4P(F_?=Sm=}6Pk1>= zi>BrSf5a(#r2fu$1l6y6r5ACrYhi4;ZZ@U9h}C_Hp`Mlx@H>4DZm&C?3KrW|et6Q4kj!n;ULV9(EC%np%2&^A3ri1mWuH${rqz zq20(IIeWAVt(VomY=V*i=**e&OuPtL=ESLQRwomX7~O8nd5!6)lhlW}ogjW&xMNIM zpfP3)VO?V`|7zP%a9eQShI=2I+HrDVhk}i#4|keMC(B=NXs{p(oj|faM-Gc0_<*$N z=Oh?67&kh0Pe5z)dr&EtsjAx1)5|A%N#WO^=HQ1mO$6g*`f8f7+J>tUXE{@GpmZ-~ zcnj(;#D)(TusmUh{D_J1D^;%#5m<$`#*qqZG2(b<2({+2haayzqS4}7OJD|?C%8@+ z^Tn(t0%kCMfwuE*o?#VjpD zIjD+b@dJ$y%?(?7^x0Djq(RFY2>K!lKXV@PZM)f-q0j96(K#A|h6@pS9?eEz?H`!U zk1a<`g^{DCPf?B)=c>&WJO7|gIlvcy%#mCRVT?#O;qSlbgcmSQ*?Yn@rc$P;C=3^2 zC9FH@Sc51j_CeZy-qk4la)2 zTv%AN<8DJkb~y+-kA$7Cz^~|Vz=Y!%vtf8`OrhaeT#Zh-iNQ!pTlGZ}&T+zo3GbN! zgojST+nvW>^Es8Hn5^sSrVmV)Z2dkzlbd_frYWQQDICETd)hWJjfMTI-NqCS7e6=bxNA;_pp!8j=$gSFSb%xHaAb?JEFF3QejH%>ZW}PFUoSbZ-3s@ z7!`qp6ecF-L$FYUD$OJHQHo2KRpj#gEW2uHr~z!1g9bHUH?w*6upf5EMCuxSUd=G5 z{0c0Bp8lD~g42bWgnFc@rdHHNh(V`OV0e|FFxbg)3fo}~MUnkCr-ZC4TqVW$&)Ei- zZbao>M6dE?V>Nw1!MND~P@Q;h0kIL+_^ObL=evzfWn8p@Go&GzDpBkPhqt%){dYF( z19z*407XIT>5#qOjIrUA-uot@x^x){)5C*Z8ay@A{_ic9bx^-`On6dnx%(z5@cJw{e71IG z^65mK;9`(ln2IB*JT#@2&D@R}45^ZJy-A>c8x!c&PFZvGhj0+9O#~ljos)u^*(TxD zRx>uSfM8Mx7H#Q>kl-K&lVyCBLmbZYpQ)w6ig!=v`A#X~ND3;Mn{Y#AU+oZIGv(E% zFx~-lD3?jxT#znI$-L>v8E9t`!={9t_qhj+FC>5BCXoL@pzmItdzQ1>$%>xkUgcw~ zv=0_+H)hL0vo>grQ$CiuC!Olv>a;)J(Sl}w{!Kwe_va}v!1F3?1PwIPl3wreKVt*F zNqT57%PMbrYxCW1GFLYpDXCDPjBI5{_$?}0c|!&>I@i_|*^^X~nLBFxGke4SdhL2A z7YmC*pg}m$b(e`O*OUs=oZG5)WxFY3qlcnJe|fm8zgu=ZHhUkR+B=LQO^IL^$`**V zQwI_I$m9Dd^&X0rvlfahzdoy3hY-tWQXG5xwZ&ow`v)w()~CbBM*2h3q8Y+6Y6WGC zOp$|_s#?SlVHtZuzaE4*y-#U2oSJNKIP@KE`eKouU?fHpzn4R*#A@a)eO46}&@ z=grML_(wy(Y@SfEZN4DU#S&q(i*psl8RtME|~sY&VZ?S1wFMTDd;s9#I;98 zMwYROuvK{=mbdYuIGSCDYCqr4?avg)W2Tf|=6&(URFYh2_4rHwTSMkz;I*LRx0Fj^ zkr<=x&waS8wJvF41NrCugGp)2e_|qB4>U+Go{>qG0EdXdcKZttNL{*ae7);^=`7*< zVh?O-*G(Wquw)}2zi72fdaVS(lH>DM{6rUTKx2r;&fv?up;3VF( zQmTB>$2|wVf=DY|^i5P~&m9yb=m_P?O|c@ig7;46dBsO14U_v%pdwsz%L9&afp5mK zZf|erJpI0v$Q$#pvepXsopOcNlRPqgtM?BT|20d|$v7`Qv6Acs`k#*Dqg()kjc@Emh) z`IWJ?nILU|iiTGEK$41f)mnXCh=8`5*wp1;8x@YujailyD0SkNZT#?wBwuf8o*)02 zpg~;F>-xFEfkgpl<)NG3q{B9U`TK<-^7?0^urcMGWkW?{+yP# zDzQ0b=~oS&B_$JgpkXVLa{1is-eouM;LeOn-012rFxpUmj`Z2wnc`UPT80*D2RXZW zLosgbKc6*;=ymTtvqWe)C>uT42EgxSVWx^S9;61>fDFm(-|?xzZjL zYB&xW(BtdIz$6hLA)QA`&bvpOW>ChMm&ka&8bkky0RAzfewi|RdHaNX4R3MD!bdYfhM>q@4_b|fz051I>FN~kbGESWsT*K`5 zggfHci=BTA*l>I#ZdeDGN+>qg`BRuf!(>4nNkp1AS8#z6X2^(ow#QG04Fj2p6=!q0 z$9O)3N+SraFkZ`5jG_w+ICc6w6?Lyw``$m7X*$E6T|F^{(r&A&$Dv{^&E>}?ET$x} zFQL=s!n?b-ABaicCPKX%ky592O#-|Be{rsE(BmA&?p$3&Sn1|wP*T0AQ!+2=e%$63 zu~!KBYF?FKcDyunfukN%(8br2N)8^Lie4W1f)~fIJl-^k$-p$)SM3mGPD8=&SBe_&;oMyF7{s?maCnNGtUyURY zuT|x*j9as=c@v@yy$>R_wr0dgLxM_T!eVD5*p(!WM@eG(ad(a2`!IJ# zlr%a`Uso7x>3f@`u zbT-Z2c1WwQTD`D~B9-ZpDcuB3ypXgqay;LEo>`3!1VZ>lD1x;spUFC7<+Or#3{3V_ z43oV-Oz72xtG@m>XhpFc)^M)@4&&UlouPPrP`Ktgh$#}-qF{{7B>3K1n#>j?Ao}j*k zi=R>an3`Un7GJz;@l&&AwvCE0fry3nOONt5A>UOE46wVV3XEQs6hEutOpoo!p*>7~43CQX{hxaS#)gjnE5l1X6A9uR z(yV&6fHIlNZd`}iaGmd<4U8IITu)g-bi4l|C@0!$+yQcH=-{F)&-2Keg3@PIwJE)K z1oy%Y7PV=rW^HWo!!ltPUw`!f+wZa+8e(E%LM>t}s4|t2oV8Q6Ml?wuQsR)t9f%U} zmMzyxrTU(g4_T2`71Zrhu(67U5eRx`vgqQ76JvdZdGi;c`6MEDJuKVC|GCkh+ZiBA z;FAh#MvIGzzRRLAnlm{fN|0e6zR;fLU2={sFG4P1;1JHi0b6al@YZA}^eh89M#0rM zLEfZCRq;JU-FtE{K!n zB%c%s#C2BzKl|4~UNQiP8&7#hW!i8kYkiw)O{<$~xP%Hq3}@&52o`X?sPO-+Eq-22 zFjvP5^2#|HO^&5=*gGlA#Xp$xG?FYJL4%>J;^4))TmP1yBytr*`aAvzS{L%Xhf&%| zq2Ghsn7H)`tJMv&|2u7dfLxaFs_X_V19YWQwVmClWInt-9`pGM?IJn0mcHFJ!Ox$4 z1SuA%sL|1>^By!@yaoGk9mW@`cogOovh)p#7N~+`EippP|LtNF_5pN3WA{SGhl6XO z;PGaP03l$=J-U%=$j#25$cyB73;ux`{0V5H{d62bphOu28_c>?jJ0D_*)Q_%!+?f} zX}i3=?E^*gFu;0c1eAgz-fhq|$adeEvtJ9MbUeT7LkcBF24YG5t&ZSLKHuFyXV<_S&$?M)ZTSAtG|saGbJgwl&5A5Q>p^pF$es*>EGufF2> zR29jAgW+b{>tqgb$0q@W8HEh1zzz&Ia5Z9_yU+i9rVKvqz*S${{DF5oZS`g0(mV^Y zi`(UcfPZs*xN>Qk~k9<`a`-9Z?! zwSUJjvUN=R_CWMGkO}mG?g84N`ev_iT>k>?Os+s?-S;g*C(sm+r(1KG|H6Q6On?(R zk1qktn<$*QwI@Uk`|dO}Tx(J*%u@+ZXhsX%7AL0=+0!gIqLqIiyCj8-hDI2u#qUS7 z{ypW^Jg+`z;aYtoXbPmiqF7xvP^hn{VCdtHCP@`p4=i!`V|)Ae%aQ#@E|F_%wYCVy z7@K>F<6*VX^T#}zJCRFt9^9)I`A%gV7x34>(GJlIZ)3%S(i04bX;D zB$5Du?mY(uV?RRE`*`@2I37H~rtqG~#5H2|qj>GA&?PoJ-fB_o?IVV&zVJGnZC6XZ zn5GldflvQlngmiM!sGgIhO6+W)@4{2ylA7zBqL~HIz#Xm@Qc@%tlESDr6Kx0+Ba1i zJb*35&pVX(eIEPMNTY=90R*?jIivWA#f-$)uB`ElYquCJ){h?1cD8IO_MZ!)Um~Zgb zfXVxQ4wBY1*;XUiT>F7WEr8+I`v0fl;9jdbNK z*PTCPa7<@r_13|~jl#jA4mV(^WbqO-h}Q}`5G3f{5j!xtH8am9b!8Lz()w@b5T1y3 z(2_S8kZcaHE?<)O09neLl~r=|X~`8e3}5?~HPK5EOh`y*{e|xO%Vx`*SL5aq8Dg*a z>-ovk&U{qM>xoSK7_P6L*Qd=D+0C1@$72ftK9d?63pQB$xmG8b`}+%__aWKa+Z_MS zTq+Rg=;+QH{+MTg_HZyFWjKV4g%$L& zrN{J)+iiiA!ocrIdh@Ih%a4?yY3)WTeDg_92v&{8!_G}z*nu+wlR(Jv>HALbnCM)> zkhS8VbGJVkz(sXE_E1`V5#sx|!={JK8DpNMVMoGfKG2is5-@FvHpK1^?7wvZE z!K-MI+Au?uX3Pbm(Xp04gN(YeFHY^)-5_d8F-I}Nna^Z2hq|Mh zOV?6{@JV{Qh~V_~ zFZVCaFxz(>1G@jdOPI(UPNykxG6oZbuKf!I>!<_-h}4Ziv%fGkgfOKm39c8>w3J*B zV_DI~D|2C=2KA*Poxdbo`@It>z^<`9r~M$(Pz>|+`QHn^jOPp#2)T)BlusA`CH4Hz zFtFx){Z}qplMf&J-=+rt4F4J)2q3~Xo6#BnzuJ?n>q0&ueHMj^deSOcc?EvNgr$Va IKI{1WA3s+3)c^nh literal 0 HcmV?d00001 diff --git a/vendor/github.com/prometheus/statsd_exporter/pkg/mapper/fsm/minmax.go b/vendor/github.com/prometheus/statsd_exporter/pkg/mapper/fsm/minmax.go new file mode 100644 index 00000000..95bd9c55 --- /dev/null +++ b/vendor/github.com/prometheus/statsd_exporter/pkg/mapper/fsm/minmax.go @@ -0,0 +1,30 @@ +// Copyright 2018 The Prometheus Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package fsm + +// min and max implementation for integer + +func min(x, y int) int { + if x < y { + return x + } + return y +} + +func max(x, y int) int { + if x > y { + return x + } + return y +} diff --git a/vendor/github.com/prometheus/statsd_exporter/pkg/mapper/mapper.go b/vendor/github.com/prometheus/statsd_exporter/pkg/mapper/mapper.go new file mode 100644 index 00000000..8aac9d1f --- /dev/null +++ b/vendor/github.com/prometheus/statsd_exporter/pkg/mapper/mapper.go @@ -0,0 +1,386 @@ +// Copyright 2013 The Prometheus Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package mapper + +import ( + "fmt" + "io/ioutil" + "regexp" + "sync" + "time" + + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/common/log" + "github.com/prometheus/statsd_exporter/pkg/mapper/fsm" + yaml "gopkg.in/yaml.v2" +) + +var ( + statsdMetricRE = `[a-zA-Z_](-?[a-zA-Z0-9_])+` + templateReplaceRE = `(\$\{?\d+\}?)` + + metricLineRE = regexp.MustCompile(`^(\*\.|` + statsdMetricRE + `\.)+(\*|` + statsdMetricRE + `)$`) + metricNameRE = regexp.MustCompile(`^([a-zA-Z_]|` + templateReplaceRE + `)([a-zA-Z0-9_]|` + templateReplaceRE + `)*$`) + labelNameRE = regexp.MustCompile(`^[a-zA-Z_][a-zA-Z0-9_]+$`) +) + +type mapperConfigDefaults struct { + TimerType TimerType `yaml:"timer_type"` + Buckets []float64 `yaml:"buckets"` + Quantiles []metricObjective `yaml:"quantiles"` + MatchType MatchType `yaml:"match_type"` + GlobDisableOrdering bool `yaml:"glob_disable_ordering"` + Ttl time.Duration `yaml:"ttl"` +} + +type MetricMapper struct { + Defaults mapperConfigDefaults `yaml:"defaults"` + Mappings []MetricMapping `yaml:"mappings"` + FSM *fsm.FSM + doFSM bool + doRegex bool + cache MetricMapperCache + mutex sync.RWMutex + + MappingsCount prometheus.Gauge +} + +type MetricMapping struct { + Match string `yaml:"match"` + Name string `yaml:"name"` + nameFormatter *fsm.TemplateFormatter + regex *regexp.Regexp + Labels prometheus.Labels `yaml:"labels"` + labelKeys []string + labelFormatters []*fsm.TemplateFormatter + TimerType TimerType `yaml:"timer_type"` + LegacyBuckets []float64 `yaml:"buckets"` + LegacyQuantiles []metricObjective `yaml:"quantiles"` + MatchType MatchType `yaml:"match_type"` + HelpText string `yaml:"help"` + Action ActionType `yaml:"action"` + MatchMetricType MetricType `yaml:"match_metric_type"` + Ttl time.Duration `yaml:"ttl"` + SummaryOptions *SummaryOptions `yaml:"summary_options"` + HistogramOptions *HistogramOptions `yaml:"histogram_options"` +} + +type SummaryOptions struct { + Quantiles []metricObjective `yaml:"quantiles"` + MaxAge time.Duration `yaml:"max_age"` + AgeBuckets uint32 `yaml:"age_buckets"` + BufCap uint32 `yaml:"buf_cap"` +} + +type HistogramOptions struct { + Buckets []float64 `yaml:"buckets"` +} + +type metricObjective struct { + Quantile float64 `yaml:"quantile"` + Error float64 `yaml:"error"` +} + +var defaultQuantiles = []metricObjective{ + {Quantile: 0.5, Error: 0.05}, + {Quantile: 0.9, Error: 0.01}, + {Quantile: 0.99, Error: 0.001}, +} + +func (m *MetricMapper) InitFromYAMLString(fileContents string, cacheSize int, options ...CacheOption) error { + var n MetricMapper + + if err := yaml.Unmarshal([]byte(fileContents), &n); err != nil { + return err + } + + if n.Defaults.Buckets == nil || len(n.Defaults.Buckets) == 0 { + n.Defaults.Buckets = prometheus.DefBuckets + } + + if n.Defaults.Quantiles == nil || len(n.Defaults.Quantiles) == 0 { + n.Defaults.Quantiles = defaultQuantiles + } + + if n.Defaults.MatchType == MatchTypeDefault { + n.Defaults.MatchType = MatchTypeGlob + } + + remainingMappingsCount := len(n.Mappings) + + n.FSM = fsm.NewFSM([]string{string(MetricTypeCounter), string(MetricTypeGauge), string(MetricTypeTimer)}, + remainingMappingsCount, n.Defaults.GlobDisableOrdering) + + for i := range n.Mappings { + remainingMappingsCount-- + + currentMapping := &n.Mappings[i] + + // check that label is correct + for k := range currentMapping.Labels { + if !labelNameRE.MatchString(k) { + return fmt.Errorf("invalid label key: %s", k) + } + } + + if currentMapping.Name == "" { + return fmt.Errorf("line %d: metric mapping didn't set a metric name", i) + } + + if !metricNameRE.MatchString(currentMapping.Name) { + return fmt.Errorf("metric name '%s' doesn't match regex '%s'", currentMapping.Name, metricNameRE) + } + + if currentMapping.MatchType == "" { + currentMapping.MatchType = n.Defaults.MatchType + } + + if currentMapping.Action == "" { + currentMapping.Action = ActionTypeMap + } + + if currentMapping.MatchType == MatchTypeGlob { + n.doFSM = true + if !metricLineRE.MatchString(currentMapping.Match) { + return fmt.Errorf("invalid match: %s", currentMapping.Match) + } + + captureCount := n.FSM.AddState(currentMapping.Match, string(currentMapping.MatchMetricType), + remainingMappingsCount, currentMapping) + + currentMapping.nameFormatter = fsm.NewTemplateFormatter(currentMapping.Name, captureCount) + + labelKeys := make([]string, len(currentMapping.Labels)) + labelFormatters := make([]*fsm.TemplateFormatter, len(currentMapping.Labels)) + labelIndex := 0 + for label, valueExpr := range currentMapping.Labels { + labelKeys[labelIndex] = label + labelFormatters[labelIndex] = fsm.NewTemplateFormatter(valueExpr, captureCount) + labelIndex++ + } + currentMapping.labelFormatters = labelFormatters + currentMapping.labelKeys = labelKeys + + } else { + if regex, err := regexp.Compile(currentMapping.Match); err != nil { + return fmt.Errorf("invalid regex %s in mapping: %v", currentMapping.Match, err) + } else { + currentMapping.regex = regex + } + n.doRegex = true + } + + if currentMapping.TimerType == "" { + currentMapping.TimerType = n.Defaults.TimerType + } + + if currentMapping.LegacyQuantiles != nil && + (currentMapping.SummaryOptions == nil || currentMapping.SummaryOptions.Quantiles != nil) { + log.Warn("using the top level quantiles is deprecated. Please use quantiles in the summary_options hierarchy") + } + + if currentMapping.LegacyBuckets != nil && + (currentMapping.HistogramOptions == nil || currentMapping.HistogramOptions.Buckets != nil) { + log.Warn("using the top level buckets is deprecated. Please use buckets in the histogram_options hierarchy") + } + + if currentMapping.SummaryOptions != nil && + currentMapping.LegacyQuantiles != nil && + currentMapping.SummaryOptions.Quantiles != nil { + return fmt.Errorf("cannot use quantiles in both the top level and summary options at the same time in %s", currentMapping.Match) + } + + if currentMapping.HistogramOptions != nil && + currentMapping.LegacyBuckets != nil && + currentMapping.HistogramOptions.Buckets != nil { + return fmt.Errorf("cannot use buckets in both the top level and histogram options at the same time in %s", currentMapping.Match) + } + + if currentMapping.TimerType == TimerTypeHistogram { + if currentMapping.SummaryOptions != nil { + return fmt.Errorf("cannot use histogram timer and summary options at the same time") + } + if currentMapping.HistogramOptions == nil { + currentMapping.HistogramOptions = &HistogramOptions{} + } + if currentMapping.LegacyBuckets != nil && len(currentMapping.LegacyBuckets) != 0 { + currentMapping.HistogramOptions.Buckets = currentMapping.LegacyBuckets + } + if currentMapping.HistogramOptions.Buckets == nil || len(currentMapping.HistogramOptions.Buckets) == 0 { + currentMapping.HistogramOptions.Buckets = n.Defaults.Buckets + } + } + + if currentMapping.TimerType == TimerTypeSummary { + if currentMapping.HistogramOptions != nil { + return fmt.Errorf("cannot use summary timer and histogram options at the same time") + } + if currentMapping.SummaryOptions == nil { + currentMapping.SummaryOptions = &SummaryOptions{} + } + if currentMapping.LegacyQuantiles != nil && len(currentMapping.LegacyQuantiles) != 0 { + currentMapping.SummaryOptions.Quantiles = currentMapping.LegacyQuantiles + } + if currentMapping.SummaryOptions.Quantiles == nil || len(currentMapping.SummaryOptions.Quantiles) == 0 { + currentMapping.SummaryOptions.Quantiles = n.Defaults.Quantiles + } + } + + if currentMapping.Ttl == 0 && n.Defaults.Ttl > 0 { + currentMapping.Ttl = n.Defaults.Ttl + } + + } + + m.mutex.Lock() + defer m.mutex.Unlock() + + m.Defaults = n.Defaults + m.Mappings = n.Mappings + m.InitCache(cacheSize, options...) + + if n.doFSM { + var mappings []string + for _, mapping := range n.Mappings { + if mapping.MatchType == MatchTypeGlob { + mappings = append(mappings, mapping.Match) + } + } + n.FSM.BacktrackingNeeded = fsm.TestIfNeedBacktracking(mappings, n.FSM.OrderingDisabled) + + m.FSM = n.FSM + m.doRegex = n.doRegex + } + m.doFSM = n.doFSM + + if m.MappingsCount != nil { + m.MappingsCount.Set(float64(len(n.Mappings))) + } + return nil +} + +func (m *MetricMapper) InitFromFile(fileName string, cacheSize int, options ...CacheOption) error { + mappingStr, err := ioutil.ReadFile(fileName) + if err != nil { + return err + } + + return m.InitFromYAMLString(string(mappingStr), cacheSize, options...) +} + +func (m *MetricMapper) InitCache(cacheSize int, options ...CacheOption) { + if cacheSize == 0 { + m.cache = NewMetricMapperNoopCache() + } else { + o := cacheOptions{ + cacheType: "lru", + } + for _, f := range options { + f(&o) + } + + var ( + cache MetricMapperCache + err error + ) + switch o.cacheType { + case "lru": + cache, err = NewMetricMapperCache(cacheSize) + case "random": + cache, err = NewMetricMapperRRCache(cacheSize) + default: + err = fmt.Errorf("unsupported cache type %q", o.cacheType) + } + + if err != nil { + log.Fatalf("Unable to setup metric cache. Caused by: %s", err) + } + m.cache = cache + } +} + +func (m *MetricMapper) GetMapping(statsdMetric string, statsdMetricType MetricType) (*MetricMapping, prometheus.Labels, bool) { + m.mutex.RLock() + defer m.mutex.RUnlock() + result, cached := m.cache.Get(statsdMetric, statsdMetricType) + if cached { + return result.Mapping, result.Labels, result.Matched + } + // glob matching + if m.doFSM { + finalState, captures := m.FSM.GetMapping(statsdMetric, string(statsdMetricType)) + if finalState != nil && finalState.Result != nil { + v := finalState.Result.(*MetricMapping) + result := copyMetricMapping(v) + result.Name = result.nameFormatter.Format(captures) + + labels := prometheus.Labels{} + for index, formatter := range result.labelFormatters { + labels[result.labelKeys[index]] = formatter.Format(captures) + } + + m.cache.AddMatch(statsdMetric, statsdMetricType, result, labels) + + return result, labels, true + } else if !m.doRegex { + // if there's no regex match type, return immediately + m.cache.AddMiss(statsdMetric, statsdMetricType) + return nil, nil, false + } + } + + // regex matching + for _, mapping := range m.Mappings { + // if a rule don't have regex matching type, the regex field is unset + if mapping.regex == nil { + continue + } + matches := mapping.regex.FindStringSubmatchIndex(statsdMetric) + if len(matches) == 0 { + continue + } + + mapping.Name = string(mapping.regex.ExpandString( + []byte{}, + mapping.Name, + statsdMetric, + matches, + )) + + if mt := mapping.MatchMetricType; mt != "" && mt != statsdMetricType { + continue + } + + labels := prometheus.Labels{} + for label, valueExpr := range mapping.Labels { + value := mapping.regex.ExpandString([]byte{}, valueExpr, statsdMetric, matches) + labels[label] = string(value) + } + + m.cache.AddMatch(statsdMetric, statsdMetricType, &mapping, labels) + + return &mapping, labels, true + } + + m.cache.AddMiss(statsdMetric, statsdMetricType) + return nil, nil, false +} + +// make a shallow copy so that we do not overwrite name +// as multiple names can be matched by same mapping +func copyMetricMapping(in *MetricMapping) *MetricMapping { + var out MetricMapping + out = *in + return &out +} diff --git a/vendor/github.com/prometheus/statsd_exporter/pkg/mapper/mapper_cache.go b/vendor/github.com/prometheus/statsd_exporter/pkg/mapper/mapper_cache.go new file mode 100644 index 00000000..5b252a7b --- /dev/null +++ b/vendor/github.com/prometheus/statsd_exporter/pkg/mapper/mapper_cache.go @@ -0,0 +1,198 @@ +// Copyright 2019 The Prometheus Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package mapper + +import ( + "sync" + + lru "github.com/hashicorp/golang-lru" + "github.com/prometheus/client_golang/prometheus" +) + +var ( + cacheLength = prometheus.NewGauge( + prometheus.GaugeOpts{ + Name: "statsd_metric_mapper_cache_length", + Help: "The count of unique metrics currently cached.", + }, + ) + cacheGetsTotal = prometheus.NewCounter( + prometheus.CounterOpts{ + Name: "statsd_metric_mapper_cache_gets_total", + Help: "The count of total metric cache gets.", + }, + ) + cacheHitsTotal = prometheus.NewCounter( + prometheus.CounterOpts{ + Name: "statsd_metric_mapper_cache_hits_total", + Help: "The count of total metric cache hits.", + }, + ) +) + +type cacheOptions struct { + cacheType string +} + +type CacheOption func(*cacheOptions) + +func WithCacheType(cacheType string) CacheOption { + return func(o *cacheOptions) { + o.cacheType = cacheType + } +} + +type MetricMapperCacheResult struct { + Mapping *MetricMapping + Matched bool + Labels prometheus.Labels +} + +type MetricMapperCache interface { + Get(metricString string, metricType MetricType) (*MetricMapperCacheResult, bool) + AddMatch(metricString string, metricType MetricType, mapping *MetricMapping, labels prometheus.Labels) + AddMiss(metricString string, metricType MetricType) +} + +type MetricMapperLRUCache struct { + MetricMapperCache + cache *lru.Cache +} + +type MetricMapperNoopCache struct { + MetricMapperCache +} + +func NewMetricMapperCache(size int) (*MetricMapperLRUCache, error) { + cacheLength.Set(0) + cache, err := lru.New(size) + if err != nil { + return &MetricMapperLRUCache{}, err + } + return &MetricMapperLRUCache{cache: cache}, nil +} + +func (m *MetricMapperLRUCache) Get(metricString string, metricType MetricType) (*MetricMapperCacheResult, bool) { + cacheGetsTotal.Inc() + if result, ok := m.cache.Get(formatKey(metricString, metricType)); ok { + cacheHitsTotal.Inc() + return result.(*MetricMapperCacheResult), true + } else { + return nil, false + } +} + +func (m *MetricMapperLRUCache) AddMatch(metricString string, metricType MetricType, mapping *MetricMapping, labels prometheus.Labels) { + go m.trackCacheLength() + m.cache.Add(formatKey(metricString, metricType), &MetricMapperCacheResult{Mapping: mapping, Matched: true, Labels: labels}) +} + +func (m *MetricMapperLRUCache) AddMiss(metricString string, metricType MetricType) { + go m.trackCacheLength() + m.cache.Add(formatKey(metricString, metricType), &MetricMapperCacheResult{Matched: false}) +} + +func (m *MetricMapperLRUCache) trackCacheLength() { + cacheLength.Set(float64(m.cache.Len())) +} + +func formatKey(metricString string, metricType MetricType) string { + return string(metricType) + "." + metricString +} + +func NewMetricMapperNoopCache() *MetricMapperNoopCache { + cacheLength.Set(0) + return &MetricMapperNoopCache{} +} + +func (m *MetricMapperNoopCache) Get(metricString string, metricType MetricType) (*MetricMapperCacheResult, bool) { + return nil, false +} + +func (m *MetricMapperNoopCache) AddMatch(metricString string, metricType MetricType, mapping *MetricMapping, labels prometheus.Labels) { + return +} + +func (m *MetricMapperNoopCache) AddMiss(metricString string, metricType MetricType) { + return +} + +type MetricMapperRRCache struct { + MetricMapperCache + lock sync.RWMutex + size int + items map[string]*MetricMapperCacheResult +} + +func NewMetricMapperRRCache(size int) (*MetricMapperRRCache, error) { + cacheLength.Set(0) + c := &MetricMapperRRCache{ + items: make(map[string]*MetricMapperCacheResult, size+1), + size: size, + } + return c, nil +} + +func (m *MetricMapperRRCache) Get(metricString string, metricType MetricType) (*MetricMapperCacheResult, bool) { + key := formatKey(metricString, metricType) + + m.lock.RLock() + result, ok := m.items[key] + m.lock.RUnlock() + + return result, ok +} + +func (m *MetricMapperRRCache) addItem(metricString string, metricType MetricType, result *MetricMapperCacheResult) { + go m.trackCacheLength() + + key := formatKey(metricString, metricType) + + m.lock.Lock() + + m.items[key] = result + + // evict an item if needed + if len(m.items) > m.size { + for k := range m.items { + delete(m.items, k) + break + } + } + + m.lock.Unlock() +} + +func (m *MetricMapperRRCache) AddMatch(metricString string, metricType MetricType, mapping *MetricMapping, labels prometheus.Labels) { + e := &MetricMapperCacheResult{Mapping: mapping, Matched: true, Labels: labels} + m.addItem(metricString, metricType, e) +} + +func (m *MetricMapperRRCache) AddMiss(metricString string, metricType MetricType) { + e := &MetricMapperCacheResult{Matched: false} + m.addItem(metricString, metricType, e) +} + +func (m *MetricMapperRRCache) trackCacheLength() { + m.lock.RLock() + length := len(m.items) + m.lock.RUnlock() + cacheLength.Set(float64(length)) +} + +func init() { + prometheus.MustRegister(cacheLength) + prometheus.MustRegister(cacheGetsTotal) + prometheus.MustRegister(cacheHitsTotal) +} diff --git a/vendor/github.com/prometheus/statsd_exporter/pkg/mapper/match.go b/vendor/github.com/prometheus/statsd_exporter/pkg/mapper/match.go new file mode 100644 index 00000000..12d5e8d6 --- /dev/null +++ b/vendor/github.com/prometheus/statsd_exporter/pkg/mapper/match.go @@ -0,0 +1,41 @@ +// Copyright 2013 The Prometheus Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package mapper + +import "fmt" + +type MatchType string + +const ( + MatchTypeGlob MatchType = "glob" + MatchTypeRegex MatchType = "regex" + MatchTypeDefault MatchType = "" +) + +func (t *MatchType) UnmarshalYAML(unmarshal func(interface{}) error) error { + var v string + if err := unmarshal(&v); err != nil { + return err + } + + switch MatchType(v) { + case MatchTypeRegex: + *t = MatchTypeRegex + case MatchTypeGlob, MatchTypeDefault: + *t = MatchTypeGlob + default: + return fmt.Errorf("invalid match type %q", v) + } + return nil +} diff --git a/vendor/github.com/prometheus/statsd_exporter/pkg/mapper/metric_type.go b/vendor/github.com/prometheus/statsd_exporter/pkg/mapper/metric_type.go new file mode 100644 index 00000000..0a0810f8 --- /dev/null +++ b/vendor/github.com/prometheus/statsd_exporter/pkg/mapper/metric_type.go @@ -0,0 +1,43 @@ +// Copyright 2018 The Prometheus Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package mapper + +import "fmt" + +type MetricType string + +const ( + MetricTypeCounter MetricType = "counter" + MetricTypeGauge MetricType = "gauge" + MetricTypeTimer MetricType = "timer" +) + +func (m *MetricType) UnmarshalYAML(unmarshal func(interface{}) error) error { + var v string + if err := unmarshal(&v); err != nil { + return err + } + + switch MetricType(v) { + case MetricTypeCounter: + *m = MetricTypeCounter + case MetricTypeGauge: + *m = MetricTypeGauge + case MetricTypeTimer: + *m = MetricTypeTimer + default: + return fmt.Errorf("invalid metric type '%s'", v) + } + return nil +} diff --git a/vendor/github.com/prometheus/statsd_exporter/pkg/mapper/timer.go b/vendor/github.com/prometheus/statsd_exporter/pkg/mapper/timer.go new file mode 100644 index 00000000..f1d2fb70 --- /dev/null +++ b/vendor/github.com/prometheus/statsd_exporter/pkg/mapper/timer.go @@ -0,0 +1,41 @@ +// Copyright 2013 The Prometheus Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package mapper + +import "fmt" + +type TimerType string + +const ( + TimerTypeHistogram TimerType = "histogram" + TimerTypeSummary TimerType = "summary" + TimerTypeDefault TimerType = "" +) + +func (t *TimerType) UnmarshalYAML(unmarshal func(interface{}) error) error { + var v string + if err := unmarshal(&v); err != nil { + return err + } + + switch TimerType(v) { + case TimerTypeHistogram: + *t = TimerTypeHistogram + case TimerTypeSummary, TimerTypeDefault: + *t = TimerTypeSummary + default: + return fmt.Errorf("invalid timer type '%s'", v) + } + return nil +} diff --git a/vendor/github.com/sirupsen/logrus/.gitignore b/vendor/github.com/sirupsen/logrus/.gitignore new file mode 100644 index 00000000..6b7d7d1e --- /dev/null +++ b/vendor/github.com/sirupsen/logrus/.gitignore @@ -0,0 +1,2 @@ +logrus +vendor diff --git a/vendor/github.com/sirupsen/logrus/.golangci.yml b/vendor/github.com/sirupsen/logrus/.golangci.yml new file mode 100644 index 00000000..65dc2850 --- /dev/null +++ b/vendor/github.com/sirupsen/logrus/.golangci.yml @@ -0,0 +1,40 @@ +run: + # do not run on test files yet + tests: false + +# all available settings of specific linters +linters-settings: + errcheck: + # report about not checking of errors in type assetions: `a := b.(MyStruct)`; + # default is false: such cases aren't reported by default. + check-type-assertions: false + + # report about assignment of errors to blank identifier: `num, _ := strconv.Atoi(numStr)`; + # default is false: such cases aren't reported by default. + check-blank: false + + lll: + line-length: 100 + tab-width: 4 + + prealloc: + simple: false + range-loops: false + for-loops: false + + whitespace: + multi-if: false # Enforces newlines (or comments) after every multi-line if statement + multi-func: false # Enforces newlines (or comments) after every multi-line function signature + +linters: + enable: + - megacheck + - govet + disable: + - maligned + - prealloc + disable-all: false + presets: + - bugs + - unused + fast: false diff --git a/vendor/github.com/sirupsen/logrus/.travis.yml b/vendor/github.com/sirupsen/logrus/.travis.yml new file mode 100644 index 00000000..5e20aa41 --- /dev/null +++ b/vendor/github.com/sirupsen/logrus/.travis.yml @@ -0,0 +1,17 @@ +language: go +go_import_path: github.com/sirupsen/logrus +git: + depth: 1 +env: + - GO111MODULE=on +go: [1.13.x, 1.14.x] +os: [linux, osx] +install: + - ./travis/install.sh +script: + - ./travis/cross_build.sh + - ./travis/lint.sh + - export GOMAXPROCS=4 + - export GORACE=halt_on_error=1 + - go test -race -v ./... + - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then go test -race -v -tags appengine ./... ; fi diff --git a/vendor/github.com/sirupsen/logrus/CHANGELOG.md b/vendor/github.com/sirupsen/logrus/CHANGELOG.md new file mode 100644 index 00000000..584026d6 --- /dev/null +++ b/vendor/github.com/sirupsen/logrus/CHANGELOG.md @@ -0,0 +1,223 @@ +# 1.6.0 +Fixes: + * end of line cleanup + * revert the entry concurrency bug fix whic leads to deadlock under some circumstances + * update dependency on go-windows-terminal-sequences to fix a crash with go 1.14 + +Features: + * add an option to the `TextFormatter` to completely disable fields quoting + +# 1.5.0 +Code quality: + * add golangci linter run on travis + +Fixes: + * add mutex for hooks concurrent access on `Entry` data + * caller function field for go1.14 + * fix build issue for gopherjs target + +Feature: + * add an hooks/writer sub-package whose goal is to split output on different stream depending on the trace level + * add a `DisableHTMLEscape` option in the `JSONFormatter` + * add `ForceQuote` and `PadLevelText` options in the `TextFormatter` + +# 1.4.2 + * Fixes build break for plan9, nacl, solaris +# 1.4.1 +This new release introduces: + * Enhance TextFormatter to not print caller information when they are empty (#944) + * Remove dependency on golang.org/x/crypto (#932, #943) + +Fixes: + * Fix Entry.WithContext method to return a copy of the initial entry (#941) + +# 1.4.0 +This new release introduces: + * Add `DeferExitHandler`, similar to `RegisterExitHandler` but prepending the handler to the list of handlers (semantically like `defer`) (#848). + * Add `CallerPrettyfier` to `JSONFormatter` and `TextFormatter` (#909, #911) + * Add `Entry.WithContext()` and `Entry.Context`, to set a context on entries to be used e.g. in hooks (#919). + +Fixes: + * Fix wrong method calls `Logger.Print` and `Logger.Warningln` (#893). + * Update `Entry.Logf` to not do string formatting unless the log level is enabled (#903) + * Fix infinite recursion on unknown `Level.String()` (#907) + * Fix race condition in `getCaller` (#916). + + +# 1.3.0 +This new release introduces: + * Log, Logf, Logln functions for Logger and Entry that take a Level + +Fixes: + * Building prometheus node_exporter on AIX (#840) + * Race condition in TextFormatter (#468) + * Travis CI import path (#868) + * Remove coloured output on Windows (#862) + * Pointer to func as field in JSONFormatter (#870) + * Properly marshal Levels (#873) + +# 1.2.0 +This new release introduces: + * A new method `SetReportCaller` in the `Logger` to enable the file, line and calling function from which the trace has been issued + * A new trace level named `Trace` whose level is below `Debug` + * A configurable exit function to be called upon a Fatal trace + * The `Level` object now implements `encoding.TextUnmarshaler` interface + +# 1.1.1 +This is a bug fix release. + * fix the build break on Solaris + * don't drop a whole trace in JSONFormatter when a field param is a function pointer which can not be serialized + +# 1.1.0 +This new release introduces: + * several fixes: + * a fix for a race condition on entry formatting + * proper cleanup of previously used entries before putting them back in the pool + * the extra new line at the end of message in text formatter has been removed + * a new global public API to check if a level is activated: IsLevelEnabled + * the following methods have been added to the Logger object + * IsLevelEnabled + * SetFormatter + * SetOutput + * ReplaceHooks + * introduction of go module + * an indent configuration for the json formatter + * output colour support for windows + * the field sort function is now configurable for text formatter + * the CLICOLOR and CLICOLOR\_FORCE environment variable support in text formater + +# 1.0.6 + +This new release introduces: + * a new api WithTime which allows to easily force the time of the log entry + which is mostly useful for logger wrapper + * a fix reverting the immutability of the entry given as parameter to the hooks + a new configuration field of the json formatter in order to put all the fields + in a nested dictionnary + * a new SetOutput method in the Logger + * a new configuration of the textformatter to configure the name of the default keys + * a new configuration of the text formatter to disable the level truncation + +# 1.0.5 + +* Fix hooks race (#707) +* Fix panic deadlock (#695) + +# 1.0.4 + +* Fix race when adding hooks (#612) +* Fix terminal check in AppEngine (#635) + +# 1.0.3 + +* Replace example files with testable examples + +# 1.0.2 + +* bug: quote non-string values in text formatter (#583) +* Make (*Logger) SetLevel a public method + +# 1.0.1 + +* bug: fix escaping in text formatter (#575) + +# 1.0.0 + +* Officially changed name to lower-case +* bug: colors on Windows 10 (#541) +* bug: fix race in accessing level (#512) + +# 0.11.5 + +* feature: add writer and writerlevel to entry (#372) + +# 0.11.4 + +* bug: fix undefined variable on solaris (#493) + +# 0.11.3 + +* formatter: configure quoting of empty values (#484) +* formatter: configure quoting character (default is `"`) (#484) +* bug: fix not importing io correctly in non-linux environments (#481) + +# 0.11.2 + +* bug: fix windows terminal detection (#476) + +# 0.11.1 + +* bug: fix tty detection with custom out (#471) + +# 0.11.0 + +* performance: Use bufferpool to allocate (#370) +* terminal: terminal detection for app-engine (#343) +* feature: exit handler (#375) + +# 0.10.0 + +* feature: Add a test hook (#180) +* feature: `ParseLevel` is now case-insensitive (#326) +* feature: `FieldLogger` interface that generalizes `Logger` and `Entry` (#308) +* performance: avoid re-allocations on `WithFields` (#335) + +# 0.9.0 + +* logrus/text_formatter: don't emit empty msg +* logrus/hooks/airbrake: move out of main repository +* logrus/hooks/sentry: move out of main repository +* logrus/hooks/papertrail: move out of main repository +* logrus/hooks/bugsnag: move out of main repository +* logrus/core: run tests with `-race` +* logrus/core: detect TTY based on `stderr` +* logrus/core: support `WithError` on logger +* logrus/core: Solaris support + +# 0.8.7 + +* logrus/core: fix possible race (#216) +* logrus/doc: small typo fixes and doc improvements + + +# 0.8.6 + +* hooks/raven: allow passing an initialized client + +# 0.8.5 + +* logrus/core: revert #208 + +# 0.8.4 + +* formatter/text: fix data race (#218) + +# 0.8.3 + +* logrus/core: fix entry log level (#208) +* logrus/core: improve performance of text formatter by 40% +* logrus/core: expose `LevelHooks` type +* logrus/core: add support for DragonflyBSD and NetBSD +* formatter/text: print structs more verbosely + +# 0.8.2 + +* logrus: fix more Fatal family functions + +# 0.8.1 + +* logrus: fix not exiting on `Fatalf` and `Fatalln` + +# 0.8.0 + +* logrus: defaults to stderr instead of stdout +* hooks/sentry: add special field for `*http.Request` +* formatter/text: ignore Windows for colors + +# 0.7.3 + +* formatter/\*: allow configuration of timestamp layout + +# 0.7.2 + +* formatter/text: Add configuration option for time format (#158) diff --git a/vendor/github.com/sirupsen/logrus/LICENSE b/vendor/github.com/sirupsen/logrus/LICENSE new file mode 100644 index 00000000..f090cb42 --- /dev/null +++ b/vendor/github.com/sirupsen/logrus/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2014 Simon Eskildsen + +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. diff --git a/vendor/github.com/sirupsen/logrus/README.md b/vendor/github.com/sirupsen/logrus/README.md new file mode 100644 index 00000000..5796706d --- /dev/null +++ b/vendor/github.com/sirupsen/logrus/README.md @@ -0,0 +1,513 @@ +# Logrus :walrus: [![Build Status](https://travis-ci.org/sirupsen/logrus.svg?branch=master)](https://travis-ci.org/sirupsen/logrus) [![GoDoc](https://godoc.org/github.com/sirupsen/logrus?status.svg)](https://godoc.org/github.com/sirupsen/logrus) + +Logrus is a structured logger for Go (golang), completely API compatible with +the standard library logger. + +**Logrus is in maintenance-mode.** We will not be introducing new features. It's +simply too hard to do in a way that won't break many people's projects, which is +the last thing you want from your Logging library (again...). + +This does not mean Logrus is dead. Logrus will continue to be maintained for +security, (backwards compatible) bug fixes, and performance (where we are +limited by the interface). + +I believe Logrus' biggest contribution is to have played a part in today's +widespread use of structured logging in Golang. There doesn't seem to be a +reason to do a major, breaking iteration into Logrus V2, since the fantastic Go +community has built those independently. Many fantastic alternatives have sprung +up. Logrus would look like those, had it been re-designed with what we know +about structured logging in Go today. Check out, for example, +[Zerolog][zerolog], [Zap][zap], and [Apex][apex]. + +[zerolog]: https://github.com/rs/zerolog +[zap]: https://github.com/uber-go/zap +[apex]: https://github.com/apex/log + +**Seeing weird case-sensitive problems?** It's in the past been possible to +import Logrus as both upper- and lower-case. Due to the Go package environment, +this caused issues in the community and we needed a standard. Some environments +experienced problems with the upper-case variant, so the lower-case was decided. +Everything using `logrus` will need to use the lower-case: +`github.com/sirupsen/logrus`. Any package that isn't, should be changed. + +To fix Glide, see [these +comments](https://github.com/sirupsen/logrus/issues/553#issuecomment-306591437). +For an in-depth explanation of the casing issue, see [this +comment](https://github.com/sirupsen/logrus/issues/570#issuecomment-313933276). + +Nicely color-coded in development (when a TTY is attached, otherwise just +plain text): + +![Colored](http://i.imgur.com/PY7qMwd.png) + +With `log.SetFormatter(&log.JSONFormatter{})`, for easy parsing by logstash +or Splunk: + +```json +{"animal":"walrus","level":"info","msg":"A group of walrus emerges from the +ocean","size":10,"time":"2014-03-10 19:57:38.562264131 -0400 EDT"} + +{"level":"warning","msg":"The group's number increased tremendously!", +"number":122,"omg":true,"time":"2014-03-10 19:57:38.562471297 -0400 EDT"} + +{"animal":"walrus","level":"info","msg":"A giant walrus appears!", +"size":10,"time":"2014-03-10 19:57:38.562500591 -0400 EDT"} + +{"animal":"walrus","level":"info","msg":"Tremendously sized cow enters the ocean.", +"size":9,"time":"2014-03-10 19:57:38.562527896 -0400 EDT"} + +{"level":"fatal","msg":"The ice breaks!","number":100,"omg":true, +"time":"2014-03-10 19:57:38.562543128 -0400 EDT"} +``` + +With the default `log.SetFormatter(&log.TextFormatter{})` when a TTY is not +attached, the output is compatible with the +[logfmt](http://godoc.org/github.com/kr/logfmt) format: + +```text +time="2015-03-26T01:27:38-04:00" level=debug msg="Started observing beach" animal=walrus number=8 +time="2015-03-26T01:27:38-04:00" level=info msg="A group of walrus emerges from the ocean" animal=walrus size=10 +time="2015-03-26T01:27:38-04:00" level=warning msg="The group's number increased tremendously!" number=122 omg=true +time="2015-03-26T01:27:38-04:00" level=debug msg="Temperature changes" temperature=-4 +time="2015-03-26T01:27:38-04:00" level=panic msg="It's over 9000!" animal=orca size=9009 +time="2015-03-26T01:27:38-04:00" level=fatal msg="The ice breaks!" err=&{0x2082280c0 map[animal:orca size:9009] 2015-03-26 01:27:38.441574009 -0400 EDT panic It's over 9000!} number=100 omg=true +``` +To ensure this behaviour even if a TTY is attached, set your formatter as follows: + +```go + log.SetFormatter(&log.TextFormatter{ + DisableColors: true, + FullTimestamp: true, + }) +``` + +#### Logging Method Name + +If you wish to add the calling method as a field, instruct the logger via: +```go +log.SetReportCaller(true) +``` +This adds the caller as 'method' like so: + +```json +{"animal":"penguin","level":"fatal","method":"github.com/sirupsen/arcticcreatures.migrate","msg":"a penguin swims by", +"time":"2014-03-10 19:57:38.562543129 -0400 EDT"} +``` + +```text +time="2015-03-26T01:27:38-04:00" level=fatal method=github.com/sirupsen/arcticcreatures.migrate msg="a penguin swims by" animal=penguin +``` +Note that this does add measurable overhead - the cost will depend on the version of Go, but is +between 20 and 40% in recent tests with 1.6 and 1.7. You can validate this in your +environment via benchmarks: +``` +go test -bench=.*CallerTracing +``` + + +#### Case-sensitivity + +The organization's name was changed to lower-case--and this will not be changed +back. If you are getting import conflicts due to case sensitivity, please use +the lower-case import: `github.com/sirupsen/logrus`. + +#### Example + +The simplest way to use Logrus is simply the package-level exported logger: + +```go +package main + +import ( + log "github.com/sirupsen/logrus" +) + +func main() { + log.WithFields(log.Fields{ + "animal": "walrus", + }).Info("A walrus appears") +} +``` + +Note that it's completely api-compatible with the stdlib logger, so you can +replace your `log` imports everywhere with `log "github.com/sirupsen/logrus"` +and you'll now have the flexibility of Logrus. You can customize it all you +want: + +```go +package main + +import ( + "os" + log "github.com/sirupsen/logrus" +) + +func init() { + // Log as JSON instead of the default ASCII formatter. + log.SetFormatter(&log.JSONFormatter{}) + + // Output to stdout instead of the default stderr + // Can be any io.Writer, see below for File example + log.SetOutput(os.Stdout) + + // Only log the warning severity or above. + log.SetLevel(log.WarnLevel) +} + +func main() { + log.WithFields(log.Fields{ + "animal": "walrus", + "size": 10, + }).Info("A group of walrus emerges from the ocean") + + log.WithFields(log.Fields{ + "omg": true, + "number": 122, + }).Warn("The group's number increased tremendously!") + + log.WithFields(log.Fields{ + "omg": true, + "number": 100, + }).Fatal("The ice breaks!") + + // A common pattern is to re-use fields between logging statements by re-using + // the logrus.Entry returned from WithFields() + contextLogger := log.WithFields(log.Fields{ + "common": "this is a common field", + "other": "I also should be logged always", + }) + + contextLogger.Info("I'll be logged with common and other field") + contextLogger.Info("Me too") +} +``` + +For more advanced usage such as logging to multiple locations from the same +application, you can also create an instance of the `logrus` Logger: + +```go +package main + +import ( + "os" + "github.com/sirupsen/logrus" +) + +// Create a new instance of the logger. You can have any number of instances. +var log = logrus.New() + +func main() { + // The API for setting attributes is a little different than the package level + // exported logger. See Godoc. + log.Out = os.Stdout + + // You could set this to any `io.Writer` such as a file + // file, err := os.OpenFile("logrus.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666) + // if err == nil { + // log.Out = file + // } else { + // log.Info("Failed to log to file, using default stderr") + // } + + log.WithFields(logrus.Fields{ + "animal": "walrus", + "size": 10, + }).Info("A group of walrus emerges from the ocean") +} +``` + +#### Fields + +Logrus encourages careful, structured logging through logging fields instead of +long, unparseable error messages. For example, instead of: `log.Fatalf("Failed +to send event %s to topic %s with key %d")`, you should log the much more +discoverable: + +```go +log.WithFields(log.Fields{ + "event": event, + "topic": topic, + "key": key, +}).Fatal("Failed to send event") +``` + +We've found this API forces you to think about logging in a way that produces +much more useful logging messages. We've been in countless situations where just +a single added field to a log statement that was already there would've saved us +hours. The `WithFields` call is optional. + +In general, with Logrus using any of the `printf`-family functions should be +seen as a hint you should add a field, however, you can still use the +`printf`-family functions with Logrus. + +#### Default Fields + +Often it's helpful to have fields _always_ attached to log statements in an +application or parts of one. For example, you may want to always log the +`request_id` and `user_ip` in the context of a request. Instead of writing +`log.WithFields(log.Fields{"request_id": request_id, "user_ip": user_ip})` on +every line, you can create a `logrus.Entry` to pass around instead: + +```go +requestLogger := log.WithFields(log.Fields{"request_id": request_id, "user_ip": user_ip}) +requestLogger.Info("something happened on that request") # will log request_id and user_ip +requestLogger.Warn("something not great happened") +``` + +#### Hooks + +You can add hooks for logging levels. For example to send errors to an exception +tracking service on `Error`, `Fatal` and `Panic`, info to StatsD or log to +multiple places simultaneously, e.g. syslog. + +Logrus comes with [built-in hooks](hooks/). Add those, or your custom hook, in +`init`: + +```go +import ( + log "github.com/sirupsen/logrus" + "gopkg.in/gemnasium/logrus-airbrake-hook.v2" // the package is named "airbrake" + logrus_syslog "github.com/sirupsen/logrus/hooks/syslog" + "log/syslog" +) + +func init() { + + // Use the Airbrake hook to report errors that have Error severity or above to + // an exception tracker. You can create custom hooks, see the Hooks section. + log.AddHook(airbrake.NewHook(123, "xyz", "production")) + + hook, err := logrus_syslog.NewSyslogHook("udp", "localhost:514", syslog.LOG_INFO, "") + if err != nil { + log.Error("Unable to connect to local syslog daemon") + } else { + log.AddHook(hook) + } +} +``` +Note: Syslog hook also support connecting to local syslog (Ex. "/dev/log" or "/var/run/syslog" or "/var/run/log"). For the detail, please check the [syslog hook README](hooks/syslog/README.md). + +A list of currently known service hooks can be found in this wiki [page](https://github.com/sirupsen/logrus/wiki/Hooks) + + +#### Level logging + +Logrus has seven logging levels: Trace, Debug, Info, Warning, Error, Fatal and Panic. + +```go +log.Trace("Something very low level.") +log.Debug("Useful debugging information.") +log.Info("Something noteworthy happened!") +log.Warn("You should probably take a look at this.") +log.Error("Something failed but I'm not quitting.") +// Calls os.Exit(1) after logging +log.Fatal("Bye.") +// Calls panic() after logging +log.Panic("I'm bailing.") +``` + +You can set the logging level on a `Logger`, then it will only log entries with +that severity or anything above it: + +```go +// Will log anything that is info or above (warn, error, fatal, panic). Default. +log.SetLevel(log.InfoLevel) +``` + +It may be useful to set `log.Level = logrus.DebugLevel` in a debug or verbose +environment if your application has that. + +#### Entries + +Besides the fields added with `WithField` or `WithFields` some fields are +automatically added to all logging events: + +1. `time`. The timestamp when the entry was created. +2. `msg`. The logging message passed to `{Info,Warn,Error,Fatal,Panic}` after + the `AddFields` call. E.g. `Failed to send event.` +3. `level`. The logging level. E.g. `info`. + +#### Environments + +Logrus has no notion of environment. + +If you wish for hooks and formatters to only be used in specific environments, +you should handle that yourself. For example, if your application has a global +variable `Environment`, which is a string representation of the environment you +could do: + +```go +import ( + log "github.com/sirupsen/logrus" +) + +init() { + // do something here to set environment depending on an environment variable + // or command-line flag + if Environment == "production" { + log.SetFormatter(&log.JSONFormatter{}) + } else { + // The TextFormatter is default, you don't actually have to do this. + log.SetFormatter(&log.TextFormatter{}) + } +} +``` + +This configuration is how `logrus` was intended to be used, but JSON in +production is mostly only useful if you do log aggregation with tools like +Splunk or Logstash. + +#### Formatters + +The built-in logging formatters are: + +* `logrus.TextFormatter`. Logs the event in colors if stdout is a tty, otherwise + without colors. + * *Note:* to force colored output when there is no TTY, set the `ForceColors` + field to `true`. To force no colored output even if there is a TTY set the + `DisableColors` field to `true`. For Windows, see + [github.com/mattn/go-colorable](https://github.com/mattn/go-colorable). + * When colors are enabled, levels are truncated to 4 characters by default. To disable + truncation set the `DisableLevelTruncation` field to `true`. + * When outputting to a TTY, it's often helpful to visually scan down a column where all the levels are the same width. Setting the `PadLevelText` field to `true` enables this behavior, by adding padding to the level text. + * All options are listed in the [generated docs](https://godoc.org/github.com/sirupsen/logrus#TextFormatter). +* `logrus.JSONFormatter`. Logs fields as JSON. + * All options are listed in the [generated docs](https://godoc.org/github.com/sirupsen/logrus#JSONFormatter). + +Third party logging formatters: + +* [`FluentdFormatter`](https://github.com/joonix/log). Formats entries that can be parsed by Kubernetes and Google Container Engine. +* [`GELF`](https://github.com/fabienm/go-logrus-formatters). Formats entries so they comply to Graylog's [GELF 1.1 specification](http://docs.graylog.org/en/2.4/pages/gelf.html). +* [`logstash`](https://github.com/bshuster-repo/logrus-logstash-hook). Logs fields as [Logstash](http://logstash.net) Events. +* [`prefixed`](https://github.com/x-cray/logrus-prefixed-formatter). Displays log entry source along with alternative layout. +* [`zalgo`](https://github.com/aybabtme/logzalgo). Invoking the Power of Zalgo. +* [`nested-logrus-formatter`](https://github.com/antonfisher/nested-logrus-formatter). Converts logrus fields to a nested structure. +* [`powerful-logrus-formatter`](https://github.com/zput/zxcTool). get fileName, log's line number and the latest function's name when print log; Sava log to files. +* [`caption-json-formatter`](https://github.com/nolleh/caption_json_formatter). logrus's message json formatter with human-readable caption added. + +You can define your formatter by implementing the `Formatter` interface, +requiring a `Format` method. `Format` takes an `*Entry`. `entry.Data` is a +`Fields` type (`map[string]interface{}`) with all your fields as well as the +default ones (see Entries section above): + +```go +type MyJSONFormatter struct { +} + +log.SetFormatter(new(MyJSONFormatter)) + +func (f *MyJSONFormatter) Format(entry *Entry) ([]byte, error) { + // Note this doesn't include Time, Level and Message which are available on + // the Entry. Consult `godoc` on information about those fields or read the + // source of the official loggers. + serialized, err := json.Marshal(entry.Data) + if err != nil { + return nil, fmt.Errorf("Failed to marshal fields to JSON, %v", err) + } + return append(serialized, '\n'), nil +} +``` + +#### Logger as an `io.Writer` + +Logrus can be transformed into an `io.Writer`. That writer is the end of an `io.Pipe` and it is your responsibility to close it. + +```go +w := logger.Writer() +defer w.Close() + +srv := http.Server{ + // create a stdlib log.Logger that writes to + // logrus.Logger. + ErrorLog: log.New(w, "", 0), +} +``` + +Each line written to that writer will be printed the usual way, using formatters +and hooks. The level for those entries is `info`. + +This means that we can override the standard library logger easily: + +```go +logger := logrus.New() +logger.Formatter = &logrus.JSONFormatter{} + +// Use logrus for standard log output +// Note that `log` here references stdlib's log +// Not logrus imported under the name `log`. +log.SetOutput(logger.Writer()) +``` + +#### Rotation + +Log rotation is not provided with Logrus. Log rotation should be done by an +external program (like `logrotate(8)`) that can compress and delete old log +entries. It should not be a feature of the application-level logger. + +#### Tools + +| Tool | Description | +| ---- | ----------- | +|[Logrus Mate](https://github.com/gogap/logrus_mate)|Logrus mate is a tool for Logrus to manage loggers, you can initial logger's level, hook and formatter by config file, the logger will be generated with different configs in different environments.| +|[Logrus Viper Helper](https://github.com/heirko/go-contrib/tree/master/logrusHelper)|An Helper around Logrus to wrap with spf13/Viper to load configuration with fangs! And to simplify Logrus configuration use some behavior of [Logrus Mate](https://github.com/gogap/logrus_mate). [sample](https://github.com/heirko/iris-contrib/blob/master/middleware/logrus-logger/example) | + +#### Testing + +Logrus has a built in facility for asserting the presence of log messages. This is implemented through the `test` hook and provides: + +* decorators for existing logger (`test.NewLocal` and `test.NewGlobal`) which basically just adds the `test` hook +* a test logger (`test.NewNullLogger`) that just records log messages (and does not output any): + +```go +import( + "github.com/sirupsen/logrus" + "github.com/sirupsen/logrus/hooks/test" + "github.com/stretchr/testify/assert" + "testing" +) + +func TestSomething(t*testing.T){ + logger, hook := test.NewNullLogger() + logger.Error("Helloerror") + + assert.Equal(t, 1, len(hook.Entries)) + assert.Equal(t, logrus.ErrorLevel, hook.LastEntry().Level) + assert.Equal(t, "Helloerror", hook.LastEntry().Message) + + hook.Reset() + assert.Nil(t, hook.LastEntry()) +} +``` + +#### Fatal handlers + +Logrus can register one or more functions that will be called when any `fatal` +level message is logged. The registered handlers will be executed before +logrus performs an `os.Exit(1)`. This behavior may be helpful if callers need +to gracefully shutdown. Unlike a `panic("Something went wrong...")` call which can be intercepted with a deferred `recover` a call to `os.Exit(1)` can not be intercepted. + +``` +... +handler := func() { + // gracefully shutdown something... +} +logrus.RegisterExitHandler(handler) +... +``` + +#### Thread safety + +By default, Logger is protected by a mutex for concurrent writes. The mutex is held when calling hooks and writing logs. +If you are sure such locking is not needed, you can call logger.SetNoLock() to disable the locking. + +Situation when locking is not needed includes: + +* You have no hooks registered, or hooks calling is already thread-safe. + +* Writing to logger.Out is already thread-safe, for example: + + 1) logger.Out is protected by locks. + + 2) logger.Out is an os.File handler opened with `O_APPEND` flag, and every write is smaller than 4k. (This allows multi-thread/multi-process writing) + + (Refer to http://www.notthewizard.com/2014/06/17/are-files-appends-really-atomic/) diff --git a/vendor/github.com/sirupsen/logrus/alt_exit.go b/vendor/github.com/sirupsen/logrus/alt_exit.go new file mode 100644 index 00000000..8fd189e1 --- /dev/null +++ b/vendor/github.com/sirupsen/logrus/alt_exit.go @@ -0,0 +1,76 @@ +package logrus + +// The following code was sourced and modified from the +// https://github.com/tebeka/atexit package governed by the following license: +// +// Copyright (c) 2012 Miki Tebeka . +// +// 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 ( + "fmt" + "os" +) + +var handlers = []func(){} + +func runHandler(handler func()) { + defer func() { + if err := recover(); err != nil { + fmt.Fprintln(os.Stderr, "Error: Logrus exit handler error:", err) + } + }() + + handler() +} + +func runHandlers() { + for _, handler := range handlers { + runHandler(handler) + } +} + +// Exit runs all the Logrus atexit handlers and then terminates the program using os.Exit(code) +func Exit(code int) { + runHandlers() + os.Exit(code) +} + +// RegisterExitHandler appends a Logrus Exit handler to the list of handlers, +// call logrus.Exit to invoke all handlers. The handlers will also be invoked when +// any Fatal log entry is made. +// +// This method is useful when a caller wishes to use logrus to log a fatal +// message but also needs to gracefully shutdown. An example usecase could be +// closing database connections, or sending a alert that the application is +// closing. +func RegisterExitHandler(handler func()) { + handlers = append(handlers, handler) +} + +// DeferExitHandler prepends a Logrus Exit handler to the list of handlers, +// call logrus.Exit to invoke all handlers. The handlers will also be invoked when +// any Fatal log entry is made. +// +// This method is useful when a caller wishes to use logrus to log a fatal +// message but also needs to gracefully shutdown. An example usecase could be +// closing database connections, or sending a alert that the application is +// closing. +func DeferExitHandler(handler func()) { + handlers = append([]func(){handler}, handlers...) +} diff --git a/vendor/github.com/sirupsen/logrus/appveyor.yml b/vendor/github.com/sirupsen/logrus/appveyor.yml new file mode 100644 index 00000000..df9d65c3 --- /dev/null +++ b/vendor/github.com/sirupsen/logrus/appveyor.yml @@ -0,0 +1,14 @@ +version: "{build}" +platform: x64 +clone_folder: c:\gopath\src\github.com\sirupsen\logrus +environment: + GOPATH: c:\gopath +branches: + only: + - master +install: + - set PATH=%GOPATH%\bin;c:\go\bin;%PATH% + - go version +build_script: + - go get -t + - go test diff --git a/vendor/github.com/sirupsen/logrus/doc.go b/vendor/github.com/sirupsen/logrus/doc.go new file mode 100644 index 00000000..da67aba0 --- /dev/null +++ b/vendor/github.com/sirupsen/logrus/doc.go @@ -0,0 +1,26 @@ +/* +Package logrus is a structured logger for Go, completely API compatible with the standard library logger. + + +The simplest way to use Logrus is simply the package-level exported logger: + + package main + + import ( + log "github.com/sirupsen/logrus" + ) + + func main() { + log.WithFields(log.Fields{ + "animal": "walrus", + "number": 1, + "size": 10, + }).Info("A walrus appears") + } + +Output: + time="2015-09-07T08:48:33Z" level=info msg="A walrus appears" animal=walrus number=1 size=10 + +For a full guide visit https://github.com/sirupsen/logrus +*/ +package logrus diff --git a/vendor/github.com/sirupsen/logrus/entry.go b/vendor/github.com/sirupsen/logrus/entry.go new file mode 100644 index 00000000..f6e062a3 --- /dev/null +++ b/vendor/github.com/sirupsen/logrus/entry.go @@ -0,0 +1,426 @@ +package logrus + +import ( + "bytes" + "context" + "fmt" + "os" + "reflect" + "runtime" + "strings" + "sync" + "time" +) + +var ( + bufferPool *sync.Pool + + // qualified package name, cached at first use + logrusPackage string + + // Positions in the call stack when tracing to report the calling method + minimumCallerDepth int + + // Used for caller information initialisation + callerInitOnce sync.Once +) + +const ( + maximumCallerDepth int = 25 + knownLogrusFrames int = 4 +) + +func init() { + bufferPool = &sync.Pool{ + New: func() interface{} { + return new(bytes.Buffer) + }, + } + + // start at the bottom of the stack before the package-name cache is primed + minimumCallerDepth = 1 +} + +// Defines the key when adding errors using WithError. +var ErrorKey = "error" + +// An entry is the final or intermediate Logrus logging entry. It contains all +// the fields passed with WithField{,s}. It's finally logged when Trace, Debug, +// Info, Warn, Error, Fatal or Panic is called on it. These objects can be +// reused and passed around as much as you wish to avoid field duplication. +type Entry struct { + Logger *Logger + + // Contains all the fields set by the user. + Data Fields + + // Time at which the log entry was created + Time time.Time + + // Level the log entry was logged at: Trace, Debug, Info, Warn, Error, Fatal or Panic + // This field will be set on entry firing and the value will be equal to the one in Logger struct field. + Level Level + + // Calling method, with package name + Caller *runtime.Frame + + // Message passed to Trace, Debug, Info, Warn, Error, Fatal or Panic + Message string + + // When formatter is called in entry.log(), a Buffer may be set to entry + Buffer *bytes.Buffer + + // Contains the context set by the user. Useful for hook processing etc. + Context context.Context + + // err may contain a field formatting error + err string +} + +func NewEntry(logger *Logger) *Entry { + return &Entry{ + Logger: logger, + // Default is three fields, plus one optional. Give a little extra room. + Data: make(Fields, 6), + } +} + +// Returns the bytes representation of this entry from the formatter. +func (entry *Entry) Bytes() ([]byte, error) { + return entry.Logger.Formatter.Format(entry) +} + +// Returns the string representation from the reader and ultimately the +// formatter. +func (entry *Entry) String() (string, error) { + serialized, err := entry.Bytes() + if err != nil { + return "", err + } + str := string(serialized) + return str, nil +} + +// Add an error as single field (using the key defined in ErrorKey) to the Entry. +func (entry *Entry) WithError(err error) *Entry { + return entry.WithField(ErrorKey, err) +} + +// Add a context to the Entry. +func (entry *Entry) WithContext(ctx context.Context) *Entry { + dataCopy := make(Fields, len(entry.Data)) + for k, v := range entry.Data { + dataCopy[k] = v + } + return &Entry{Logger: entry.Logger, Data: dataCopy, Time: entry.Time, err: entry.err, Context: ctx} +} + +// Add a single field to the Entry. +func (entry *Entry) WithField(key string, value interface{}) *Entry { + return entry.WithFields(Fields{key: value}) +} + +// Add a map of fields to the Entry. +func (entry *Entry) WithFields(fields Fields) *Entry { + data := make(Fields, len(entry.Data)+len(fields)) + for k, v := range entry.Data { + data[k] = v + } + fieldErr := entry.err + for k, v := range fields { + isErrField := false + if t := reflect.TypeOf(v); t != nil { + switch t.Kind() { + case reflect.Func: + isErrField = true + case reflect.Ptr: + isErrField = t.Elem().Kind() == reflect.Func + } + } + if isErrField { + tmp := fmt.Sprintf("can not add field %q", k) + if fieldErr != "" { + fieldErr = entry.err + ", " + tmp + } else { + fieldErr = tmp + } + } else { + data[k] = v + } + } + return &Entry{Logger: entry.Logger, Data: data, Time: entry.Time, err: fieldErr, Context: entry.Context} +} + +// Overrides the time of the Entry. +func (entry *Entry) WithTime(t time.Time) *Entry { + dataCopy := make(Fields, len(entry.Data)) + for k, v := range entry.Data { + dataCopy[k] = v + } + return &Entry{Logger: entry.Logger, Data: dataCopy, Time: t, err: entry.err, Context: entry.Context} +} + +// getPackageName reduces a fully qualified function name to the package name +// There really ought to be to be a better way... +func getPackageName(f string) string { + for { + lastPeriod := strings.LastIndex(f, ".") + lastSlash := strings.LastIndex(f, "/") + if lastPeriod > lastSlash { + f = f[:lastPeriod] + } else { + break + } + } + + return f +} + +// getCaller retrieves the name of the first non-logrus calling function +func getCaller() *runtime.Frame { + // cache this package's fully-qualified name + callerInitOnce.Do(func() { + pcs := make([]uintptr, maximumCallerDepth) + _ = runtime.Callers(0, pcs) + + // dynamic get the package name and the minimum caller depth + for i := 0; i < maximumCallerDepth; i++ { + funcName := runtime.FuncForPC(pcs[i]).Name() + if strings.Contains(funcName, "getCaller") { + logrusPackage = getPackageName(funcName) + break + } + } + + minimumCallerDepth = knownLogrusFrames + }) + + // Restrict the lookback frames to avoid runaway lookups + pcs := make([]uintptr, maximumCallerDepth) + depth := runtime.Callers(minimumCallerDepth, pcs) + frames := runtime.CallersFrames(pcs[:depth]) + + for f, again := frames.Next(); again; f, again = frames.Next() { + pkg := getPackageName(f.Function) + + // If the caller isn't part of this package, we're done + if pkg != logrusPackage { + return &f //nolint:scopelint + } + } + + // if we got here, we failed to find the caller's context + return nil +} + +func (entry Entry) HasCaller() (has bool) { + return entry.Logger != nil && + entry.Logger.ReportCaller && + entry.Caller != nil +} + +// This function is not declared with a pointer value because otherwise +// race conditions will occur when using multiple goroutines +func (entry Entry) log(level Level, msg string) { + var buffer *bytes.Buffer + + // Default to now, but allow users to override if they want. + // + // We don't have to worry about polluting future calls to Entry#log() + // with this assignment because this function is declared with a + // non-pointer receiver. + if entry.Time.IsZero() { + entry.Time = time.Now() + } + + entry.Level = level + entry.Message = msg + entry.Logger.mu.Lock() + if entry.Logger.ReportCaller { + entry.Caller = getCaller() + } + entry.Logger.mu.Unlock() + + entry.fireHooks() + + buffer = bufferPool.Get().(*bytes.Buffer) + buffer.Reset() + defer bufferPool.Put(buffer) + entry.Buffer = buffer + + entry.write() + + entry.Buffer = nil + + // To avoid Entry#log() returning a value that only would make sense for + // panic() to use in Entry#Panic(), we avoid the allocation by checking + // directly here. + if level <= PanicLevel { + panic(&entry) + } +} + +func (entry *Entry) fireHooks() { + entry.Logger.mu.Lock() + defer entry.Logger.mu.Unlock() + err := entry.Logger.Hooks.Fire(entry.Level, entry) + if err != nil { + fmt.Fprintf(os.Stderr, "Failed to fire hook: %v\n", err) + } +} + +func (entry *Entry) write() { + entry.Logger.mu.Lock() + defer entry.Logger.mu.Unlock() + serialized, err := entry.Logger.Formatter.Format(entry) + if err != nil { + fmt.Fprintf(os.Stderr, "Failed to obtain reader, %v\n", err) + return + } + if _, err = entry.Logger.Out.Write(serialized); err != nil { + fmt.Fprintf(os.Stderr, "Failed to write to log, %v\n", err) + } +} + +func (entry *Entry) Log(level Level, args ...interface{}) { + if entry.Logger.IsLevelEnabled(level) { + entry.log(level, fmt.Sprint(args...)) + } +} + +func (entry *Entry) Trace(args ...interface{}) { + entry.Log(TraceLevel, args...) +} + +func (entry *Entry) Debug(args ...interface{}) { + entry.Log(DebugLevel, args...) +} + +func (entry *Entry) Print(args ...interface{}) { + entry.Info(args...) +} + +func (entry *Entry) Info(args ...interface{}) { + entry.Log(InfoLevel, args...) +} + +func (entry *Entry) Warn(args ...interface{}) { + entry.Log(WarnLevel, args...) +} + +func (entry *Entry) Warning(args ...interface{}) { + entry.Warn(args...) +} + +func (entry *Entry) Error(args ...interface{}) { + entry.Log(ErrorLevel, args...) +} + +func (entry *Entry) Fatal(args ...interface{}) { + entry.Log(FatalLevel, args...) + entry.Logger.Exit(1) +} + +func (entry *Entry) Panic(args ...interface{}) { + entry.Log(PanicLevel, args...) + panic(fmt.Sprint(args...)) +} + +// Entry Printf family functions + +func (entry *Entry) Logf(level Level, format string, args ...interface{}) { + if entry.Logger.IsLevelEnabled(level) { + entry.Log(level, fmt.Sprintf(format, args...)) + } +} + +func (entry *Entry) Tracef(format string, args ...interface{}) { + entry.Logf(TraceLevel, format, args...) +} + +func (entry *Entry) Debugf(format string, args ...interface{}) { + entry.Logf(DebugLevel, format, args...) +} + +func (entry *Entry) Infof(format string, args ...interface{}) { + entry.Logf(InfoLevel, format, args...) +} + +func (entry *Entry) Printf(format string, args ...interface{}) { + entry.Infof(format, args...) +} + +func (entry *Entry) Warnf(format string, args ...interface{}) { + entry.Logf(WarnLevel, format, args...) +} + +func (entry *Entry) Warningf(format string, args ...interface{}) { + entry.Warnf(format, args...) +} + +func (entry *Entry) Errorf(format string, args ...interface{}) { + entry.Logf(ErrorLevel, format, args...) +} + +func (entry *Entry) Fatalf(format string, args ...interface{}) { + entry.Logf(FatalLevel, format, args...) + entry.Logger.Exit(1) +} + +func (entry *Entry) Panicf(format string, args ...interface{}) { + entry.Logf(PanicLevel, format, args...) +} + +// Entry Println family functions + +func (entry *Entry) Logln(level Level, args ...interface{}) { + if entry.Logger.IsLevelEnabled(level) { + entry.Log(level, entry.sprintlnn(args...)) + } +} + +func (entry *Entry) Traceln(args ...interface{}) { + entry.Logln(TraceLevel, args...) +} + +func (entry *Entry) Debugln(args ...interface{}) { + entry.Logln(DebugLevel, args...) +} + +func (entry *Entry) Infoln(args ...interface{}) { + entry.Logln(InfoLevel, args...) +} + +func (entry *Entry) Println(args ...interface{}) { + entry.Infoln(args...) +} + +func (entry *Entry) Warnln(args ...interface{}) { + entry.Logln(WarnLevel, args...) +} + +func (entry *Entry) Warningln(args ...interface{}) { + entry.Warnln(args...) +} + +func (entry *Entry) Errorln(args ...interface{}) { + entry.Logln(ErrorLevel, args...) +} + +func (entry *Entry) Fatalln(args ...interface{}) { + entry.Logln(FatalLevel, args...) + entry.Logger.Exit(1) +} + +func (entry *Entry) Panicln(args ...interface{}) { + entry.Logln(PanicLevel, args...) +} + +// Sprintlnn => Sprint no newline. This is to get the behavior of how +// fmt.Sprintln where spaces are always added between operands, regardless of +// their type. Instead of vendoring the Sprintln implementation to spare a +// string allocation, we do the simplest thing. +func (entry *Entry) sprintlnn(args ...interface{}) string { + msg := fmt.Sprintln(args...) + return msg[:len(msg)-1] +} diff --git a/vendor/github.com/sirupsen/logrus/exported.go b/vendor/github.com/sirupsen/logrus/exported.go new file mode 100644 index 00000000..42b04f6c --- /dev/null +++ b/vendor/github.com/sirupsen/logrus/exported.go @@ -0,0 +1,225 @@ +package logrus + +import ( + "context" + "io" + "time" +) + +var ( + // std is the name of the standard logger in stdlib `log` + std = New() +) + +func StandardLogger() *Logger { + return std +} + +// SetOutput sets the standard logger output. +func SetOutput(out io.Writer) { + std.SetOutput(out) +} + +// SetFormatter sets the standard logger formatter. +func SetFormatter(formatter Formatter) { + std.SetFormatter(formatter) +} + +// SetReportCaller sets whether the standard logger will include the calling +// method as a field. +func SetReportCaller(include bool) { + std.SetReportCaller(include) +} + +// SetLevel sets the standard logger level. +func SetLevel(level Level) { + std.SetLevel(level) +} + +// GetLevel returns the standard logger level. +func GetLevel() Level { + return std.GetLevel() +} + +// IsLevelEnabled checks if the log level of the standard logger is greater than the level param +func IsLevelEnabled(level Level) bool { + return std.IsLevelEnabled(level) +} + +// AddHook adds a hook to the standard logger hooks. +func AddHook(hook Hook) { + std.AddHook(hook) +} + +// WithError creates an entry from the standard logger and adds an error to it, using the value defined in ErrorKey as key. +func WithError(err error) *Entry { + return std.WithField(ErrorKey, err) +} + +// WithContext creates an entry from the standard logger and adds a context to it. +func WithContext(ctx context.Context) *Entry { + return std.WithContext(ctx) +} + +// WithField creates an entry from the standard logger and adds a field to +// it. If you want multiple fields, use `WithFields`. +// +// Note that it doesn't log until you call Debug, Print, Info, Warn, Fatal +// or Panic on the Entry it returns. +func WithField(key string, value interface{}) *Entry { + return std.WithField(key, value) +} + +// WithFields creates an entry from the standard logger and adds multiple +// fields to it. This is simply a helper for `WithField`, invoking it +// once for each field. +// +// Note that it doesn't log until you call Debug, Print, Info, Warn, Fatal +// or Panic on the Entry it returns. +func WithFields(fields Fields) *Entry { + return std.WithFields(fields) +} + +// WithTime creates an entry from the standard logger and overrides the time of +// logs generated with it. +// +// Note that it doesn't log until you call Debug, Print, Info, Warn, Fatal +// or Panic on the Entry it returns. +func WithTime(t time.Time) *Entry { + return std.WithTime(t) +} + +// Trace logs a message at level Trace on the standard logger. +func Trace(args ...interface{}) { + std.Trace(args...) +} + +// Debug logs a message at level Debug on the standard logger. +func Debug(args ...interface{}) { + std.Debug(args...) +} + +// Print logs a message at level Info on the standard logger. +func Print(args ...interface{}) { + std.Print(args...) +} + +// Info logs a message at level Info on the standard logger. +func Info(args ...interface{}) { + std.Info(args...) +} + +// Warn logs a message at level Warn on the standard logger. +func Warn(args ...interface{}) { + std.Warn(args...) +} + +// Warning logs a message at level Warn on the standard logger. +func Warning(args ...interface{}) { + std.Warning(args...) +} + +// Error logs a message at level Error on the standard logger. +func Error(args ...interface{}) { + std.Error(args...) +} + +// Panic logs a message at level Panic on the standard logger. +func Panic(args ...interface{}) { + std.Panic(args...) +} + +// Fatal logs a message at level Fatal on the standard logger then the process will exit with status set to 1. +func Fatal(args ...interface{}) { + std.Fatal(args...) +} + +// Tracef logs a message at level Trace on the standard logger. +func Tracef(format string, args ...interface{}) { + std.Tracef(format, args...) +} + +// Debugf logs a message at level Debug on the standard logger. +func Debugf(format string, args ...interface{}) { + std.Debugf(format, args...) +} + +// Printf logs a message at level Info on the standard logger. +func Printf(format string, args ...interface{}) { + std.Printf(format, args...) +} + +// Infof logs a message at level Info on the standard logger. +func Infof(format string, args ...interface{}) { + std.Infof(format, args...) +} + +// Warnf logs a message at level Warn on the standard logger. +func Warnf(format string, args ...interface{}) { + std.Warnf(format, args...) +} + +// Warningf logs a message at level Warn on the standard logger. +func Warningf(format string, args ...interface{}) { + std.Warningf(format, args...) +} + +// Errorf logs a message at level Error on the standard logger. +func Errorf(format string, args ...interface{}) { + std.Errorf(format, args...) +} + +// Panicf logs a message at level Panic on the standard logger. +func Panicf(format string, args ...interface{}) { + std.Panicf(format, args...) +} + +// Fatalf logs a message at level Fatal on the standard logger then the process will exit with status set to 1. +func Fatalf(format string, args ...interface{}) { + std.Fatalf(format, args...) +} + +// Traceln logs a message at level Trace on the standard logger. +func Traceln(args ...interface{}) { + std.Traceln(args...) +} + +// Debugln logs a message at level Debug on the standard logger. +func Debugln(args ...interface{}) { + std.Debugln(args...) +} + +// Println logs a message at level Info on the standard logger. +func Println(args ...interface{}) { + std.Println(args...) +} + +// Infoln logs a message at level Info on the standard logger. +func Infoln(args ...interface{}) { + std.Infoln(args...) +} + +// Warnln logs a message at level Warn on the standard logger. +func Warnln(args ...interface{}) { + std.Warnln(args...) +} + +// Warningln logs a message at level Warn on the standard logger. +func Warningln(args ...interface{}) { + std.Warningln(args...) +} + +// Errorln logs a message at level Error on the standard logger. +func Errorln(args ...interface{}) { + std.Errorln(args...) +} + +// Panicln logs a message at level Panic on the standard logger. +func Panicln(args ...interface{}) { + std.Panicln(args...) +} + +// Fatalln logs a message at level Fatal on the standard logger then the process will exit with status set to 1. +func Fatalln(args ...interface{}) { + std.Fatalln(args...) +} diff --git a/vendor/github.com/sirupsen/logrus/formatter.go b/vendor/github.com/sirupsen/logrus/formatter.go new file mode 100644 index 00000000..40888377 --- /dev/null +++ b/vendor/github.com/sirupsen/logrus/formatter.go @@ -0,0 +1,78 @@ +package logrus + +import "time" + +// Default key names for the default fields +const ( + defaultTimestampFormat = time.RFC3339 + FieldKeyMsg = "msg" + FieldKeyLevel = "level" + FieldKeyTime = "time" + FieldKeyLogrusError = "logrus_error" + FieldKeyFunc = "func" + FieldKeyFile = "file" +) + +// The Formatter interface is used to implement a custom Formatter. It takes an +// `Entry`. It exposes all the fields, including the default ones: +// +// * `entry.Data["msg"]`. The message passed from Info, Warn, Error .. +// * `entry.Data["time"]`. The timestamp. +// * `entry.Data["level"]. The level the entry was logged at. +// +// Any additional fields added with `WithField` or `WithFields` are also in +// `entry.Data`. Format is expected to return an array of bytes which are then +// logged to `logger.Out`. +type Formatter interface { + Format(*Entry) ([]byte, error) +} + +// This is to not silently overwrite `time`, `msg`, `func` and `level` fields when +// dumping it. If this code wasn't there doing: +// +// logrus.WithField("level", 1).Info("hello") +// +// Would just silently drop the user provided level. Instead with this code +// it'll logged as: +// +// {"level": "info", "fields.level": 1, "msg": "hello", "time": "..."} +// +// It's not exported because it's still using Data in an opinionated way. It's to +// avoid code duplication between the two default formatters. +func prefixFieldClashes(data Fields, fieldMap FieldMap, reportCaller bool) { + timeKey := fieldMap.resolve(FieldKeyTime) + if t, ok := data[timeKey]; ok { + data["fields."+timeKey] = t + delete(data, timeKey) + } + + msgKey := fieldMap.resolve(FieldKeyMsg) + if m, ok := data[msgKey]; ok { + data["fields."+msgKey] = m + delete(data, msgKey) + } + + levelKey := fieldMap.resolve(FieldKeyLevel) + if l, ok := data[levelKey]; ok { + data["fields."+levelKey] = l + delete(data, levelKey) + } + + logrusErrKey := fieldMap.resolve(FieldKeyLogrusError) + if l, ok := data[logrusErrKey]; ok { + data["fields."+logrusErrKey] = l + delete(data, logrusErrKey) + } + + // If reportCaller is not set, 'func' will not conflict. + if reportCaller { + funcKey := fieldMap.resolve(FieldKeyFunc) + if l, ok := data[funcKey]; ok { + data["fields."+funcKey] = l + } + fileKey := fieldMap.resolve(FieldKeyFile) + if l, ok := data[fileKey]; ok { + data["fields."+fileKey] = l + } + } +} diff --git a/vendor/github.com/sirupsen/logrus/go.mod b/vendor/github.com/sirupsen/logrus/go.mod new file mode 100644 index 00000000..d4132967 --- /dev/null +++ b/vendor/github.com/sirupsen/logrus/go.mod @@ -0,0 +1,11 @@ +module github.com/sirupsen/logrus + +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/konsorten/go-windows-terminal-sequences v1.0.3 + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/stretchr/testify v1.2.2 + golang.org/x/sys v0.0.0-20190422165155-953cdadca894 +) + +go 1.13 diff --git a/vendor/github.com/sirupsen/logrus/go.sum b/vendor/github.com/sirupsen/logrus/go.sum new file mode 100644 index 00000000..49c690f2 --- /dev/null +++ b/vendor/github.com/sirupsen/logrus/go.sum @@ -0,0 +1,12 @@ +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.3 h1:CE8S1cTafDpPvMhIxNJKvHsGVBgn1xWYf1NbHQhywc8= +github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +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/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894 h1:Cz4ceDQGXuKRnVBDTS23GTn/pU5OE2C0WrNTOYK1Uuc= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= diff --git a/vendor/github.com/sirupsen/logrus/hooks.go b/vendor/github.com/sirupsen/logrus/hooks.go new file mode 100644 index 00000000..3f151cdc --- /dev/null +++ b/vendor/github.com/sirupsen/logrus/hooks.go @@ -0,0 +1,34 @@ +package logrus + +// A hook to be fired when logging on the logging levels returned from +// `Levels()` on your implementation of the interface. Note that this is not +// fired in a goroutine or a channel with workers, you should handle such +// functionality yourself if your call is non-blocking and you don't wish for +// the logging calls for levels returned from `Levels()` to block. +type Hook interface { + Levels() []Level + Fire(*Entry) error +} + +// Internal type for storing the hooks on a logger instance. +type LevelHooks map[Level][]Hook + +// Add a hook to an instance of logger. This is called with +// `log.Hooks.Add(new(MyHook))` where `MyHook` implements the `Hook` interface. +func (hooks LevelHooks) Add(hook Hook) { + for _, level := range hook.Levels() { + hooks[level] = append(hooks[level], hook) + } +} + +// Fire all the hooks for the passed level. Used by `entry.log` to fire +// appropriate hooks for a log entry. +func (hooks LevelHooks) Fire(level Level, entry *Entry) error { + for _, hook := range hooks[level] { + if err := hook.Fire(entry); err != nil { + return err + } + } + + return nil +} diff --git a/vendor/github.com/sirupsen/logrus/json_formatter.go b/vendor/github.com/sirupsen/logrus/json_formatter.go new file mode 100644 index 00000000..ba7f2371 --- /dev/null +++ b/vendor/github.com/sirupsen/logrus/json_formatter.go @@ -0,0 +1,125 @@ +package logrus + +import ( + "bytes" + "encoding/json" + "fmt" + "runtime" +) + +type fieldKey string + +// FieldMap allows customization of the key names for default fields. +type FieldMap map[fieldKey]string + +func (f FieldMap) resolve(key fieldKey) string { + if k, ok := f[key]; ok { + return k + } + + return string(key) +} + +// JSONFormatter formats logs into parsable json +type JSONFormatter struct { + // TimestampFormat sets the format used for marshaling timestamps. + TimestampFormat string + + // DisableTimestamp allows disabling automatic timestamps in output + DisableTimestamp bool + + // DisableHTMLEscape allows disabling html escaping in output + DisableHTMLEscape bool + + // DataKey allows users to put all the log entry parameters into a nested dictionary at a given key. + DataKey string + + // FieldMap allows users to customize the names of keys for default fields. + // As an example: + // formatter := &JSONFormatter{ + // FieldMap: FieldMap{ + // FieldKeyTime: "@timestamp", + // FieldKeyLevel: "@level", + // FieldKeyMsg: "@message", + // FieldKeyFunc: "@caller", + // }, + // } + FieldMap FieldMap + + // CallerPrettyfier can be set by the user to modify the content + // of the function and file keys in the json data when ReportCaller is + // activated. If any of the returned value is the empty string the + // corresponding key will be removed from json fields. + CallerPrettyfier func(*runtime.Frame) (function string, file string) + + // PrettyPrint will indent all json logs + PrettyPrint bool +} + +// Format renders a single log entry +func (f *JSONFormatter) Format(entry *Entry) ([]byte, error) { + data := make(Fields, len(entry.Data)+4) + for k, v := range entry.Data { + switch v := v.(type) { + case error: + // Otherwise errors are ignored by `encoding/json` + // https://github.com/sirupsen/logrus/issues/137 + data[k] = v.Error() + default: + data[k] = v + } + } + + if f.DataKey != "" { + newData := make(Fields, 4) + newData[f.DataKey] = data + data = newData + } + + prefixFieldClashes(data, f.FieldMap, entry.HasCaller()) + + timestampFormat := f.TimestampFormat + if timestampFormat == "" { + timestampFormat = defaultTimestampFormat + } + + if entry.err != "" { + data[f.FieldMap.resolve(FieldKeyLogrusError)] = entry.err + } + if !f.DisableTimestamp { + data[f.FieldMap.resolve(FieldKeyTime)] = entry.Time.Format(timestampFormat) + } + data[f.FieldMap.resolve(FieldKeyMsg)] = entry.Message + data[f.FieldMap.resolve(FieldKeyLevel)] = entry.Level.String() + if entry.HasCaller() { + funcVal := entry.Caller.Function + fileVal := fmt.Sprintf("%s:%d", entry.Caller.File, entry.Caller.Line) + if f.CallerPrettyfier != nil { + funcVal, fileVal = f.CallerPrettyfier(entry.Caller) + } + if funcVal != "" { + data[f.FieldMap.resolve(FieldKeyFunc)] = funcVal + } + if fileVal != "" { + data[f.FieldMap.resolve(FieldKeyFile)] = fileVal + } + } + + var b *bytes.Buffer + if entry.Buffer != nil { + b = entry.Buffer + } else { + b = &bytes.Buffer{} + } + + encoder := json.NewEncoder(b) + encoder.SetEscapeHTML(!f.DisableHTMLEscape) + if f.PrettyPrint { + encoder.SetIndent("", " ") + } + if err := encoder.Encode(data); err != nil { + return nil, fmt.Errorf("failed to marshal fields to JSON, %v", err) + } + + return b.Bytes(), nil +} diff --git a/vendor/github.com/sirupsen/logrus/logger.go b/vendor/github.com/sirupsen/logrus/logger.go new file mode 100644 index 00000000..6fdda748 --- /dev/null +++ b/vendor/github.com/sirupsen/logrus/logger.go @@ -0,0 +1,352 @@ +package logrus + +import ( + "context" + "io" + "os" + "sync" + "sync/atomic" + "time" +) + +type Logger struct { + // The logs are `io.Copy`'d to this in a mutex. It's common to set this to a + // file, or leave it default which is `os.Stderr`. You can also set this to + // something more adventurous, such as logging to Kafka. + Out io.Writer + // Hooks for the logger instance. These allow firing events based on logging + // levels and log entries. For example, to send errors to an error tracking + // service, log to StatsD or dump the core on fatal errors. + Hooks LevelHooks + // All log entries pass through the formatter before logged to Out. The + // included formatters are `TextFormatter` and `JSONFormatter` for which + // TextFormatter is the default. In development (when a TTY is attached) it + // logs with colors, but to a file it wouldn't. You can easily implement your + // own that implements the `Formatter` interface, see the `README` or included + // formatters for examples. + Formatter Formatter + + // Flag for whether to log caller info (off by default) + ReportCaller bool + + // The logging level the logger should log at. This is typically (and defaults + // to) `logrus.Info`, which allows Info(), Warn(), Error() and Fatal() to be + // logged. + Level Level + // Used to sync writing to the log. Locking is enabled by Default + mu MutexWrap + // Reusable empty entry + entryPool sync.Pool + // Function to exit the application, defaults to `os.Exit()` + ExitFunc exitFunc +} + +type exitFunc func(int) + +type MutexWrap struct { + lock sync.Mutex + disabled bool +} + +func (mw *MutexWrap) Lock() { + if !mw.disabled { + mw.lock.Lock() + } +} + +func (mw *MutexWrap) Unlock() { + if !mw.disabled { + mw.lock.Unlock() + } +} + +func (mw *MutexWrap) Disable() { + mw.disabled = true +} + +// Creates a new logger. Configuration should be set by changing `Formatter`, +// `Out` and `Hooks` directly on the default logger instance. You can also just +// instantiate your own: +// +// var log = &logrus.Logger{ +// Out: os.Stderr, +// Formatter: new(logrus.JSONFormatter), +// Hooks: make(logrus.LevelHooks), +// Level: logrus.DebugLevel, +// } +// +// It's recommended to make this a global instance called `log`. +func New() *Logger { + return &Logger{ + Out: os.Stderr, + Formatter: new(TextFormatter), + Hooks: make(LevelHooks), + Level: InfoLevel, + ExitFunc: os.Exit, + ReportCaller: false, + } +} + +func (logger *Logger) newEntry() *Entry { + entry, ok := logger.entryPool.Get().(*Entry) + if ok { + return entry + } + return NewEntry(logger) +} + +func (logger *Logger) releaseEntry(entry *Entry) { + entry.Data = map[string]interface{}{} + logger.entryPool.Put(entry) +} + +// WithField allocates a new entry and adds a field to it. +// Debug, Print, Info, Warn, Error, Fatal or Panic must be then applied to +// this new returned entry. +// If you want multiple fields, use `WithFields`. +func (logger *Logger) WithField(key string, value interface{}) *Entry { + entry := logger.newEntry() + defer logger.releaseEntry(entry) + return entry.WithField(key, value) +} + +// Adds a struct of fields to the log entry. All it does is call `WithField` for +// each `Field`. +func (logger *Logger) WithFields(fields Fields) *Entry { + entry := logger.newEntry() + defer logger.releaseEntry(entry) + return entry.WithFields(fields) +} + +// Add an error as single field to the log entry. All it does is call +// `WithError` for the given `error`. +func (logger *Logger) WithError(err error) *Entry { + entry := logger.newEntry() + defer logger.releaseEntry(entry) + return entry.WithError(err) +} + +// Add a context to the log entry. +func (logger *Logger) WithContext(ctx context.Context) *Entry { + entry := logger.newEntry() + defer logger.releaseEntry(entry) + return entry.WithContext(ctx) +} + +// Overrides the time of the log entry. +func (logger *Logger) WithTime(t time.Time) *Entry { + entry := logger.newEntry() + defer logger.releaseEntry(entry) + return entry.WithTime(t) +} + +func (logger *Logger) Logf(level Level, format string, args ...interface{}) { + if logger.IsLevelEnabled(level) { + entry := logger.newEntry() + entry.Logf(level, format, args...) + logger.releaseEntry(entry) + } +} + +func (logger *Logger) Tracef(format string, args ...interface{}) { + logger.Logf(TraceLevel, format, args...) +} + +func (logger *Logger) Debugf(format string, args ...interface{}) { + logger.Logf(DebugLevel, format, args...) +} + +func (logger *Logger) Infof(format string, args ...interface{}) { + logger.Logf(InfoLevel, format, args...) +} + +func (logger *Logger) Printf(format string, args ...interface{}) { + entry := logger.newEntry() + entry.Printf(format, args...) + logger.releaseEntry(entry) +} + +func (logger *Logger) Warnf(format string, args ...interface{}) { + logger.Logf(WarnLevel, format, args...) +} + +func (logger *Logger) Warningf(format string, args ...interface{}) { + logger.Warnf(format, args...) +} + +func (logger *Logger) Errorf(format string, args ...interface{}) { + logger.Logf(ErrorLevel, format, args...) +} + +func (logger *Logger) Fatalf(format string, args ...interface{}) { + logger.Logf(FatalLevel, format, args...) + logger.Exit(1) +} + +func (logger *Logger) Panicf(format string, args ...interface{}) { + logger.Logf(PanicLevel, format, args...) +} + +func (logger *Logger) Log(level Level, args ...interface{}) { + if logger.IsLevelEnabled(level) { + entry := logger.newEntry() + entry.Log(level, args...) + logger.releaseEntry(entry) + } +} + +func (logger *Logger) Trace(args ...interface{}) { + logger.Log(TraceLevel, args...) +} + +func (logger *Logger) Debug(args ...interface{}) { + logger.Log(DebugLevel, args...) +} + +func (logger *Logger) Info(args ...interface{}) { + logger.Log(InfoLevel, args...) +} + +func (logger *Logger) Print(args ...interface{}) { + entry := logger.newEntry() + entry.Print(args...) + logger.releaseEntry(entry) +} + +func (logger *Logger) Warn(args ...interface{}) { + logger.Log(WarnLevel, args...) +} + +func (logger *Logger) Warning(args ...interface{}) { + logger.Warn(args...) +} + +func (logger *Logger) Error(args ...interface{}) { + logger.Log(ErrorLevel, args...) +} + +func (logger *Logger) Fatal(args ...interface{}) { + logger.Log(FatalLevel, args...) + logger.Exit(1) +} + +func (logger *Logger) Panic(args ...interface{}) { + logger.Log(PanicLevel, args...) +} + +func (logger *Logger) Logln(level Level, args ...interface{}) { + if logger.IsLevelEnabled(level) { + entry := logger.newEntry() + entry.Logln(level, args...) + logger.releaseEntry(entry) + } +} + +func (logger *Logger) Traceln(args ...interface{}) { + logger.Logln(TraceLevel, args...) +} + +func (logger *Logger) Debugln(args ...interface{}) { + logger.Logln(DebugLevel, args...) +} + +func (logger *Logger) Infoln(args ...interface{}) { + logger.Logln(InfoLevel, args...) +} + +func (logger *Logger) Println(args ...interface{}) { + entry := logger.newEntry() + entry.Println(args...) + logger.releaseEntry(entry) +} + +func (logger *Logger) Warnln(args ...interface{}) { + logger.Logln(WarnLevel, args...) +} + +func (logger *Logger) Warningln(args ...interface{}) { + logger.Warnln(args...) +} + +func (logger *Logger) Errorln(args ...interface{}) { + logger.Logln(ErrorLevel, args...) +} + +func (logger *Logger) Fatalln(args ...interface{}) { + logger.Logln(FatalLevel, args...) + logger.Exit(1) +} + +func (logger *Logger) Panicln(args ...interface{}) { + logger.Logln(PanicLevel, args...) +} + +func (logger *Logger) Exit(code int) { + runHandlers() + if logger.ExitFunc == nil { + logger.ExitFunc = os.Exit + } + logger.ExitFunc(code) +} + +//When file is opened with appending mode, it's safe to +//write concurrently to a file (within 4k message on Linux). +//In these cases user can choose to disable the lock. +func (logger *Logger) SetNoLock() { + logger.mu.Disable() +} + +func (logger *Logger) level() Level { + return Level(atomic.LoadUint32((*uint32)(&logger.Level))) +} + +// SetLevel sets the logger level. +func (logger *Logger) SetLevel(level Level) { + atomic.StoreUint32((*uint32)(&logger.Level), uint32(level)) +} + +// GetLevel returns the logger level. +func (logger *Logger) GetLevel() Level { + return logger.level() +} + +// AddHook adds a hook to the logger hooks. +func (logger *Logger) AddHook(hook Hook) { + logger.mu.Lock() + defer logger.mu.Unlock() + logger.Hooks.Add(hook) +} + +// IsLevelEnabled checks if the log level of the logger is greater than the level param +func (logger *Logger) IsLevelEnabled(level Level) bool { + return logger.level() >= level +} + +// SetFormatter sets the logger formatter. +func (logger *Logger) SetFormatter(formatter Formatter) { + logger.mu.Lock() + defer logger.mu.Unlock() + logger.Formatter = formatter +} + +// SetOutput sets the logger output. +func (logger *Logger) SetOutput(output io.Writer) { + logger.mu.Lock() + defer logger.mu.Unlock() + logger.Out = output +} + +func (logger *Logger) SetReportCaller(reportCaller bool) { + logger.mu.Lock() + defer logger.mu.Unlock() + logger.ReportCaller = reportCaller +} + +// ReplaceHooks replaces the logger hooks and returns the old ones +func (logger *Logger) ReplaceHooks(hooks LevelHooks) LevelHooks { + logger.mu.Lock() + oldHooks := logger.Hooks + logger.Hooks = hooks + logger.mu.Unlock() + return oldHooks +} diff --git a/vendor/github.com/sirupsen/logrus/logrus.go b/vendor/github.com/sirupsen/logrus/logrus.go new file mode 100644 index 00000000..2f16224c --- /dev/null +++ b/vendor/github.com/sirupsen/logrus/logrus.go @@ -0,0 +1,186 @@ +package logrus + +import ( + "fmt" + "log" + "strings" +) + +// Fields type, used to pass to `WithFields`. +type Fields map[string]interface{} + +// Level type +type Level uint32 + +// Convert the Level to a string. E.g. PanicLevel becomes "panic". +func (level Level) String() string { + if b, err := level.MarshalText(); err == nil { + return string(b) + } else { + return "unknown" + } +} + +// ParseLevel takes a string level and returns the Logrus log level constant. +func ParseLevel(lvl string) (Level, error) { + switch strings.ToLower(lvl) { + case "panic": + return PanicLevel, nil + case "fatal": + return FatalLevel, nil + case "error": + return ErrorLevel, nil + case "warn", "warning": + return WarnLevel, nil + case "info": + return InfoLevel, nil + case "debug": + return DebugLevel, nil + case "trace": + return TraceLevel, nil + } + + var l Level + return l, fmt.Errorf("not a valid logrus Level: %q", lvl) +} + +// UnmarshalText implements encoding.TextUnmarshaler. +func (level *Level) UnmarshalText(text []byte) error { + l, err := ParseLevel(string(text)) + if err != nil { + return err + } + + *level = l + + return nil +} + +func (level Level) MarshalText() ([]byte, error) { + switch level { + case TraceLevel: + return []byte("trace"), nil + case DebugLevel: + return []byte("debug"), nil + case InfoLevel: + return []byte("info"), nil + case WarnLevel: + return []byte("warning"), nil + case ErrorLevel: + return []byte("error"), nil + case FatalLevel: + return []byte("fatal"), nil + case PanicLevel: + return []byte("panic"), nil + } + + return nil, fmt.Errorf("not a valid logrus level %d", level) +} + +// A constant exposing all logging levels +var AllLevels = []Level{ + PanicLevel, + FatalLevel, + ErrorLevel, + WarnLevel, + InfoLevel, + DebugLevel, + TraceLevel, +} + +// These are the different logging levels. You can set the logging level to log +// on your instance of logger, obtained with `logrus.New()`. +const ( + // PanicLevel level, highest level of severity. Logs and then calls panic with the + // message passed to Debug, Info, ... + PanicLevel Level = iota + // FatalLevel level. Logs and then calls `logger.Exit(1)`. It will exit even if the + // logging level is set to Panic. + FatalLevel + // ErrorLevel level. Logs. Used for errors that should definitely be noted. + // Commonly used for hooks to send errors to an error tracking service. + ErrorLevel + // WarnLevel level. Non-critical entries that deserve eyes. + WarnLevel + // InfoLevel level. General operational entries about what's going on inside the + // application. + InfoLevel + // DebugLevel level. Usually only enabled when debugging. Very verbose logging. + DebugLevel + // TraceLevel level. Designates finer-grained informational events than the Debug. + TraceLevel +) + +// Won't compile if StdLogger can't be realized by a log.Logger +var ( + _ StdLogger = &log.Logger{} + _ StdLogger = &Entry{} + _ StdLogger = &Logger{} +) + +// StdLogger is what your logrus-enabled library should take, that way +// it'll accept a stdlib logger and a logrus logger. There's no standard +// interface, this is the closest we get, unfortunately. +type StdLogger interface { + Print(...interface{}) + Printf(string, ...interface{}) + Println(...interface{}) + + Fatal(...interface{}) + Fatalf(string, ...interface{}) + Fatalln(...interface{}) + + Panic(...interface{}) + Panicf(string, ...interface{}) + Panicln(...interface{}) +} + +// The FieldLogger interface generalizes the Entry and Logger types +type FieldLogger interface { + WithField(key string, value interface{}) *Entry + WithFields(fields Fields) *Entry + WithError(err error) *Entry + + Debugf(format string, args ...interface{}) + Infof(format string, args ...interface{}) + Printf(format string, args ...interface{}) + Warnf(format string, args ...interface{}) + Warningf(format string, args ...interface{}) + Errorf(format string, args ...interface{}) + Fatalf(format string, args ...interface{}) + Panicf(format string, args ...interface{}) + + Debug(args ...interface{}) + Info(args ...interface{}) + Print(args ...interface{}) + Warn(args ...interface{}) + Warning(args ...interface{}) + Error(args ...interface{}) + Fatal(args ...interface{}) + Panic(args ...interface{}) + + Debugln(args ...interface{}) + Infoln(args ...interface{}) + Println(args ...interface{}) + Warnln(args ...interface{}) + Warningln(args ...interface{}) + Errorln(args ...interface{}) + Fatalln(args ...interface{}) + Panicln(args ...interface{}) + + // IsDebugEnabled() bool + // IsInfoEnabled() bool + // IsWarnEnabled() bool + // IsErrorEnabled() bool + // IsFatalEnabled() bool + // IsPanicEnabled() bool +} + +// Ext1FieldLogger (the first extension to FieldLogger) is superfluous, it is +// here for consistancy. Do not use. Use Logger or Entry instead. +type Ext1FieldLogger interface { + FieldLogger + Tracef(format string, args ...interface{}) + Trace(args ...interface{}) + Traceln(args ...interface{}) +} diff --git a/vendor/github.com/sirupsen/logrus/terminal_check_appengine.go b/vendor/github.com/sirupsen/logrus/terminal_check_appengine.go new file mode 100644 index 00000000..2403de98 --- /dev/null +++ b/vendor/github.com/sirupsen/logrus/terminal_check_appengine.go @@ -0,0 +1,11 @@ +// +build appengine + +package logrus + +import ( + "io" +) + +func checkIfTerminal(w io.Writer) bool { + return true +} diff --git a/vendor/github.com/sirupsen/logrus/terminal_check_bsd.go b/vendor/github.com/sirupsen/logrus/terminal_check_bsd.go new file mode 100644 index 00000000..49978998 --- /dev/null +++ b/vendor/github.com/sirupsen/logrus/terminal_check_bsd.go @@ -0,0 +1,13 @@ +// +build darwin dragonfly freebsd netbsd openbsd +// +build !js + +package logrus + +import "golang.org/x/sys/unix" + +const ioctlReadTermios = unix.TIOCGETA + +func isTerminal(fd int) bool { + _, err := unix.IoctlGetTermios(fd, ioctlReadTermios) + return err == nil +} diff --git a/vendor/github.com/sirupsen/logrus/terminal_check_js.go b/vendor/github.com/sirupsen/logrus/terminal_check_js.go new file mode 100644 index 00000000..ebdae3ec --- /dev/null +++ b/vendor/github.com/sirupsen/logrus/terminal_check_js.go @@ -0,0 +1,7 @@ +// +build js + +package logrus + +func isTerminal(fd int) bool { + return false +} diff --git a/vendor/github.com/sirupsen/logrus/terminal_check_no_terminal.go b/vendor/github.com/sirupsen/logrus/terminal_check_no_terminal.go new file mode 100644 index 00000000..97af92c6 --- /dev/null +++ b/vendor/github.com/sirupsen/logrus/terminal_check_no_terminal.go @@ -0,0 +1,11 @@ +// +build js nacl plan9 + +package logrus + +import ( + "io" +) + +func checkIfTerminal(w io.Writer) bool { + return false +} diff --git a/vendor/github.com/sirupsen/logrus/terminal_check_notappengine.go b/vendor/github.com/sirupsen/logrus/terminal_check_notappengine.go new file mode 100644 index 00000000..3293fb3c --- /dev/null +++ b/vendor/github.com/sirupsen/logrus/terminal_check_notappengine.go @@ -0,0 +1,17 @@ +// +build !appengine,!js,!windows,!nacl,!plan9 + +package logrus + +import ( + "io" + "os" +) + +func checkIfTerminal(w io.Writer) bool { + switch v := w.(type) { + case *os.File: + return isTerminal(int(v.Fd())) + default: + return false + } +} diff --git a/vendor/github.com/sirupsen/logrus/terminal_check_solaris.go b/vendor/github.com/sirupsen/logrus/terminal_check_solaris.go new file mode 100644 index 00000000..f6710b3b --- /dev/null +++ b/vendor/github.com/sirupsen/logrus/terminal_check_solaris.go @@ -0,0 +1,11 @@ +package logrus + +import ( + "golang.org/x/sys/unix" +) + +// IsTerminal returns true if the given file descriptor is a terminal. +func isTerminal(fd int) bool { + _, err := unix.IoctlGetTermio(fd, unix.TCGETA) + return err == nil +} diff --git a/vendor/github.com/sirupsen/logrus/terminal_check_unix.go b/vendor/github.com/sirupsen/logrus/terminal_check_unix.go new file mode 100644 index 00000000..cc4fe6e3 --- /dev/null +++ b/vendor/github.com/sirupsen/logrus/terminal_check_unix.go @@ -0,0 +1,13 @@ +// +build linux aix +// +build !js + +package logrus + +import "golang.org/x/sys/unix" + +const ioctlReadTermios = unix.TCGETS + +func isTerminal(fd int) bool { + _, err := unix.IoctlGetTermios(fd, ioctlReadTermios) + return err == nil +} diff --git a/vendor/github.com/sirupsen/logrus/terminal_check_windows.go b/vendor/github.com/sirupsen/logrus/terminal_check_windows.go new file mode 100644 index 00000000..572889db --- /dev/null +++ b/vendor/github.com/sirupsen/logrus/terminal_check_windows.go @@ -0,0 +1,34 @@ +// +build !appengine,!js,windows + +package logrus + +import ( + "io" + "os" + "syscall" + + sequences "github.com/konsorten/go-windows-terminal-sequences" +) + +func initTerminal(w io.Writer) { + switch v := w.(type) { + case *os.File: + sequences.EnableVirtualTerminalProcessing(syscall.Handle(v.Fd()), true) + } +} + +func checkIfTerminal(w io.Writer) bool { + var ret bool + switch v := w.(type) { + case *os.File: + var mode uint32 + err := syscall.GetConsoleMode(syscall.Handle(v.Fd()), &mode) + ret = (err == nil) + default: + ret = false + } + if ret { + initTerminal(w) + } + return ret +} diff --git a/vendor/github.com/sirupsen/logrus/text_formatter.go b/vendor/github.com/sirupsen/logrus/text_formatter.go new file mode 100644 index 00000000..3c28b54c --- /dev/null +++ b/vendor/github.com/sirupsen/logrus/text_formatter.go @@ -0,0 +1,334 @@ +package logrus + +import ( + "bytes" + "fmt" + "os" + "runtime" + "sort" + "strconv" + "strings" + "sync" + "time" + "unicode/utf8" +) + +const ( + red = 31 + yellow = 33 + blue = 36 + gray = 37 +) + +var baseTimestamp time.Time + +func init() { + baseTimestamp = time.Now() +} + +// TextFormatter formats logs into text +type TextFormatter struct { + // Set to true to bypass checking for a TTY before outputting colors. + ForceColors bool + + // Force disabling colors. + DisableColors bool + + // Force quoting of all values + ForceQuote bool + + // DisableQuote disables quoting for all values. + // DisableQuote will have a lower priority than ForceQuote. + // If both of them are set to true, quote will be forced on all values. + DisableQuote bool + + // Override coloring based on CLICOLOR and CLICOLOR_FORCE. - https://bixense.com/clicolors/ + EnvironmentOverrideColors bool + + // Disable timestamp logging. useful when output is redirected to logging + // system that already adds timestamps. + DisableTimestamp bool + + // Enable logging the full timestamp when a TTY is attached instead of just + // the time passed since beginning of execution. + FullTimestamp bool + + // TimestampFormat to use for display when a full timestamp is printed + TimestampFormat string + + // The fields are sorted by default for a consistent output. For applications + // that log extremely frequently and don't use the JSON formatter this may not + // be desired. + DisableSorting bool + + // The keys sorting function, when uninitialized it uses sort.Strings. + SortingFunc func([]string) + + // Disables the truncation of the level text to 4 characters. + DisableLevelTruncation bool + + // PadLevelText Adds padding the level text so that all the levels output at the same length + // PadLevelText is a superset of the DisableLevelTruncation option + PadLevelText bool + + // QuoteEmptyFields will wrap empty fields in quotes if true + QuoteEmptyFields bool + + // Whether the logger's out is to a terminal + isTerminal bool + + // FieldMap allows users to customize the names of keys for default fields. + // As an example: + // formatter := &TextFormatter{ + // FieldMap: FieldMap{ + // FieldKeyTime: "@timestamp", + // FieldKeyLevel: "@level", + // FieldKeyMsg: "@message"}} + FieldMap FieldMap + + // CallerPrettyfier can be set by the user to modify the content + // of the function and file keys in the data when ReportCaller is + // activated. If any of the returned value is the empty string the + // corresponding key will be removed from fields. + CallerPrettyfier func(*runtime.Frame) (function string, file string) + + terminalInitOnce sync.Once + + // The max length of the level text, generated dynamically on init + levelTextMaxLength int +} + +func (f *TextFormatter) init(entry *Entry) { + if entry.Logger != nil { + f.isTerminal = checkIfTerminal(entry.Logger.Out) + } + // Get the max length of the level text + for _, level := range AllLevels { + levelTextLength := utf8.RuneCount([]byte(level.String())) + if levelTextLength > f.levelTextMaxLength { + f.levelTextMaxLength = levelTextLength + } + } +} + +func (f *TextFormatter) isColored() bool { + isColored := f.ForceColors || (f.isTerminal && (runtime.GOOS != "windows")) + + if f.EnvironmentOverrideColors { + switch force, ok := os.LookupEnv("CLICOLOR_FORCE"); { + case ok && force != "0": + isColored = true + case ok && force == "0", os.Getenv("CLICOLOR") == "0": + isColored = false + } + } + + return isColored && !f.DisableColors +} + +// Format renders a single log entry +func (f *TextFormatter) Format(entry *Entry) ([]byte, error) { + data := make(Fields) + for k, v := range entry.Data { + data[k] = v + } + prefixFieldClashes(data, f.FieldMap, entry.HasCaller()) + keys := make([]string, 0, len(data)) + for k := range data { + keys = append(keys, k) + } + + var funcVal, fileVal string + + fixedKeys := make([]string, 0, 4+len(data)) + if !f.DisableTimestamp { + fixedKeys = append(fixedKeys, f.FieldMap.resolve(FieldKeyTime)) + } + fixedKeys = append(fixedKeys, f.FieldMap.resolve(FieldKeyLevel)) + if entry.Message != "" { + fixedKeys = append(fixedKeys, f.FieldMap.resolve(FieldKeyMsg)) + } + if entry.err != "" { + fixedKeys = append(fixedKeys, f.FieldMap.resolve(FieldKeyLogrusError)) + } + if entry.HasCaller() { + if f.CallerPrettyfier != nil { + funcVal, fileVal = f.CallerPrettyfier(entry.Caller) + } else { + funcVal = entry.Caller.Function + fileVal = fmt.Sprintf("%s:%d", entry.Caller.File, entry.Caller.Line) + } + + if funcVal != "" { + fixedKeys = append(fixedKeys, f.FieldMap.resolve(FieldKeyFunc)) + } + if fileVal != "" { + fixedKeys = append(fixedKeys, f.FieldMap.resolve(FieldKeyFile)) + } + } + + if !f.DisableSorting { + if f.SortingFunc == nil { + sort.Strings(keys) + fixedKeys = append(fixedKeys, keys...) + } else { + if !f.isColored() { + fixedKeys = append(fixedKeys, keys...) + f.SortingFunc(fixedKeys) + } else { + f.SortingFunc(keys) + } + } + } else { + fixedKeys = append(fixedKeys, keys...) + } + + var b *bytes.Buffer + if entry.Buffer != nil { + b = entry.Buffer + } else { + b = &bytes.Buffer{} + } + + f.terminalInitOnce.Do(func() { f.init(entry) }) + + timestampFormat := f.TimestampFormat + if timestampFormat == "" { + timestampFormat = defaultTimestampFormat + } + if f.isColored() { + f.printColored(b, entry, keys, data, timestampFormat) + } else { + + for _, key := range fixedKeys { + var value interface{} + switch { + case key == f.FieldMap.resolve(FieldKeyTime): + value = entry.Time.Format(timestampFormat) + case key == f.FieldMap.resolve(FieldKeyLevel): + value = entry.Level.String() + case key == f.FieldMap.resolve(FieldKeyMsg): + value = entry.Message + case key == f.FieldMap.resolve(FieldKeyLogrusError): + value = entry.err + case key == f.FieldMap.resolve(FieldKeyFunc) && entry.HasCaller(): + value = funcVal + case key == f.FieldMap.resolve(FieldKeyFile) && entry.HasCaller(): + value = fileVal + default: + value = data[key] + } + f.appendKeyValue(b, key, value) + } + } + + b.WriteByte('\n') + return b.Bytes(), nil +} + +func (f *TextFormatter) printColored(b *bytes.Buffer, entry *Entry, keys []string, data Fields, timestampFormat string) { + var levelColor int + switch entry.Level { + case DebugLevel, TraceLevel: + levelColor = gray + case WarnLevel: + levelColor = yellow + case ErrorLevel, FatalLevel, PanicLevel: + levelColor = red + default: + levelColor = blue + } + + levelText := strings.ToUpper(entry.Level.String()) + if !f.DisableLevelTruncation && !f.PadLevelText { + levelText = levelText[0:4] + } + if f.PadLevelText { + // Generates the format string used in the next line, for example "%-6s" or "%-7s". + // Based on the max level text length. + formatString := "%-" + strconv.Itoa(f.levelTextMaxLength) + "s" + // Formats the level text by appending spaces up to the max length, for example: + // - "INFO " + // - "WARNING" + levelText = fmt.Sprintf(formatString, levelText) + } + + // Remove a single newline if it already exists in the message to keep + // the behavior of logrus text_formatter the same as the stdlib log package + entry.Message = strings.TrimSuffix(entry.Message, "\n") + + caller := "" + if entry.HasCaller() { + funcVal := fmt.Sprintf("%s()", entry.Caller.Function) + fileVal := fmt.Sprintf("%s:%d", entry.Caller.File, entry.Caller.Line) + + if f.CallerPrettyfier != nil { + funcVal, fileVal = f.CallerPrettyfier(entry.Caller) + } + + if fileVal == "" { + caller = funcVal + } else if funcVal == "" { + caller = fileVal + } else { + caller = fileVal + " " + funcVal + } + } + + switch { + case f.DisableTimestamp: + fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m%s %-44s ", levelColor, levelText, caller, entry.Message) + case !f.FullTimestamp: + fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%04d]%s %-44s ", levelColor, levelText, int(entry.Time.Sub(baseTimestamp)/time.Second), caller, entry.Message) + default: + fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%s]%s %-44s ", levelColor, levelText, entry.Time.Format(timestampFormat), caller, entry.Message) + } + for _, k := range keys { + v := data[k] + fmt.Fprintf(b, " \x1b[%dm%s\x1b[0m=", levelColor, k) + f.appendValue(b, v) + } +} + +func (f *TextFormatter) needsQuoting(text string) bool { + if f.ForceQuote { + return true + } + if f.QuoteEmptyFields && len(text) == 0 { + return true + } + if f.DisableQuote { + return false + } + for _, ch := range text { + if !((ch >= 'a' && ch <= 'z') || + (ch >= 'A' && ch <= 'Z') || + (ch >= '0' && ch <= '9') || + ch == '-' || ch == '.' || ch == '_' || ch == '/' || ch == '@' || ch == '^' || ch == '+') { + return true + } + } + return false +} + +func (f *TextFormatter) appendKeyValue(b *bytes.Buffer, key string, value interface{}) { + if b.Len() > 0 { + b.WriteByte(' ') + } + b.WriteString(key) + b.WriteByte('=') + f.appendValue(b, value) +} + +func (f *TextFormatter) appendValue(b *bytes.Buffer, value interface{}) { + stringVal, ok := value.(string) + if !ok { + stringVal = fmt.Sprint(value) + } + + if !f.needsQuoting(stringVal) { + b.WriteString(stringVal) + } else { + b.WriteString(fmt.Sprintf("%q", stringVal)) + } +} diff --git a/vendor/github.com/sirupsen/logrus/writer.go b/vendor/github.com/sirupsen/logrus/writer.go new file mode 100644 index 00000000..72e8e3a1 --- /dev/null +++ b/vendor/github.com/sirupsen/logrus/writer.go @@ -0,0 +1,70 @@ +package logrus + +import ( + "bufio" + "io" + "runtime" +) + +// Writer at INFO level. See WriterLevel for details. +func (logger *Logger) Writer() *io.PipeWriter { + return logger.WriterLevel(InfoLevel) +} + +// WriterLevel returns an io.Writer that can be used to write arbitrary text to +// the logger at the given log level. Each line written to the writer will be +// printed in the usual way using formatters and hooks. The writer is part of an +// io.Pipe and it is the callers responsibility to close the writer when done. +// This can be used to override the standard library logger easily. +func (logger *Logger) WriterLevel(level Level) *io.PipeWriter { + return NewEntry(logger).WriterLevel(level) +} + +func (entry *Entry) Writer() *io.PipeWriter { + return entry.WriterLevel(InfoLevel) +} + +func (entry *Entry) WriterLevel(level Level) *io.PipeWriter { + reader, writer := io.Pipe() + + var printFunc func(args ...interface{}) + + switch level { + case TraceLevel: + printFunc = entry.Trace + case DebugLevel: + printFunc = entry.Debug + case InfoLevel: + printFunc = entry.Info + case WarnLevel: + printFunc = entry.Warn + case ErrorLevel: + printFunc = entry.Error + case FatalLevel: + printFunc = entry.Fatal + case PanicLevel: + printFunc = entry.Panic + default: + printFunc = entry.Print + } + + go entry.writerScanner(reader, printFunc) + runtime.SetFinalizer(writer, writerFinalizer) + + return writer +} + +func (entry *Entry) writerScanner(reader *io.PipeReader, printFunc func(args ...interface{})) { + scanner := bufio.NewScanner(reader) + for scanner.Scan() { + printFunc(scanner.Text()) + } + if err := scanner.Err(); err != nil { + entry.Errorf("Error while reading from Writer: %s", err) + } + reader.Close() +} + +func writerFinalizer(writer *io.PipeWriter) { + writer.Close() +} diff --git a/vendor/golang.org/x/sys/windows/registry/key.go b/vendor/golang.org/x/sys/windows/registry/key.go new file mode 100644 index 00000000..c2564834 --- /dev/null +++ b/vendor/golang.org/x/sys/windows/registry/key.go @@ -0,0 +1,198 @@ +// Copyright 2015 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build windows + +// Package registry provides access to the Windows registry. +// +// Here is a simple example, opening a registry key and reading a string value from it. +// +// k, err := registry.OpenKey(registry.LOCAL_MACHINE, `SOFTWARE\Microsoft\Windows NT\CurrentVersion`, registry.QUERY_VALUE) +// if err != nil { +// log.Fatal(err) +// } +// defer k.Close() +// +// s, _, err := k.GetStringValue("SystemRoot") +// if err != nil { +// log.Fatal(err) +// } +// fmt.Printf("Windows system root is %q\n", s) +// +package registry + +import ( + "io" + "syscall" + "time" +) + +const ( + // Registry key security and access rights. + // See https://msdn.microsoft.com/en-us/library/windows/desktop/ms724878.aspx + // for details. + ALL_ACCESS = 0xf003f + CREATE_LINK = 0x00020 + CREATE_SUB_KEY = 0x00004 + ENUMERATE_SUB_KEYS = 0x00008 + EXECUTE = 0x20019 + NOTIFY = 0x00010 + QUERY_VALUE = 0x00001 + READ = 0x20019 + SET_VALUE = 0x00002 + WOW64_32KEY = 0x00200 + WOW64_64KEY = 0x00100 + WRITE = 0x20006 +) + +// Key is a handle to an open Windows registry key. +// Keys can be obtained by calling OpenKey; there are +// also some predefined root keys such as CURRENT_USER. +// Keys can be used directly in the Windows API. +type Key syscall.Handle + +const ( + // Windows defines some predefined root keys that are always open. + // An application can use these keys as entry points to the registry. + // Normally these keys are used in OpenKey to open new keys, + // but they can also be used anywhere a Key is required. + CLASSES_ROOT = Key(syscall.HKEY_CLASSES_ROOT) + CURRENT_USER = Key(syscall.HKEY_CURRENT_USER) + LOCAL_MACHINE = Key(syscall.HKEY_LOCAL_MACHINE) + USERS = Key(syscall.HKEY_USERS) + CURRENT_CONFIG = Key(syscall.HKEY_CURRENT_CONFIG) + PERFORMANCE_DATA = Key(syscall.HKEY_PERFORMANCE_DATA) +) + +// Close closes open key k. +func (k Key) Close() error { + return syscall.RegCloseKey(syscall.Handle(k)) +} + +// OpenKey opens a new key with path name relative to key k. +// It accepts any open key, including CURRENT_USER and others, +// and returns the new key and an error. +// The access parameter specifies desired access rights to the +// key to be opened. +func OpenKey(k Key, path string, access uint32) (Key, error) { + p, err := syscall.UTF16PtrFromString(path) + if err != nil { + return 0, err + } + var subkey syscall.Handle + err = syscall.RegOpenKeyEx(syscall.Handle(k), p, 0, access, &subkey) + if err != nil { + return 0, err + } + return Key(subkey), nil +} + +// OpenRemoteKey opens a predefined registry key on another +// computer pcname. The key to be opened is specified by k, but +// can only be one of LOCAL_MACHINE, PERFORMANCE_DATA or USERS. +// If pcname is "", OpenRemoteKey returns local computer key. +func OpenRemoteKey(pcname string, k Key) (Key, error) { + var err error + var p *uint16 + if pcname != "" { + p, err = syscall.UTF16PtrFromString(`\\` + pcname) + if err != nil { + return 0, err + } + } + var remoteKey syscall.Handle + err = regConnectRegistry(p, syscall.Handle(k), &remoteKey) + if err != nil { + return 0, err + } + return Key(remoteKey), nil +} + +// ReadSubKeyNames returns the names of subkeys of key k. +// The parameter n controls the number of returned names, +// analogous to the way os.File.Readdirnames works. +func (k Key) ReadSubKeyNames(n int) ([]string, error) { + names := make([]string, 0) + // Registry key size limit is 255 bytes and described there: + // https://msdn.microsoft.com/library/windows/desktop/ms724872.aspx + buf := make([]uint16, 256) //plus extra room for terminating zero byte +loopItems: + for i := uint32(0); ; i++ { + if n > 0 { + if len(names) == n { + return names, nil + } + } + l := uint32(len(buf)) + for { + err := syscall.RegEnumKeyEx(syscall.Handle(k), i, &buf[0], &l, nil, nil, nil, nil) + if err == nil { + break + } + if err == syscall.ERROR_MORE_DATA { + // Double buffer size and try again. + l = uint32(2 * len(buf)) + buf = make([]uint16, l) + continue + } + if err == _ERROR_NO_MORE_ITEMS { + break loopItems + } + return names, err + } + names = append(names, syscall.UTF16ToString(buf[:l])) + } + if n > len(names) { + return names, io.EOF + } + return names, nil +} + +// CreateKey creates a key named path under open key k. +// CreateKey returns the new key and a boolean flag that reports +// whether the key already existed. +// The access parameter specifies the access rights for the key +// to be created. +func CreateKey(k Key, path string, access uint32) (newk Key, openedExisting bool, err error) { + var h syscall.Handle + var d uint32 + err = regCreateKeyEx(syscall.Handle(k), syscall.StringToUTF16Ptr(path), + 0, nil, _REG_OPTION_NON_VOLATILE, access, nil, &h, &d) + if err != nil { + return 0, false, err + } + return Key(h), d == _REG_OPENED_EXISTING_KEY, nil +} + +// DeleteKey deletes the subkey path of key k and its values. +func DeleteKey(k Key, path string) error { + return regDeleteKey(syscall.Handle(k), syscall.StringToUTF16Ptr(path)) +} + +// A KeyInfo describes the statistics of a key. It is returned by Stat. +type KeyInfo struct { + SubKeyCount uint32 + MaxSubKeyLen uint32 // size of the key's subkey with the longest name, in Unicode characters, not including the terminating zero byte + ValueCount uint32 + MaxValueNameLen uint32 // size of the key's longest value name, in Unicode characters, not including the terminating zero byte + MaxValueLen uint32 // longest data component among the key's values, in bytes + lastWriteTime syscall.Filetime +} + +// ModTime returns the key's last write time. +func (ki *KeyInfo) ModTime() time.Time { + return time.Unix(0, ki.lastWriteTime.Nanoseconds()) +} + +// Stat retrieves information about the open key k. +func (k Key) Stat() (*KeyInfo, error) { + var ki KeyInfo + err := syscall.RegQueryInfoKey(syscall.Handle(k), nil, nil, nil, + &ki.SubKeyCount, &ki.MaxSubKeyLen, nil, &ki.ValueCount, + &ki.MaxValueNameLen, &ki.MaxValueLen, nil, &ki.lastWriteTime) + if err != nil { + return nil, err + } + return &ki, nil +} diff --git a/vendor/golang.org/x/sys/windows/registry/mksyscall.go b/vendor/golang.org/x/sys/windows/registry/mksyscall.go new file mode 100644 index 00000000..50c32a3f --- /dev/null +++ b/vendor/golang.org/x/sys/windows/registry/mksyscall.go @@ -0,0 +1,9 @@ +// Copyright 2015 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build generate + +package registry + +//go:generate go run golang.org/x/sys/windows/mkwinsyscall -output zsyscall_windows.go syscall.go diff --git a/vendor/golang.org/x/sys/windows/registry/syscall.go b/vendor/golang.org/x/sys/windows/registry/syscall.go new file mode 100644 index 00000000..e66643cb --- /dev/null +++ b/vendor/golang.org/x/sys/windows/registry/syscall.go @@ -0,0 +1,32 @@ +// Copyright 2015 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build windows + +package registry + +import "syscall" + +const ( + _REG_OPTION_NON_VOLATILE = 0 + + _REG_CREATED_NEW_KEY = 1 + _REG_OPENED_EXISTING_KEY = 2 + + _ERROR_NO_MORE_ITEMS syscall.Errno = 259 +) + +func LoadRegLoadMUIString() error { + return procRegLoadMUIStringW.Find() +} + +//sys regCreateKeyEx(key syscall.Handle, subkey *uint16, reserved uint32, class *uint16, options uint32, desired uint32, sa *syscall.SecurityAttributes, result *syscall.Handle, disposition *uint32) (regerrno error) = advapi32.RegCreateKeyExW +//sys regDeleteKey(key syscall.Handle, subkey *uint16) (regerrno error) = advapi32.RegDeleteKeyW +//sys regSetValueEx(key syscall.Handle, valueName *uint16, reserved uint32, vtype uint32, buf *byte, bufsize uint32) (regerrno error) = advapi32.RegSetValueExW +//sys regEnumValue(key syscall.Handle, index uint32, name *uint16, nameLen *uint32, reserved *uint32, valtype *uint32, buf *byte, buflen *uint32) (regerrno error) = advapi32.RegEnumValueW +//sys regDeleteValue(key syscall.Handle, name *uint16) (regerrno error) = advapi32.RegDeleteValueW +//sys regLoadMUIString(key syscall.Handle, name *uint16, buf *uint16, buflen uint32, buflenCopied *uint32, flags uint32, dir *uint16) (regerrno error) = advapi32.RegLoadMUIStringW +//sys regConnectRegistry(machinename *uint16, key syscall.Handle, result *syscall.Handle) (regerrno error) = advapi32.RegConnectRegistryW + +//sys expandEnvironmentStrings(src *uint16, dst *uint16, size uint32) (n uint32, err error) = kernel32.ExpandEnvironmentStringsW diff --git a/vendor/golang.org/x/sys/windows/registry/value.go b/vendor/golang.org/x/sys/windows/registry/value.go new file mode 100644 index 00000000..f25e7e97 --- /dev/null +++ b/vendor/golang.org/x/sys/windows/registry/value.go @@ -0,0 +1,386 @@ +// Copyright 2015 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build windows + +package registry + +import ( + "errors" + "io" + "syscall" + "unicode/utf16" + "unsafe" +) + +const ( + // Registry value types. + NONE = 0 + SZ = 1 + EXPAND_SZ = 2 + BINARY = 3 + DWORD = 4 + DWORD_BIG_ENDIAN = 5 + LINK = 6 + MULTI_SZ = 7 + RESOURCE_LIST = 8 + FULL_RESOURCE_DESCRIPTOR = 9 + RESOURCE_REQUIREMENTS_LIST = 10 + QWORD = 11 +) + +var ( + // ErrShortBuffer is returned when the buffer was too short for the operation. + ErrShortBuffer = syscall.ERROR_MORE_DATA + + // ErrNotExist is returned when a registry key or value does not exist. + ErrNotExist = syscall.ERROR_FILE_NOT_FOUND + + // ErrUnexpectedType is returned by Get*Value when the value's type was unexpected. + ErrUnexpectedType = errors.New("unexpected key value type") +) + +// GetValue retrieves the type and data for the specified value associated +// with an open key k. It fills up buffer buf and returns the retrieved +// byte count n. If buf is too small to fit the stored value it returns +// ErrShortBuffer error along with the required buffer size n. +// If no buffer is provided, it returns true and actual buffer size n. +// If no buffer is provided, GetValue returns the value's type only. +// If the value does not exist, the error returned is ErrNotExist. +// +// GetValue is a low level function. If value's type is known, use the appropriate +// Get*Value function instead. +func (k Key) GetValue(name string, buf []byte) (n int, valtype uint32, err error) { + pname, err := syscall.UTF16PtrFromString(name) + if err != nil { + return 0, 0, err + } + var pbuf *byte + if len(buf) > 0 { + pbuf = (*byte)(unsafe.Pointer(&buf[0])) + } + l := uint32(len(buf)) + err = syscall.RegQueryValueEx(syscall.Handle(k), pname, nil, &valtype, pbuf, &l) + if err != nil { + return int(l), valtype, err + } + return int(l), valtype, nil +} + +func (k Key) getValue(name string, buf []byte) (data []byte, valtype uint32, err error) { + p, err := syscall.UTF16PtrFromString(name) + if err != nil { + return nil, 0, err + } + var t uint32 + n := uint32(len(buf)) + for { + err = syscall.RegQueryValueEx(syscall.Handle(k), p, nil, &t, (*byte)(unsafe.Pointer(&buf[0])), &n) + if err == nil { + return buf[:n], t, nil + } + if err != syscall.ERROR_MORE_DATA { + return nil, 0, err + } + if n <= uint32(len(buf)) { + return nil, 0, err + } + buf = make([]byte, n) + } +} + +// GetStringValue retrieves the string value for the specified +// value name associated with an open key k. It also returns the value's type. +// If value does not exist, GetStringValue returns ErrNotExist. +// If value is not SZ or EXPAND_SZ, it will return the correct value +// type and ErrUnexpectedType. +func (k Key) GetStringValue(name string) (val string, valtype uint32, err error) { + data, typ, err2 := k.getValue(name, make([]byte, 64)) + if err2 != nil { + return "", typ, err2 + } + switch typ { + case SZ, EXPAND_SZ: + default: + return "", typ, ErrUnexpectedType + } + if len(data) == 0 { + return "", typ, nil + } + u := (*[1 << 29]uint16)(unsafe.Pointer(&data[0]))[: len(data)/2 : len(data)/2] + return syscall.UTF16ToString(u), typ, nil +} + +// GetMUIStringValue retrieves the localized string value for +// the specified value name associated with an open key k. +// If the value name doesn't exist or the localized string value +// can't be resolved, GetMUIStringValue returns ErrNotExist. +// GetMUIStringValue panics if the system doesn't support +// regLoadMUIString; use LoadRegLoadMUIString to check if +// regLoadMUIString is supported before calling this function. +func (k Key) GetMUIStringValue(name string) (string, error) { + pname, err := syscall.UTF16PtrFromString(name) + if err != nil { + return "", err + } + + buf := make([]uint16, 1024) + var buflen uint32 + var pdir *uint16 + + err = regLoadMUIString(syscall.Handle(k), pname, &buf[0], uint32(len(buf)), &buflen, 0, pdir) + if err == syscall.ERROR_FILE_NOT_FOUND { // Try fallback path + + // Try to resolve the string value using the system directory as + // a DLL search path; this assumes the string value is of the form + // @[path]\dllname,-strID but with no path given, e.g. @tzres.dll,-320. + + // This approach works with tzres.dll but may have to be revised + // in the future to allow callers to provide custom search paths. + + var s string + s, err = ExpandString("%SystemRoot%\\system32\\") + if err != nil { + return "", err + } + pdir, err = syscall.UTF16PtrFromString(s) + if err != nil { + return "", err + } + + err = regLoadMUIString(syscall.Handle(k), pname, &buf[0], uint32(len(buf)), &buflen, 0, pdir) + } + + for err == syscall.ERROR_MORE_DATA { // Grow buffer if needed + if buflen <= uint32(len(buf)) { + break // Buffer not growing, assume race; break + } + buf = make([]uint16, buflen) + err = regLoadMUIString(syscall.Handle(k), pname, &buf[0], uint32(len(buf)), &buflen, 0, pdir) + } + + if err != nil { + return "", err + } + + return syscall.UTF16ToString(buf), nil +} + +// ExpandString expands environment-variable strings and replaces +// them with the values defined for the current user. +// Use ExpandString to expand EXPAND_SZ strings. +func ExpandString(value string) (string, error) { + if value == "" { + return "", nil + } + p, err := syscall.UTF16PtrFromString(value) + if err != nil { + return "", err + } + r := make([]uint16, 100) + for { + n, err := expandEnvironmentStrings(p, &r[0], uint32(len(r))) + if err != nil { + return "", err + } + if n <= uint32(len(r)) { + return syscall.UTF16ToString(r[:n]), nil + } + r = make([]uint16, n) + } +} + +// GetStringsValue retrieves the []string value for the specified +// value name associated with an open key k. It also returns the value's type. +// If value does not exist, GetStringsValue returns ErrNotExist. +// If value is not MULTI_SZ, it will return the correct value +// type and ErrUnexpectedType. +func (k Key) GetStringsValue(name string) (val []string, valtype uint32, err error) { + data, typ, err2 := k.getValue(name, make([]byte, 64)) + if err2 != nil { + return nil, typ, err2 + } + if typ != MULTI_SZ { + return nil, typ, ErrUnexpectedType + } + if len(data) == 0 { + return nil, typ, nil + } + p := (*[1 << 29]uint16)(unsafe.Pointer(&data[0]))[: len(data)/2 : len(data)/2] + if len(p) == 0 { + return nil, typ, nil + } + if p[len(p)-1] == 0 { + p = p[:len(p)-1] // remove terminating null + } + val = make([]string, 0, 5) + from := 0 + for i, c := range p { + if c == 0 { + val = append(val, string(utf16.Decode(p[from:i]))) + from = i + 1 + } + } + return val, typ, nil +} + +// GetIntegerValue retrieves the integer value for the specified +// value name associated with an open key k. It also returns the value's type. +// If value does not exist, GetIntegerValue returns ErrNotExist. +// If value is not DWORD or QWORD, it will return the correct value +// type and ErrUnexpectedType. +func (k Key) GetIntegerValue(name string) (val uint64, valtype uint32, err error) { + data, typ, err2 := k.getValue(name, make([]byte, 8)) + if err2 != nil { + return 0, typ, err2 + } + switch typ { + case DWORD: + if len(data) != 4 { + return 0, typ, errors.New("DWORD value is not 4 bytes long") + } + var val32 uint32 + copy((*[4]byte)(unsafe.Pointer(&val32))[:], data) + return uint64(val32), DWORD, nil + case QWORD: + if len(data) != 8 { + return 0, typ, errors.New("QWORD value is not 8 bytes long") + } + copy((*[8]byte)(unsafe.Pointer(&val))[:], data) + return val, QWORD, nil + default: + return 0, typ, ErrUnexpectedType + } +} + +// GetBinaryValue retrieves the binary value for the specified +// value name associated with an open key k. It also returns the value's type. +// If value does not exist, GetBinaryValue returns ErrNotExist. +// If value is not BINARY, it will return the correct value +// type and ErrUnexpectedType. +func (k Key) GetBinaryValue(name string) (val []byte, valtype uint32, err error) { + data, typ, err2 := k.getValue(name, make([]byte, 64)) + if err2 != nil { + return nil, typ, err2 + } + if typ != BINARY { + return nil, typ, ErrUnexpectedType + } + return data, typ, nil +} + +func (k Key) setValue(name string, valtype uint32, data []byte) error { + p, err := syscall.UTF16PtrFromString(name) + if err != nil { + return err + } + if len(data) == 0 { + return regSetValueEx(syscall.Handle(k), p, 0, valtype, nil, 0) + } + return regSetValueEx(syscall.Handle(k), p, 0, valtype, &data[0], uint32(len(data))) +} + +// SetDWordValue sets the data and type of a name value +// under key k to value and DWORD. +func (k Key) SetDWordValue(name string, value uint32) error { + return k.setValue(name, DWORD, (*[4]byte)(unsafe.Pointer(&value))[:]) +} + +// SetQWordValue sets the data and type of a name value +// under key k to value and QWORD. +func (k Key) SetQWordValue(name string, value uint64) error { + return k.setValue(name, QWORD, (*[8]byte)(unsafe.Pointer(&value))[:]) +} + +func (k Key) setStringValue(name string, valtype uint32, value string) error { + v, err := syscall.UTF16FromString(value) + if err != nil { + return err + } + buf := (*[1 << 29]byte)(unsafe.Pointer(&v[0]))[: len(v)*2 : len(v)*2] + return k.setValue(name, valtype, buf) +} + +// SetStringValue sets the data and type of a name value +// under key k to value and SZ. The value must not contain a zero byte. +func (k Key) SetStringValue(name, value string) error { + return k.setStringValue(name, SZ, value) +} + +// SetExpandStringValue sets the data and type of a name value +// under key k to value and EXPAND_SZ. The value must not contain a zero byte. +func (k Key) SetExpandStringValue(name, value string) error { + return k.setStringValue(name, EXPAND_SZ, value) +} + +// SetStringsValue sets the data and type of a name value +// under key k to value and MULTI_SZ. The value strings +// must not contain a zero byte. +func (k Key) SetStringsValue(name string, value []string) error { + ss := "" + for _, s := range value { + for i := 0; i < len(s); i++ { + if s[i] == 0 { + return errors.New("string cannot have 0 inside") + } + } + ss += s + "\x00" + } + v := utf16.Encode([]rune(ss + "\x00")) + buf := (*[1 << 29]byte)(unsafe.Pointer(&v[0]))[: len(v)*2 : len(v)*2] + return k.setValue(name, MULTI_SZ, buf) +} + +// SetBinaryValue sets the data and type of a name value +// under key k to value and BINARY. +func (k Key) SetBinaryValue(name string, value []byte) error { + return k.setValue(name, BINARY, value) +} + +// DeleteValue removes a named value from the key k. +func (k Key) DeleteValue(name string) error { + return regDeleteValue(syscall.Handle(k), syscall.StringToUTF16Ptr(name)) +} + +// ReadValueNames returns the value names of key k. +// The parameter n controls the number of returned names, +// analogous to the way os.File.Readdirnames works. +func (k Key) ReadValueNames(n int) ([]string, error) { + ki, err := k.Stat() + if err != nil { + return nil, err + } + names := make([]string, 0, ki.ValueCount) + buf := make([]uint16, ki.MaxValueNameLen+1) // extra room for terminating null character +loopItems: + for i := uint32(0); ; i++ { + if n > 0 { + if len(names) == n { + return names, nil + } + } + l := uint32(len(buf)) + for { + err := regEnumValue(syscall.Handle(k), i, &buf[0], &l, nil, nil, nil, nil) + if err == nil { + break + } + if err == syscall.ERROR_MORE_DATA { + // Double buffer size and try again. + l = uint32(2 * len(buf)) + buf = make([]uint16, l) + continue + } + if err == _ERROR_NO_MORE_ITEMS { + break loopItems + } + return names, err + } + names = append(names, syscall.UTF16ToString(buf[:l])) + } + if n > len(names) { + return names, io.EOF + } + return names, nil +} diff --git a/vendor/golang.org/x/sys/windows/registry/zsyscall_windows.go b/vendor/golang.org/x/sys/windows/registry/zsyscall_windows.go new file mode 100644 index 00000000..3778075d --- /dev/null +++ b/vendor/golang.org/x/sys/windows/registry/zsyscall_windows.go @@ -0,0 +1,120 @@ +// Code generated by 'go generate'; DO NOT EDIT. + +package registry + +import ( + "syscall" + "unsafe" + + "golang.org/x/sys/windows" +) + +var _ unsafe.Pointer + +// Do the interface allocations only once for common +// Errno values. +const ( + errnoERROR_IO_PENDING = 997 +) + +var ( + errERROR_IO_PENDING error = syscall.Errno(errnoERROR_IO_PENDING) +) + +// errnoErr returns common boxed Errno values, to prevent +// allocations at runtime. +func errnoErr(e syscall.Errno) error { + switch e { + case 0: + return nil + case errnoERROR_IO_PENDING: + return errERROR_IO_PENDING + } + // TODO: add more here, after collecting data on the common + // error values see on Windows. (perhaps when running + // all.bat?) + return e +} + +var ( + modadvapi32 = windows.NewLazySystemDLL("advapi32.dll") + modkernel32 = windows.NewLazySystemDLL("kernel32.dll") + + procRegCreateKeyExW = modadvapi32.NewProc("RegCreateKeyExW") + procRegDeleteKeyW = modadvapi32.NewProc("RegDeleteKeyW") + procRegSetValueExW = modadvapi32.NewProc("RegSetValueExW") + procRegEnumValueW = modadvapi32.NewProc("RegEnumValueW") + procRegDeleteValueW = modadvapi32.NewProc("RegDeleteValueW") + procRegLoadMUIStringW = modadvapi32.NewProc("RegLoadMUIStringW") + procRegConnectRegistryW = modadvapi32.NewProc("RegConnectRegistryW") + procExpandEnvironmentStringsW = modkernel32.NewProc("ExpandEnvironmentStringsW") +) + +func regCreateKeyEx(key syscall.Handle, subkey *uint16, reserved uint32, class *uint16, options uint32, desired uint32, sa *syscall.SecurityAttributes, result *syscall.Handle, disposition *uint32) (regerrno error) { + r0, _, _ := syscall.Syscall9(procRegCreateKeyExW.Addr(), 9, uintptr(key), uintptr(unsafe.Pointer(subkey)), uintptr(reserved), uintptr(unsafe.Pointer(class)), uintptr(options), uintptr(desired), uintptr(unsafe.Pointer(sa)), uintptr(unsafe.Pointer(result)), uintptr(unsafe.Pointer(disposition))) + if r0 != 0 { + regerrno = syscall.Errno(r0) + } + return +} + +func regDeleteKey(key syscall.Handle, subkey *uint16) (regerrno error) { + r0, _, _ := syscall.Syscall(procRegDeleteKeyW.Addr(), 2, uintptr(key), uintptr(unsafe.Pointer(subkey)), 0) + if r0 != 0 { + regerrno = syscall.Errno(r0) + } + return +} + +func regSetValueEx(key syscall.Handle, valueName *uint16, reserved uint32, vtype uint32, buf *byte, bufsize uint32) (regerrno error) { + r0, _, _ := syscall.Syscall6(procRegSetValueExW.Addr(), 6, uintptr(key), uintptr(unsafe.Pointer(valueName)), uintptr(reserved), uintptr(vtype), uintptr(unsafe.Pointer(buf)), uintptr(bufsize)) + if r0 != 0 { + regerrno = syscall.Errno(r0) + } + return +} + +func regEnumValue(key syscall.Handle, index uint32, name *uint16, nameLen *uint32, reserved *uint32, valtype *uint32, buf *byte, buflen *uint32) (regerrno error) { + r0, _, _ := syscall.Syscall9(procRegEnumValueW.Addr(), 8, uintptr(key), uintptr(index), uintptr(unsafe.Pointer(name)), uintptr(unsafe.Pointer(nameLen)), uintptr(unsafe.Pointer(reserved)), uintptr(unsafe.Pointer(valtype)), uintptr(unsafe.Pointer(buf)), uintptr(unsafe.Pointer(buflen)), 0) + if r0 != 0 { + regerrno = syscall.Errno(r0) + } + return +} + +func regDeleteValue(key syscall.Handle, name *uint16) (regerrno error) { + r0, _, _ := syscall.Syscall(procRegDeleteValueW.Addr(), 2, uintptr(key), uintptr(unsafe.Pointer(name)), 0) + if r0 != 0 { + regerrno = syscall.Errno(r0) + } + return +} + +func regLoadMUIString(key syscall.Handle, name *uint16, buf *uint16, buflen uint32, buflenCopied *uint32, flags uint32, dir *uint16) (regerrno error) { + r0, _, _ := syscall.Syscall9(procRegLoadMUIStringW.Addr(), 7, uintptr(key), uintptr(unsafe.Pointer(name)), uintptr(unsafe.Pointer(buf)), uintptr(buflen), uintptr(unsafe.Pointer(buflenCopied)), uintptr(flags), uintptr(unsafe.Pointer(dir)), 0, 0) + if r0 != 0 { + regerrno = syscall.Errno(r0) + } + return +} + +func regConnectRegistry(machinename *uint16, key syscall.Handle, result *syscall.Handle) (regerrno error) { + r0, _, _ := syscall.Syscall(procRegConnectRegistryW.Addr(), 3, uintptr(unsafe.Pointer(machinename)), uintptr(key), uintptr(unsafe.Pointer(result))) + if r0 != 0 { + regerrno = syscall.Errno(r0) + } + return +} + +func expandEnvironmentStrings(src *uint16, dst *uint16, size uint32) (n uint32, err error) { + r0, _, e1 := syscall.Syscall(procExpandEnvironmentStringsW.Addr(), 3, uintptr(unsafe.Pointer(src)), uintptr(unsafe.Pointer(dst)), uintptr(size)) + n = uint32(r0) + if n == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} diff --git a/vendor/golang.org/x/sys/windows/svc/eventlog/install.go b/vendor/golang.org/x/sys/windows/svc/eventlog/install.go new file mode 100644 index 00000000..c76a3760 --- /dev/null +++ b/vendor/golang.org/x/sys/windows/svc/eventlog/install.go @@ -0,0 +1,80 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build windows + +package eventlog + +import ( + "errors" + + "golang.org/x/sys/windows" + "golang.org/x/sys/windows/registry" +) + +const ( + // Log levels. + Info = windows.EVENTLOG_INFORMATION_TYPE + Warning = windows.EVENTLOG_WARNING_TYPE + Error = windows.EVENTLOG_ERROR_TYPE +) + +const addKeyName = `SYSTEM\CurrentControlSet\Services\EventLog\Application` + +// Install modifies PC registry to allow logging with an event source src. +// It adds all required keys and values to the event log registry key. +// Install uses msgFile as the event message file. If useExpandKey is true, +// the event message file is installed as REG_EXPAND_SZ value, +// otherwise as REG_SZ. Use bitwise of log.Error, log.Warning and +// log.Info to specify events supported by the new event source. +func Install(src, msgFile string, useExpandKey bool, eventsSupported uint32) error { + appkey, err := registry.OpenKey(registry.LOCAL_MACHINE, addKeyName, registry.CREATE_SUB_KEY) + if err != nil { + return err + } + defer appkey.Close() + + sk, alreadyExist, err := registry.CreateKey(appkey, src, registry.SET_VALUE) + if err != nil { + return err + } + defer sk.Close() + if alreadyExist { + return errors.New(addKeyName + `\` + src + " registry key already exists") + } + + err = sk.SetDWordValue("CustomSource", 1) + if err != nil { + return err + } + if useExpandKey { + err = sk.SetExpandStringValue("EventMessageFile", msgFile) + } else { + err = sk.SetStringValue("EventMessageFile", msgFile) + } + if err != nil { + return err + } + err = sk.SetDWordValue("TypesSupported", eventsSupported) + if err != nil { + return err + } + return nil +} + +// InstallAsEventCreate is the same as Install, but uses +// %SystemRoot%\System32\EventCreate.exe as the event message file. +func InstallAsEventCreate(src string, eventsSupported uint32) error { + return Install(src, "%SystemRoot%\\System32\\EventCreate.exe", true, eventsSupported) +} + +// Remove deletes all registry elements installed by the correspondent Install. +func Remove(src string) error { + appkey, err := registry.OpenKey(registry.LOCAL_MACHINE, addKeyName, registry.SET_VALUE) + if err != nil { + return err + } + defer appkey.Close() + return registry.DeleteKey(appkey, src) +} diff --git a/vendor/golang.org/x/sys/windows/svc/eventlog/log.go b/vendor/golang.org/x/sys/windows/svc/eventlog/log.go new file mode 100644 index 00000000..46e5153d --- /dev/null +++ b/vendor/golang.org/x/sys/windows/svc/eventlog/log.go @@ -0,0 +1,70 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build windows + +// Package eventlog implements access to Windows event log. +// +package eventlog + +import ( + "errors" + "syscall" + + "golang.org/x/sys/windows" +) + +// Log provides access to the system log. +type Log struct { + Handle windows.Handle +} + +// Open retrieves a handle to the specified event log. +func Open(source string) (*Log, error) { + return OpenRemote("", source) +} + +// OpenRemote does the same as Open, but on different computer host. +func OpenRemote(host, source string) (*Log, error) { + if source == "" { + return nil, errors.New("Specify event log source") + } + var s *uint16 + if host != "" { + s = syscall.StringToUTF16Ptr(host) + } + h, err := windows.RegisterEventSource(s, syscall.StringToUTF16Ptr(source)) + if err != nil { + return nil, err + } + return &Log{Handle: h}, nil +} + +// Close closes event log l. +func (l *Log) Close() error { + return windows.DeregisterEventSource(l.Handle) +} + +func (l *Log) report(etype uint16, eid uint32, msg string) error { + ss := []*uint16{syscall.StringToUTF16Ptr(msg)} + return windows.ReportEvent(l.Handle, etype, 0, eid, 0, 1, 0, &ss[0], nil) +} + +// Info writes an information event msg with event id eid to the end of event log l. +// When EventCreate.exe is used, eid must be between 1 and 1000. +func (l *Log) Info(eid uint32, msg string) error { + return l.report(windows.EVENTLOG_INFORMATION_TYPE, eid, msg) +} + +// Warning writes an warning event msg with event id eid to the end of event log l. +// When EventCreate.exe is used, eid must be between 1 and 1000. +func (l *Log) Warning(eid uint32, msg string) error { + return l.report(windows.EVENTLOG_WARNING_TYPE, eid, msg) +} + +// Error writes an error event msg with event id eid to the end of event log l. +// When EventCreate.exe is used, eid must be between 1 and 1000. +func (l *Log) Error(eid uint32, msg string) error { + return l.report(windows.EVENTLOG_ERROR_TYPE, eid, msg) +} diff --git a/vendor/gopkg.in/alecthomas/kingpin.v2/.travis.yml b/vendor/gopkg.in/alecthomas/kingpin.v2/.travis.yml new file mode 100644 index 00000000..e564b743 --- /dev/null +++ b/vendor/gopkg.in/alecthomas/kingpin.v2/.travis.yml @@ -0,0 +1,4 @@ +sudo: false +language: go +install: go get -t -v ./... +go: 1.2 diff --git a/vendor/gopkg.in/alecthomas/kingpin.v2/COPYING b/vendor/gopkg.in/alecthomas/kingpin.v2/COPYING new file mode 100644 index 00000000..2993ec08 --- /dev/null +++ b/vendor/gopkg.in/alecthomas/kingpin.v2/COPYING @@ -0,0 +1,19 @@ +Copyright (C) 2014 Alec Thomas + +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. diff --git a/vendor/gopkg.in/alecthomas/kingpin.v2/README.md b/vendor/gopkg.in/alecthomas/kingpin.v2/README.md new file mode 100644 index 00000000..498704c8 --- /dev/null +++ b/vendor/gopkg.in/alecthomas/kingpin.v2/README.md @@ -0,0 +1,674 @@ +# Kingpin - A Go (golang) command line and flag parser +[![](https://godoc.org/github.com/alecthomas/kingpin?status.svg)](http://godoc.org/github.com/alecthomas/kingpin) [![Build Status](https://travis-ci.org/alecthomas/kingpin.svg?branch=master)](https://travis-ci.org/alecthomas/kingpin) [![Gitter chat](https://badges.gitter.im/alecthomas.png)](https://gitter.im/alecthomas/Lobby) + + + + + +- [Overview](#overview) +- [Features](#features) +- [User-visible changes between v1 and v2](#user-visible-changes-between-v1-and-v2) + - [Flags can be used at any point after their definition.](#flags-can-be-used-at-any-point-after-their-definition) + - [Short flags can be combined with their parameters](#short-flags-can-be-combined-with-their-parameters) +- [API changes between v1 and v2](#api-changes-between-v1-and-v2) +- [Versions](#versions) + - [V2 is the current stable version](#v2-is-the-current-stable-version) + - [V1 is the OLD stable version](#v1-is-the-old-stable-version) +- [Change History](#change-history) +- [Examples](#examples) + - [Simple Example](#simple-example) + - [Complex Example](#complex-example) +- [Reference Documentation](#reference-documentation) + - [Displaying errors and usage information](#displaying-errors-and-usage-information) + - [Sub-commands](#sub-commands) + - [Custom Parsers](#custom-parsers) + - [Repeatable flags](#repeatable-flags) + - [Boolean Values](#boolean-values) + - [Default Values](#default-values) + - [Place-holders in Help](#place-holders-in-help) + - [Consuming all remaining arguments](#consuming-all-remaining-arguments) + - [Bash/ZSH Shell Completion](#bashzsh-shell-completion) + - [Supporting -h for help](#supporting--h-for-help) + - [Custom help](#custom-help) + + + +## Overview + +Kingpin is a [fluent-style](http://en.wikipedia.org/wiki/Fluent_interface), +type-safe command-line parser. It supports flags, nested commands, and +positional arguments. + +Install it with: + + $ go get gopkg.in/alecthomas/kingpin.v2 + +It looks like this: + +```go +var ( + verbose = kingpin.Flag("verbose", "Verbose mode.").Short('v').Bool() + name = kingpin.Arg("name", "Name of user.").Required().String() +) + +func main() { + kingpin.Parse() + fmt.Printf("%v, %s\n", *verbose, *name) +} +``` + +More [examples](https://github.com/alecthomas/kingpin/tree/master/_examples) are available. + +Second to parsing, providing the user with useful help is probably the most +important thing a command-line parser does. Kingpin tries to provide detailed +contextual help if `--help` is encountered at any point in the command line +(excluding after `--`). + +## Features + +- Help output that isn't as ugly as sin. +- Fully [customisable help](#custom-help), via Go templates. +- Parsed, type-safe flags (`kingpin.Flag("f", "help").Int()`) +- Parsed, type-safe positional arguments (`kingpin.Arg("a", "help").Int()`). +- Parsed, type-safe, arbitrarily deep commands (`kingpin.Command("c", "help")`). +- Support for required flags and required positional arguments (`kingpin.Flag("f", "").Required().Int()`). +- Support for arbitrarily nested default commands (`command.Default()`). +- Callbacks per command, flag and argument (`kingpin.Command("c", "").Action(myAction)`). +- POSIX-style short flag combining (`-a -b` -> `-ab`). +- Short-flag+parameter combining (`-a parm` -> `-aparm`). +- Read command-line from files (`@`). +- Automatically generate man pages (`--help-man`). + +## User-visible changes between v1 and v2 + +### Flags can be used at any point after their definition. + +Flags can be specified at any point after their definition, not just +*immediately after their associated command*. From the chat example below, the +following used to be required: + +``` +$ chat --server=chat.server.com:8080 post --image=~/Downloads/owls.jpg pics +``` + +But the following will now work: + +``` +$ chat post --server=chat.server.com:8080 --image=~/Downloads/owls.jpg pics +``` + +### Short flags can be combined with their parameters + +Previously, if a short flag was used, any argument to that flag would have to +be separated by a space. That is no longer the case. + +## API changes between v1 and v2 + +- `ParseWithFileExpansion()` is gone. The new parser directly supports expanding `@`. +- Added `FatalUsage()` and `FatalUsageContext()` for displaying an error + usage and terminating. +- `Dispatch()` renamed to `Action()`. +- Added `ParseContext()` for parsing a command line into its intermediate context form without executing. +- Added `Terminate()` function to override the termination function. +- Added `UsageForContextWithTemplate()` for printing usage via a custom template. +- Added `UsageTemplate()` for overriding the default template to use. Two templates are included: + 1. `DefaultUsageTemplate` - default template. + 2. `CompactUsageTemplate` - compact command template for larger applications. + +## Versions + +Kingpin uses [gopkg.in](https://gopkg.in/alecthomas/kingpin) for versioning. + +The current stable version is [gopkg.in/alecthomas/kingpin.v2](https://gopkg.in/alecthomas/kingpin.v2). The previous version, [gopkg.in/alecthomas/kingpin.v1](https://gopkg.in/alecthomas/kingpin.v1), is deprecated and in maintenance mode. + +### [V2](https://gopkg.in/alecthomas/kingpin.v2) is the current stable version + +Installation: + +```sh +$ go get gopkg.in/alecthomas/kingpin.v2 +``` + +### [V1](https://gopkg.in/alecthomas/kingpin.v1) is the OLD stable version + +Installation: + +```sh +$ go get gopkg.in/alecthomas/kingpin.v1 +``` + +## Change History + +- *2015-09-19* -- Stable v2.1.0 release. + - Added `command.Default()` to specify a default command to use if no other + command matches. This allows for convenient user shortcuts. + - Exposed `HelpFlag` and `VersionFlag` for further customisation. + - `Action()` and `PreAction()` added and both now support an arbitrary + number of callbacks. + - `kingpin.SeparateOptionalFlagsUsageTemplate`. + - `--help-long` and `--help-man` (hidden by default) flags. + - Flags are "interspersed" by default, but can be disabled with `app.Interspersed(false)`. + - Added flags for all simple builtin types (int8, uint16, etc.) and slice variants. + - Use `app.Writer(os.Writer)` to specify the default writer for all output functions. + - Dropped `os.Writer` prefix from all printf-like functions. + +- *2015-05-22* -- Stable v2.0.0 release. + - Initial stable release of v2.0.0. + - Fully supports interspersed flags, commands and arguments. + - Flags can be present at any point after their logical definition. + - Application.Parse() terminates if commands are present and a command is not parsed. + - Dispatch() -> Action(). + - Actions are dispatched after all values are populated. + - Override termination function (defaults to os.Exit). + - Override output stream (defaults to os.Stderr). + - Templatised usage help, with default and compact templates. + - Make error/usage functions more consistent. + - Support argument expansion from files by default (with @). + - Fully public data model is available via .Model(). + - Parser has been completely refactored. + - Parsing and execution has been split into distinct stages. + - Use `go generate` to generate repeated flags. + - Support combined short-flag+argument: -fARG. + +- *2015-01-23* -- Stable v1.3.4 release. + - Support "--" for separating flags from positional arguments. + - Support loading flags from files (ParseWithFileExpansion()). Use @FILE as an argument. + - Add post-app and post-cmd validation hooks. This allows arbitrary validation to be added. + - A bunch of improvements to help usage and formatting. + - Support arbitrarily nested sub-commands. + +- *2014-07-08* -- Stable v1.2.0 release. + - Pass any value through to `Strings()` when final argument. + Allows for values that look like flags to be processed. + - Allow `--help` to be used with commands. + - Support `Hidden()` flags. + - Parser for [units.Base2Bytes](https://github.com/alecthomas/units) + type. Allows for flags like `--ram=512MB` or `--ram=1GB`. + - Add an `Enum()` value, allowing only one of a set of values + to be selected. eg. `Flag(...).Enum("debug", "info", "warning")`. + +- *2014-06-27* -- Stable v1.1.0 release. + - Bug fixes. + - Always return an error (rather than panicing) when misconfigured. + - `OpenFile(flag, perm)` value type added, for finer control over opening files. + - Significantly improved usage formatting. + +- *2014-06-19* -- Stable v1.0.0 release. + - Support [cumulative positional](#consuming-all-remaining-arguments) arguments. + - Return error rather than panic when there are fatal errors not caught by + the type system. eg. when a default value is invalid. + - Use gokpg.in. + +- *2014-06-10* -- Place-holder streamlining. + - Renamed `MetaVar` to `PlaceHolder`. + - Removed `MetaVarFromDefault`. Kingpin now uses [heuristics](#place-holders-in-help) + to determine what to display. + +## Examples + +### Simple Example + +Kingpin can be used for simple flag+arg applications like so: + +``` +$ ping --help +usage: ping [] [] + +Flags: + --debug Enable debug mode. + --help Show help. + -t, --timeout=5s Timeout waiting for ping. + +Args: + IP address to ping. + [] Number of packets to send +$ ping 1.2.3.4 5 +Would ping: 1.2.3.4 with timeout 5s and count 5 +``` + +From the following source: + +```go +package main + +import ( + "fmt" + + "gopkg.in/alecthomas/kingpin.v2" +) + +var ( + debug = kingpin.Flag("debug", "Enable debug mode.").Bool() + timeout = kingpin.Flag("timeout", "Timeout waiting for ping.").Default("5s").OverrideDefaultFromEnvar("PING_TIMEOUT").Short('t').Duration() + ip = kingpin.Arg("ip", "IP address to ping.").Required().IP() + count = kingpin.Arg("count", "Number of packets to send").Int() +) + +func main() { + kingpin.Version("0.0.1") + kingpin.Parse() + fmt.Printf("Would ping: %s with timeout %s and count %d\n", *ip, *timeout, *count) +} +``` + +### Complex Example + +Kingpin can also produce complex command-line applications with global flags, +subcommands, and per-subcommand flags, like this: + +``` +$ chat --help +usage: chat [] [] [ ...] + +A command-line chat application. + +Flags: + --help Show help. + --debug Enable debug mode. + --server=127.0.0.1 Server address. + +Commands: + help [] + Show help for a command. + + register + Register a new user. + + post [] [] + Post a message to a channel. + +$ chat help post +usage: chat [] post [] [] + +Post a message to a channel. + +Flags: + --image=IMAGE Image to post. + +Args: + Channel to post to. + [] Text to post. + +$ chat post --image=~/Downloads/owls.jpg pics +... +``` + +From this code: + +```go +package main + +import ( + "os" + "strings" + "gopkg.in/alecthomas/kingpin.v2" +) + +var ( + app = kingpin.New("chat", "A command-line chat application.") + debug = app.Flag("debug", "Enable debug mode.").Bool() + serverIP = app.Flag("server", "Server address.").Default("127.0.0.1").IP() + + register = app.Command("register", "Register a new user.") + registerNick = register.Arg("nick", "Nickname for user.").Required().String() + registerName = register.Arg("name", "Name of user.").Required().String() + + post = app.Command("post", "Post a message to a channel.") + postImage = post.Flag("image", "Image to post.").File() + postChannel = post.Arg("channel", "Channel to post to.").Required().String() + postText = post.Arg("text", "Text to post.").Strings() +) + +func main() { + switch kingpin.MustParse(app.Parse(os.Args[1:])) { + // Register user + case register.FullCommand(): + println(*registerNick) + + // Post message + case post.FullCommand(): + if *postImage != nil { + } + text := strings.Join(*postText, " ") + println("Post:", text) + } +} +``` + +## Reference Documentation + +### Displaying errors and usage information + +Kingpin exports a set of functions to provide consistent errors and usage +information to the user. + +Error messages look something like this: + + : error: + +The functions on `Application` are: + +Function | Purpose +---------|-------------- +`Errorf(format, args)` | Display a printf formatted error to the user. +`Fatalf(format, args)` | As with Errorf, but also call the termination handler. +`FatalUsage(format, args)` | As with Fatalf, but also print contextual usage information. +`FatalUsageContext(context, format, args)` | As with Fatalf, but also print contextual usage information from a `ParseContext`. +`FatalIfError(err, format, args)` | Conditionally print an error prefixed with format+args, then call the termination handler + +There are equivalent global functions in the kingpin namespace for the default +`kingpin.CommandLine` instance. + +### Sub-commands + +Kingpin supports nested sub-commands, with separate flag and positional +arguments per sub-command. Note that positional arguments may only occur after +sub-commands. + +For example: + +```go +var ( + deleteCommand = kingpin.Command("delete", "Delete an object.") + deleteUserCommand = deleteCommand.Command("user", "Delete a user.") + deleteUserUIDFlag = deleteUserCommand.Flag("uid", "Delete user by UID rather than username.") + deleteUserUsername = deleteUserCommand.Arg("username", "Username to delete.") + deletePostCommand = deleteCommand.Command("post", "Delete a post.") +) + +func main() { + switch kingpin.Parse() { + case "delete user": + case "delete post": + } +} +``` + +### Custom Parsers + +Kingpin supports both flag and positional argument parsers for converting to +Go types. For example, some included parsers are `Int()`, `Float()`, +`Duration()` and `ExistingFile()` (see [parsers.go](./parsers.go) for a complete list of included parsers). + +Parsers conform to Go's [`flag.Value`](http://godoc.org/flag#Value) +interface, so any existing implementations will work. + +For example, a parser for accumulating HTTP header values might look like this: + +```go +type HTTPHeaderValue http.Header + +func (h *HTTPHeaderValue) Set(value string) error { + parts := strings.SplitN(value, ":", 2) + if len(parts) != 2 { + return fmt.Errorf("expected HEADER:VALUE got '%s'", value) + } + (*http.Header)(h).Add(parts[0], parts[1]) + return nil +} + +func (h *HTTPHeaderValue) String() string { + return "" +} +``` + +As a convenience, I would recommend something like this: + +```go +func HTTPHeader(s Settings) (target *http.Header) { + target = &http.Header{} + s.SetValue((*HTTPHeaderValue)(target)) + return +} +``` + +You would use it like so: + +```go +headers = HTTPHeader(kingpin.Flag("header", "Add a HTTP header to the request.").Short('H')) +``` + +### Repeatable flags + +Depending on the `Value` they hold, some flags may be repeated. The +`IsCumulative() bool` function on `Value` tells if it's safe to call `Set()` +multiple times or if an error should be raised if several values are passed. + +The built-in `Value`s returning slices and maps, as well as `Counter` are +examples of `Value`s that make a flag repeatable. + +### Boolean values + +Boolean values are uniquely managed by Kingpin. Each boolean flag will have a negative complement: +`--` and `--no-`. + +### Default Values + +The default value is the zero value for a type. This can be overridden with +the `Default(value...)` function on flags and arguments. This function accepts +one or several strings, which are parsed by the value itself, so they *must* +be compliant with the format expected. + +### Place-holders in Help + +The place-holder value for a flag is the value used in the help to describe +the value of a non-boolean flag. + +The value provided to PlaceHolder() is used if provided, then the value +provided by Default() if provided, then finally the capitalised flag name is +used. + +Here are some examples of flags with various permutations: + + --name=NAME // Flag(...).String() + --name="Harry" // Flag(...).Default("Harry").String() + --name=FULL-NAME // Flag(...).PlaceHolder("FULL-NAME").Default("Harry").String() + +### Consuming all remaining arguments + +A common command-line idiom is to use all remaining arguments for some +purpose. eg. The following command accepts an arbitrary number of +IP addresses as positional arguments: + + ./cmd ping 10.1.1.1 192.168.1.1 + +Such arguments are similar to [repeatable flags](#repeatable-flags), but for +arguments. Therefore they use the same `IsCumulative() bool` function on the +underlying `Value`, so the built-in `Value`s for which the `Set()` function +can be called several times will consume multiple arguments. + +To implement the above example with a custom `Value`, we might do something +like this: + +```go +type ipList []net.IP + +func (i *ipList) Set(value string) error { + if ip := net.ParseIP(value); ip == nil { + return fmt.Errorf("'%s' is not an IP address", value) + } else { + *i = append(*i, ip) + return nil + } +} + +func (i *ipList) String() string { + return "" +} + +func (i *ipList) IsCumulative() bool { + return true +} + +func IPList(s Settings) (target *[]net.IP) { + target = new([]net.IP) + s.SetValue((*ipList)(target)) + return +} +``` + +And use it like so: + +```go +ips := IPList(kingpin.Arg("ips", "IP addresses to ping.")) +``` + +### Bash/ZSH Shell Completion + +By default, all flags and commands/subcommands generate completions +internally. + +Out of the box, CLI tools using kingpin should be able to take advantage +of completion hinting for flags and commands. By specifying +`--completion-bash` as the first argument, your CLI tool will show +possible subcommands. By ending your argv with `--`, hints for flags +will be shown. + +To allow your end users to take advantage you must package a +`/etc/bash_completion.d` script with your distribution (or the equivalent +for your target platform/shell). An alternative is to instruct your end +user to source a script from their `bash_profile` (or equivalent). + +Fortunately Kingpin makes it easy to generate or source a script for use +with end users shells. `./yourtool --completion-script-bash` and +`./yourtool --completion-script-zsh` will generate these scripts for you. + +**Installation by Package** + +For the best user experience, you should bundle your pre-created +completion script with your CLI tool and install it inside +`/etc/bash_completion.d` (or equivalent). A good suggestion is to add +this as an automated step to your build pipeline, in the implementation +is improved for bug fixed. + +**Installation by `bash_profile`** + +Alternatively, instruct your users to add an additional statement to +their `bash_profile` (or equivalent): + +``` +eval "$(your-cli-tool --completion-script-bash)" +``` + +Or for ZSH + +``` +eval "$(your-cli-tool --completion-script-zsh)" +``` + +#### Additional API +To provide more flexibility, a completion option API has been +exposed for flags to allow user defined completion options, to extend +completions further than just EnumVar/Enum. + + +**Provide Static Options** + +When using an `Enum` or `EnumVar`, users are limited to only the options +given. Maybe we wish to hint possible options to the user, but also +allow them to provide their own custom option. `HintOptions` gives +this functionality to flags. + +``` +app := kingpin.New("completion", "My application with bash completion.") +app.Flag("port", "Provide a port to connect to"). + Required(). + HintOptions("80", "443", "8080"). + IntVar(&c.port) +``` + +**Provide Dynamic Options** +Consider the case that you needed to read a local database or a file to +provide suggestions. You can dynamically generate the options + +``` +func listHosts() []string { + // Provide a dynamic list of hosts from a hosts file or otherwise + // for bash completion. In this example we simply return static slice. + + // You could use this functionality to reach into a hosts file to provide + // completion for a list of known hosts. + return []string{"sshhost.example", "webhost.example", "ftphost.example"} +} + +app := kingpin.New("completion", "My application with bash completion.") +app.Flag("flag-1", "").HintAction(listHosts).String() +``` + +**EnumVar/Enum** +When using `Enum` or `EnumVar`, any provided options will be automatically +used for bash autocompletion. However, if you wish to provide a subset or +different options, you can use `HintOptions` or `HintAction` which will override +the default completion options for `Enum`/`EnumVar`. + + +**Examples** +You can see an in depth example of the completion API within +`examples/completion/main.go` + + +### Supporting -h for help + +`kingpin.CommandLine.HelpFlag.Short('h')` + +### Custom help + +Kingpin v2 supports templatised help using the text/template library (actually, [a fork](https://github.com/alecthomas/template)). + +You can specify the template to use with the [Application.UsageTemplate()](http://godoc.org/gopkg.in/alecthomas/kingpin.v2#Application.UsageTemplate) function. + +There are four included templates: `kingpin.DefaultUsageTemplate` is the default, +`kingpin.CompactUsageTemplate` provides a more compact representation for more complex command-line structures, +`kingpin.SeparateOptionalFlagsUsageTemplate` looks like the default template, but splits required +and optional command flags into separate lists, and `kingpin.ManPageTemplate` is used to generate man pages. + +See the above templates for examples of usage, and the the function [UsageForContextWithTemplate()](https://github.com/alecthomas/kingpin/blob/master/usage.go#L198) method for details on the context. + +#### Default help template + +``` +$ go run ./examples/curl/curl.go --help +usage: curl [] [ ...] + +An example implementation of curl. + +Flags: + --help Show help. + -t, --timeout=5s Set connection timeout. + -H, --headers=HEADER=VALUE + Add HTTP headers to the request. + +Commands: + help [...] + Show help. + + get url + Retrieve a URL. + + get file + Retrieve a file. + + post [] + POST a resource. +``` + +#### Compact help template + +``` +$ go run ./examples/curl/curl.go --help +usage: curl [] [ ...] + +An example implementation of curl. + +Flags: + --help Show help. + -t, --timeout=5s Set connection timeout. + -H, --headers=HEADER=VALUE + Add HTTP headers to the request. + +Commands: + help [...] + get [] + url + file + post [] +``` diff --git a/vendor/gopkg.in/alecthomas/kingpin.v2/actions.go b/vendor/gopkg.in/alecthomas/kingpin.v2/actions.go new file mode 100644 index 00000000..72d6cbd4 --- /dev/null +++ b/vendor/gopkg.in/alecthomas/kingpin.v2/actions.go @@ -0,0 +1,42 @@ +package kingpin + +// Action callback executed at various stages after all values are populated. +// The application, commands, arguments and flags all have corresponding +// actions. +type Action func(*ParseContext) error + +type actionMixin struct { + actions []Action + preActions []Action +} + +type actionApplier interface { + applyActions(*ParseContext) error + applyPreActions(*ParseContext) error +} + +func (a *actionMixin) addAction(action Action) { + a.actions = append(a.actions, action) +} + +func (a *actionMixin) addPreAction(action Action) { + a.preActions = append(a.preActions, action) +} + +func (a *actionMixin) applyActions(context *ParseContext) error { + for _, action := range a.actions { + if err := action(context); err != nil { + return err + } + } + return nil +} + +func (a *actionMixin) applyPreActions(context *ParseContext) error { + for _, preAction := range a.preActions { + if err := preAction(context); err != nil { + return err + } + } + return nil +} diff --git a/vendor/gopkg.in/alecthomas/kingpin.v2/app.go b/vendor/gopkg.in/alecthomas/kingpin.v2/app.go new file mode 100644 index 00000000..1a1a5eff --- /dev/null +++ b/vendor/gopkg.in/alecthomas/kingpin.v2/app.go @@ -0,0 +1,688 @@ +package kingpin + +import ( + "fmt" + "io" + "os" + "regexp" + "strings" +) + +var ( + ErrCommandNotSpecified = fmt.Errorf("command not specified") +) + +var ( + envarTransformRegexp = regexp.MustCompile(`[^a-zA-Z0-9_]+`) +) + +type ApplicationValidator func(*Application) error + +// An Application contains the definitions of flags, arguments and commands +// for an application. +type Application struct { + cmdMixin + initialized bool + + Name string + Help string + + author string + version string + errorWriter io.Writer // Destination for errors. + usageWriter io.Writer // Destination for usage + usageTemplate string + validator ApplicationValidator + terminate func(status int) // See Terminate() + noInterspersed bool // can flags be interspersed with args (or must they come first) + defaultEnvars bool + completion bool + + // Help flag. Exposed for user customisation. + HelpFlag *FlagClause + // Help command. Exposed for user customisation. May be nil. + HelpCommand *CmdClause + // Version flag. Exposed for user customisation. May be nil. + VersionFlag *FlagClause +} + +// New creates a new Kingpin application instance. +func New(name, help string) *Application { + a := &Application{ + Name: name, + Help: help, + errorWriter: os.Stderr, // Left for backwards compatibility purposes. + usageWriter: os.Stderr, + usageTemplate: DefaultUsageTemplate, + terminate: os.Exit, + } + a.flagGroup = newFlagGroup() + a.argGroup = newArgGroup() + a.cmdGroup = newCmdGroup(a) + a.HelpFlag = a.Flag("help", "Show context-sensitive help (also try --help-long and --help-man).") + a.HelpFlag.Bool() + a.Flag("help-long", "Generate long help.").Hidden().PreAction(a.generateLongHelp).Bool() + a.Flag("help-man", "Generate a man page.").Hidden().PreAction(a.generateManPage).Bool() + a.Flag("completion-bash", "Output possible completions for the given args.").Hidden().BoolVar(&a.completion) + a.Flag("completion-script-bash", "Generate completion script for bash.").Hidden().PreAction(a.generateBashCompletionScript).Bool() + a.Flag("completion-script-zsh", "Generate completion script for ZSH.").Hidden().PreAction(a.generateZSHCompletionScript).Bool() + + return a +} + +func (a *Application) generateLongHelp(c *ParseContext) error { + a.Writer(os.Stdout) + if err := a.UsageForContextWithTemplate(c, 2, LongHelpTemplate); err != nil { + return err + } + a.terminate(0) + return nil +} + +func (a *Application) generateManPage(c *ParseContext) error { + a.Writer(os.Stdout) + if err := a.UsageForContextWithTemplate(c, 2, ManPageTemplate); err != nil { + return err + } + a.terminate(0) + return nil +} + +func (a *Application) generateBashCompletionScript(c *ParseContext) error { + a.Writer(os.Stdout) + if err := a.UsageForContextWithTemplate(c, 2, BashCompletionTemplate); err != nil { + return err + } + a.terminate(0) + return nil +} + +func (a *Application) generateZSHCompletionScript(c *ParseContext) error { + a.Writer(os.Stdout) + if err := a.UsageForContextWithTemplate(c, 2, ZshCompletionTemplate); err != nil { + return err + } + a.terminate(0) + return nil +} + +// DefaultEnvars configures all flags (that do not already have an associated +// envar) to use a default environment variable in the form "_". +// +// For example, if the application is named "foo" and a flag is named "bar- +// waz" the environment variable: "FOO_BAR_WAZ". +func (a *Application) DefaultEnvars() *Application { + a.defaultEnvars = true + return a +} + +// Terminate specifies the termination handler. Defaults to os.Exit(status). +// If nil is passed, a no-op function will be used. +func (a *Application) Terminate(terminate func(int)) *Application { + if terminate == nil { + terminate = func(int) {} + } + a.terminate = terminate + return a +} + +// Writer specifies the writer to use for usage and errors. Defaults to os.Stderr. +// DEPRECATED: See ErrorWriter and UsageWriter. +func (a *Application) Writer(w io.Writer) *Application { + a.errorWriter = w + a.usageWriter = w + return a +} + +// ErrorWriter sets the io.Writer to use for errors. +func (a *Application) ErrorWriter(w io.Writer) *Application { + a.errorWriter = w + return a +} + +// UsageWriter sets the io.Writer to use for errors. +func (a *Application) UsageWriter(w io.Writer) *Application { + a.usageWriter = w + return a +} + +// UsageTemplate specifies the text template to use when displaying usage +// information. The default is UsageTemplate. +func (a *Application) UsageTemplate(template string) *Application { + a.usageTemplate = template + return a +} + +// Validate sets a validation function to run when parsing. +func (a *Application) Validate(validator ApplicationValidator) *Application { + a.validator = validator + return a +} + +// ParseContext parses the given command line and returns the fully populated +// ParseContext. +func (a *Application) ParseContext(args []string) (*ParseContext, error) { + return a.parseContext(false, args) +} + +func (a *Application) parseContext(ignoreDefault bool, args []string) (*ParseContext, error) { + if err := a.init(); err != nil { + return nil, err + } + context := tokenize(args, ignoreDefault) + err := parse(context, a) + return context, err +} + +// Parse parses command-line arguments. It returns the selected command and an +// error. The selected command will be a space separated subcommand, if +// subcommands have been configured. +// +// This will populate all flag and argument values, call all callbacks, and so +// on. +func (a *Application) Parse(args []string) (command string, err error) { + + context, parseErr := a.ParseContext(args) + selected := []string{} + var setValuesErr error + + if context == nil { + // Since we do not throw error immediately, there could be a case + // where a context returns nil. Protect against that. + return "", parseErr + } + + if err = a.setDefaults(context); err != nil { + return "", err + } + + selected, setValuesErr = a.setValues(context) + + if err = a.applyPreActions(context, !a.completion); err != nil { + return "", err + } + + if a.completion { + a.generateBashCompletion(context) + a.terminate(0) + } else { + if parseErr != nil { + return "", parseErr + } + + a.maybeHelp(context) + if !context.EOL() { + return "", fmt.Errorf("unexpected argument '%s'", context.Peek()) + } + + if setValuesErr != nil { + return "", setValuesErr + } + + command, err = a.execute(context, selected) + if err == ErrCommandNotSpecified { + a.writeUsage(context, nil) + } + } + return command, err +} + +func (a *Application) writeUsage(context *ParseContext, err error) { + if err != nil { + a.Errorf("%s", err) + } + if err := a.UsageForContext(context); err != nil { + panic(err) + } + if err != nil { + a.terminate(1) + } else { + a.terminate(0) + } +} + +func (a *Application) maybeHelp(context *ParseContext) { + for _, element := range context.Elements { + if flag, ok := element.Clause.(*FlagClause); ok && flag == a.HelpFlag { + // Re-parse the command-line ignoring defaults, so that help works correctly. + context, _ = a.parseContext(true, context.rawArgs) + a.writeUsage(context, nil) + } + } +} + +// Version adds a --version flag for displaying the application version. +func (a *Application) Version(version string) *Application { + a.version = version + a.VersionFlag = a.Flag("version", "Show application version.").PreAction(func(*ParseContext) error { + fmt.Fprintln(a.usageWriter, version) + a.terminate(0) + return nil + }) + a.VersionFlag.Bool() + return a +} + +// Author sets the author output by some help templates. +func (a *Application) Author(author string) *Application { + a.author = author + return a +} + +// Action callback to call when all values are populated and parsing is +// complete, but before any command, flag or argument actions. +// +// All Action() callbacks are called in the order they are encountered on the +// command line. +func (a *Application) Action(action Action) *Application { + a.addAction(action) + return a +} + +// Action called after parsing completes but before validation and execution. +func (a *Application) PreAction(action Action) *Application { + a.addPreAction(action) + return a +} + +// Command adds a new top-level command. +func (a *Application) Command(name, help string) *CmdClause { + return a.addCommand(name, help) +} + +// Interspersed control if flags can be interspersed with positional arguments +// +// true (the default) means that they can, false means that all the flags must appear before the first positional arguments. +func (a *Application) Interspersed(interspersed bool) *Application { + a.noInterspersed = !interspersed + return a +} + +func (a *Application) defaultEnvarPrefix() string { + if a.defaultEnvars { + return a.Name + } + return "" +} + +func (a *Application) init() error { + if a.initialized { + return nil + } + if a.cmdGroup.have() && a.argGroup.have() { + return fmt.Errorf("can't mix top-level Arg()s with Command()s") + } + + // If we have subcommands, add a help command at the top-level. + if a.cmdGroup.have() { + var command []string + a.HelpCommand = a.Command("help", "Show help.").PreAction(func(context *ParseContext) error { + a.Usage(command) + a.terminate(0) + return nil + }) + a.HelpCommand.Arg("command", "Show help on command.").StringsVar(&command) + // Make help first command. + l := len(a.commandOrder) + a.commandOrder = append(a.commandOrder[l-1:l], a.commandOrder[:l-1]...) + } + + if err := a.flagGroup.init(a.defaultEnvarPrefix()); err != nil { + return err + } + if err := a.cmdGroup.init(); err != nil { + return err + } + if err := a.argGroup.init(); err != nil { + return err + } + for _, cmd := range a.commands { + if err := cmd.init(); err != nil { + return err + } + } + flagGroups := []*flagGroup{a.flagGroup} + for _, cmd := range a.commandOrder { + if err := checkDuplicateFlags(cmd, flagGroups); err != nil { + return err + } + } + a.initialized = true + return nil +} + +// Recursively check commands for duplicate flags. +func checkDuplicateFlags(current *CmdClause, flagGroups []*flagGroup) error { + // Check for duplicates. + for _, flags := range flagGroups { + for _, flag := range current.flagOrder { + if flag.shorthand != 0 { + if _, ok := flags.short[string(flag.shorthand)]; ok { + return fmt.Errorf("duplicate short flag -%c", flag.shorthand) + } + } + if _, ok := flags.long[flag.name]; ok { + return fmt.Errorf("duplicate long flag --%s", flag.name) + } + } + } + flagGroups = append(flagGroups, current.flagGroup) + // Check subcommands. + for _, subcmd := range current.commandOrder { + if err := checkDuplicateFlags(subcmd, flagGroups); err != nil { + return err + } + } + return nil +} + +func (a *Application) execute(context *ParseContext, selected []string) (string, error) { + var err error + + if err = a.validateRequired(context); err != nil { + return "", err + } + + if err = a.applyValidators(context); err != nil { + return "", err + } + + if err = a.applyActions(context); err != nil { + return "", err + } + + command := strings.Join(selected, " ") + if command == "" && a.cmdGroup.have() { + return "", ErrCommandNotSpecified + } + return command, err +} + +func (a *Application) setDefaults(context *ParseContext) error { + flagElements := map[string]*ParseElement{} + for _, element := range context.Elements { + if flag, ok := element.Clause.(*FlagClause); ok { + if flag.name == "help" { + return nil + } + flagElements[flag.name] = element + } + } + + argElements := map[string]*ParseElement{} + for _, element := range context.Elements { + if arg, ok := element.Clause.(*ArgClause); ok { + argElements[arg.name] = element + } + } + + // Check required flags and set defaults. + for _, flag := range context.flags.long { + if flagElements[flag.name] == nil { + if err := flag.setDefault(); err != nil { + return err + } + } + } + + for _, arg := range context.arguments.args { + if argElements[arg.name] == nil { + if err := arg.setDefault(); err != nil { + return err + } + } + } + + return nil +} + +func (a *Application) validateRequired(context *ParseContext) error { + flagElements := map[string]*ParseElement{} + for _, element := range context.Elements { + if flag, ok := element.Clause.(*FlagClause); ok { + flagElements[flag.name] = element + } + } + + argElements := map[string]*ParseElement{} + for _, element := range context.Elements { + if arg, ok := element.Clause.(*ArgClause); ok { + argElements[arg.name] = element + } + } + + // Check required flags and set defaults. + for _, flag := range context.flags.long { + if flagElements[flag.name] == nil { + // Check required flags were provided. + if flag.needsValue() { + return fmt.Errorf("required flag --%s not provided", flag.name) + } + } + } + + for _, arg := range context.arguments.args { + if argElements[arg.name] == nil { + if arg.needsValue() { + return fmt.Errorf("required argument '%s' not provided", arg.name) + } + } + } + return nil +} + +func (a *Application) setValues(context *ParseContext) (selected []string, err error) { + // Set all arg and flag values. + var ( + lastCmd *CmdClause + flagSet = map[string]struct{}{} + ) + for _, element := range context.Elements { + switch clause := element.Clause.(type) { + case *FlagClause: + if _, ok := flagSet[clause.name]; ok { + if v, ok := clause.value.(repeatableFlag); !ok || !v.IsCumulative() { + return nil, fmt.Errorf("flag '%s' cannot be repeated", clause.name) + } + } + if err = clause.value.Set(*element.Value); err != nil { + return + } + flagSet[clause.name] = struct{}{} + + case *ArgClause: + if err = clause.value.Set(*element.Value); err != nil { + return + } + + case *CmdClause: + if clause.validator != nil { + if err = clause.validator(clause); err != nil { + return + } + } + selected = append(selected, clause.name) + lastCmd = clause + } + } + + if lastCmd != nil && len(lastCmd.commands) > 0 { + return nil, fmt.Errorf("must select a subcommand of '%s'", lastCmd.FullCommand()) + } + + return +} + +func (a *Application) applyValidators(context *ParseContext) (err error) { + // Call command validation functions. + for _, element := range context.Elements { + if cmd, ok := element.Clause.(*CmdClause); ok && cmd.validator != nil { + if err = cmd.validator(cmd); err != nil { + return err + } + } + } + + if a.validator != nil { + err = a.validator(a) + } + return err +} + +func (a *Application) applyPreActions(context *ParseContext, dispatch bool) error { + if err := a.actionMixin.applyPreActions(context); err != nil { + return err + } + // Dispatch to actions. + if dispatch { + for _, element := range context.Elements { + if applier, ok := element.Clause.(actionApplier); ok { + if err := applier.applyPreActions(context); err != nil { + return err + } + } + } + } + + return nil +} + +func (a *Application) applyActions(context *ParseContext) error { + if err := a.actionMixin.applyActions(context); err != nil { + return err + } + // Dispatch to actions. + for _, element := range context.Elements { + if applier, ok := element.Clause.(actionApplier); ok { + if err := applier.applyActions(context); err != nil { + return err + } + } + } + return nil +} + +// Errorf prints an error message to w in the format ": error: ". +func (a *Application) Errorf(format string, args ...interface{}) { + fmt.Fprintf(a.errorWriter, a.Name+": error: "+format+"\n", args...) +} + +// Fatalf writes a formatted error to w then terminates with exit status 1. +func (a *Application) Fatalf(format string, args ...interface{}) { + a.Errorf(format, args...) + a.terminate(1) +} + +// FatalUsage prints an error message followed by usage information, then +// exits with a non-zero status. +func (a *Application) FatalUsage(format string, args ...interface{}) { + a.Errorf(format, args...) + // Force usage to go to error output. + a.usageWriter = a.errorWriter + a.Usage([]string{}) + a.terminate(1) +} + +// FatalUsageContext writes a printf formatted error message to w, then usage +// information for the given ParseContext, before exiting. +func (a *Application) FatalUsageContext(context *ParseContext, format string, args ...interface{}) { + a.Errorf(format, args...) + if err := a.UsageForContext(context); err != nil { + panic(err) + } + a.terminate(1) +} + +// FatalIfError prints an error and exits if err is not nil. The error is printed +// with the given formatted string, if any. +func (a *Application) FatalIfError(err error, format string, args ...interface{}) { + if err != nil { + prefix := "" + if format != "" { + prefix = fmt.Sprintf(format, args...) + ": " + } + a.Errorf(prefix+"%s", err) + a.terminate(1) + } +} + +func (a *Application) completionOptions(context *ParseContext) []string { + args := context.rawArgs + + var ( + currArg string + prevArg string + target cmdMixin + ) + + numArgs := len(args) + if numArgs > 1 { + args = args[1:] + currArg = args[len(args)-1] + } + if numArgs > 2 { + prevArg = args[len(args)-2] + } + + target = a.cmdMixin + if context.SelectedCommand != nil { + // A subcommand was in use. We will use it as the target + target = context.SelectedCommand.cmdMixin + } + + if (currArg != "" && strings.HasPrefix(currArg, "--")) || strings.HasPrefix(prevArg, "--") { + // Perform completion for A flag. The last/current argument started with "-" + var ( + flagName string // The name of a flag if given (could be half complete) + flagValue string // The value assigned to a flag (if given) (could be half complete) + ) + + if strings.HasPrefix(prevArg, "--") && !strings.HasPrefix(currArg, "--") { + // Matches: ./myApp --flag value + // Wont Match: ./myApp --flag -- + flagName = prevArg[2:] // Strip the "--" + flagValue = currArg + } else if strings.HasPrefix(currArg, "--") { + // Matches: ./myApp --flag -- + // Matches: ./myApp --flag somevalue -- + // Matches: ./myApp -- + flagName = currArg[2:] // Strip the "--" + } + + options, flagMatched, valueMatched := target.FlagCompletion(flagName, flagValue) + if valueMatched { + // Value Matched. Show cmdCompletions + return target.CmdCompletion(context) + } + + // Add top level flags if we're not at the top level and no match was found. + if context.SelectedCommand != nil && !flagMatched { + topOptions, topFlagMatched, topValueMatched := a.FlagCompletion(flagName, flagValue) + if topValueMatched { + // Value Matched. Back to cmdCompletions + return target.CmdCompletion(context) + } + + if topFlagMatched { + // Top level had a flag which matched the input. Return it's options. + options = topOptions + } else { + // Add top level flags + options = append(options, topOptions...) + } + } + return options + } + + // Perform completion for sub commands and arguments. + return target.CmdCompletion(context) +} + +func (a *Application) generateBashCompletion(context *ParseContext) { + options := a.completionOptions(context) + fmt.Printf("%s", strings.Join(options, "\n")) +} + +func envarTransform(name string) string { + return strings.ToUpper(envarTransformRegexp.ReplaceAllString(name, "_")) +} diff --git a/vendor/gopkg.in/alecthomas/kingpin.v2/args.go b/vendor/gopkg.in/alecthomas/kingpin.v2/args.go new file mode 100644 index 00000000..34006947 --- /dev/null +++ b/vendor/gopkg.in/alecthomas/kingpin.v2/args.go @@ -0,0 +1,184 @@ +package kingpin + +import ( + "fmt" +) + +type argGroup struct { + args []*ArgClause +} + +func newArgGroup() *argGroup { + return &argGroup{} +} + +func (a *argGroup) have() bool { + return len(a.args) > 0 +} + +// GetArg gets an argument definition. +// +// This allows existing arguments to be modified after definition but before parsing. Useful for +// modular applications. +func (a *argGroup) GetArg(name string) *ArgClause { + for _, arg := range a.args { + if arg.name == name { + return arg + } + } + return nil +} + +func (a *argGroup) Arg(name, help string) *ArgClause { + arg := newArg(name, help) + a.args = append(a.args, arg) + return arg +} + +func (a *argGroup) init() error { + required := 0 + seen := map[string]struct{}{} + previousArgMustBeLast := false + for i, arg := range a.args { + if previousArgMustBeLast { + return fmt.Errorf("Args() can't be followed by another argument '%s'", arg.name) + } + if arg.consumesRemainder() { + previousArgMustBeLast = true + } + if _, ok := seen[arg.name]; ok { + return fmt.Errorf("duplicate argument '%s'", arg.name) + } + seen[arg.name] = struct{}{} + if arg.required && required != i { + return fmt.Errorf("required arguments found after non-required") + } + if arg.required { + required++ + } + if err := arg.init(); err != nil { + return err + } + } + return nil +} + +type ArgClause struct { + actionMixin + parserMixin + completionsMixin + envarMixin + name string + help string + defaultValues []string + required bool +} + +func newArg(name, help string) *ArgClause { + a := &ArgClause{ + name: name, + help: help, + } + return a +} + +func (a *ArgClause) setDefault() error { + if a.HasEnvarValue() { + if v, ok := a.value.(remainderArg); !ok || !v.IsCumulative() { + // Use the value as-is + return a.value.Set(a.GetEnvarValue()) + } + for _, value := range a.GetSplitEnvarValue() { + if err := a.value.Set(value); err != nil { + return err + } + } + return nil + } + + if len(a.defaultValues) > 0 { + for _, defaultValue := range a.defaultValues { + if err := a.value.Set(defaultValue); err != nil { + return err + } + } + return nil + } + + return nil +} + +func (a *ArgClause) needsValue() bool { + haveDefault := len(a.defaultValues) > 0 + return a.required && !(haveDefault || a.HasEnvarValue()) +} + +func (a *ArgClause) consumesRemainder() bool { + if r, ok := a.value.(remainderArg); ok { + return r.IsCumulative() + } + return false +} + +// Required arguments must be input by the user. They can not have a Default() value provided. +func (a *ArgClause) Required() *ArgClause { + a.required = true + return a +} + +// Default values for this argument. They *must* be parseable by the value of the argument. +func (a *ArgClause) Default(values ...string) *ArgClause { + a.defaultValues = values + return a +} + +// Envar overrides the default value(s) for a flag from an environment variable, +// if it is set. Several default values can be provided by using new lines to +// separate them. +func (a *ArgClause) Envar(name string) *ArgClause { + a.envar = name + a.noEnvar = false + return a +} + +// NoEnvar forces environment variable defaults to be disabled for this flag. +// Most useful in conjunction with app.DefaultEnvars(). +func (a *ArgClause) NoEnvar() *ArgClause { + a.envar = "" + a.noEnvar = true + return a +} + +func (a *ArgClause) Action(action Action) *ArgClause { + a.addAction(action) + return a +} + +func (a *ArgClause) PreAction(action Action) *ArgClause { + a.addPreAction(action) + return a +} + +// HintAction registers a HintAction (function) for the arg to provide completions +func (a *ArgClause) HintAction(action HintAction) *ArgClause { + a.addHintAction(action) + return a +} + +// HintOptions registers any number of options for the flag to provide completions +func (a *ArgClause) HintOptions(options ...string) *ArgClause { + a.addHintAction(func() []string { + return options + }) + return a +} + +func (a *ArgClause) init() error { + if a.required && len(a.defaultValues) > 0 { + return fmt.Errorf("required argument '%s' with unusable default value", a.name) + } + if a.value == nil { + return fmt.Errorf("no parser defined for arg '%s'", a.name) + } + return nil +} diff --git a/vendor/gopkg.in/alecthomas/kingpin.v2/cmd.go b/vendor/gopkg.in/alecthomas/kingpin.v2/cmd.go new file mode 100644 index 00000000..0473b871 --- /dev/null +++ b/vendor/gopkg.in/alecthomas/kingpin.v2/cmd.go @@ -0,0 +1,274 @@ +package kingpin + +import ( + "fmt" + "strings" +) + +type cmdMixin struct { + *flagGroup + *argGroup + *cmdGroup + actionMixin +} + +// CmdCompletion returns completion options for arguments, if that's where +// parsing left off, or commands if there aren't any unsatisfied args. +func (c *cmdMixin) CmdCompletion(context *ParseContext) []string { + var options []string + + // Count args already satisfied - we won't complete those, and add any + // default commands' alternatives, since they weren't listed explicitly + // and the user may want to explicitly list something else. + argsSatisfied := 0 + for _, el := range context.Elements { + switch clause := el.Clause.(type) { + case *ArgClause: + if el.Value != nil && *el.Value != "" { + argsSatisfied++ + } + case *CmdClause: + options = append(options, clause.completionAlts...) + default: + } + } + + if argsSatisfied < len(c.argGroup.args) { + // Since not all args have been satisfied, show options for the current one + options = append(options, c.argGroup.args[argsSatisfied].resolveCompletions()...) + } else { + // If all args are satisfied, then go back to completing commands + for _, cmd := range c.cmdGroup.commandOrder { + if !cmd.hidden { + options = append(options, cmd.name) + } + } + } + + return options +} + +func (c *cmdMixin) FlagCompletion(flagName string, flagValue string) (choices []string, flagMatch bool, optionMatch bool) { + // Check if flagName matches a known flag. + // If it does, show the options for the flag + // Otherwise, show all flags + + options := []string{} + + for _, flag := range c.flagGroup.flagOrder { + // Loop through each flag and determine if a match exists + if flag.name == flagName { + // User typed entire flag. Need to look for flag options. + options = flag.resolveCompletions() + if len(options) == 0 { + // No Options to Choose From, Assume Match. + return options, true, true + } + + // Loop options to find if the user specified value matches + isPrefix := false + matched := false + + for _, opt := range options { + if flagValue == opt { + matched = true + } else if strings.HasPrefix(opt, flagValue) { + isPrefix = true + } + } + + // Matched Flag Directly + // Flag Value Not Prefixed, and Matched Directly + return options, true, !isPrefix && matched + } + + if !flag.hidden { + options = append(options, "--"+flag.name) + } + } + // No Flag directly matched. + return options, false, false + +} + +type cmdGroup struct { + app *Application + parent *CmdClause + commands map[string]*CmdClause + commandOrder []*CmdClause +} + +func (c *cmdGroup) defaultSubcommand() *CmdClause { + for _, cmd := range c.commandOrder { + if cmd.isDefault { + return cmd + } + } + return nil +} + +func (c *cmdGroup) cmdNames() []string { + names := make([]string, 0, len(c.commandOrder)) + for _, cmd := range c.commandOrder { + names = append(names, cmd.name) + } + return names +} + +// GetArg gets a command definition. +// +// This allows existing commands to be modified after definition but before parsing. Useful for +// modular applications. +func (c *cmdGroup) GetCommand(name string) *CmdClause { + return c.commands[name] +} + +func newCmdGroup(app *Application) *cmdGroup { + return &cmdGroup{ + app: app, + commands: make(map[string]*CmdClause), + } +} + +func (c *cmdGroup) flattenedCommands() (out []*CmdClause) { + for _, cmd := range c.commandOrder { + if len(cmd.commands) == 0 { + out = append(out, cmd) + } + out = append(out, cmd.flattenedCommands()...) + } + return +} + +func (c *cmdGroup) addCommand(name, help string) *CmdClause { + cmd := newCommand(c.app, name, help) + c.commands[name] = cmd + c.commandOrder = append(c.commandOrder, cmd) + return cmd +} + +func (c *cmdGroup) init() error { + seen := map[string]bool{} + if c.defaultSubcommand() != nil && !c.have() { + return fmt.Errorf("default subcommand %q provided but no subcommands defined", c.defaultSubcommand().name) + } + defaults := []string{} + for _, cmd := range c.commandOrder { + if cmd.isDefault { + defaults = append(defaults, cmd.name) + } + if seen[cmd.name] { + return fmt.Errorf("duplicate command %q", cmd.name) + } + seen[cmd.name] = true + for _, alias := range cmd.aliases { + if seen[alias] { + return fmt.Errorf("alias duplicates existing command %q", alias) + } + c.commands[alias] = cmd + } + if err := cmd.init(); err != nil { + return err + } + } + if len(defaults) > 1 { + return fmt.Errorf("more than one default subcommand exists: %s", strings.Join(defaults, ", ")) + } + return nil +} + +func (c *cmdGroup) have() bool { + return len(c.commands) > 0 +} + +type CmdClauseValidator func(*CmdClause) error + +// A CmdClause is a single top-level command. It encapsulates a set of flags +// and either subcommands or positional arguments. +type CmdClause struct { + cmdMixin + app *Application + name string + aliases []string + help string + isDefault bool + validator CmdClauseValidator + hidden bool + completionAlts []string +} + +func newCommand(app *Application, name, help string) *CmdClause { + c := &CmdClause{ + app: app, + name: name, + help: help, + } + c.flagGroup = newFlagGroup() + c.argGroup = newArgGroup() + c.cmdGroup = newCmdGroup(app) + return c +} + +// Add an Alias for this command. +func (c *CmdClause) Alias(name string) *CmdClause { + c.aliases = append(c.aliases, name) + return c +} + +// Validate sets a validation function to run when parsing. +func (c *CmdClause) Validate(validator CmdClauseValidator) *CmdClause { + c.validator = validator + return c +} + +func (c *CmdClause) FullCommand() string { + out := []string{c.name} + for p := c.parent; p != nil; p = p.parent { + out = append([]string{p.name}, out...) + } + return strings.Join(out, " ") +} + +// Command adds a new sub-command. +func (c *CmdClause) Command(name, help string) *CmdClause { + cmd := c.addCommand(name, help) + cmd.parent = c + return cmd +} + +// Default makes this command the default if commands don't match. +func (c *CmdClause) Default() *CmdClause { + c.isDefault = true + return c +} + +func (c *CmdClause) Action(action Action) *CmdClause { + c.addAction(action) + return c +} + +func (c *CmdClause) PreAction(action Action) *CmdClause { + c.addPreAction(action) + return c +} + +func (c *CmdClause) init() error { + if err := c.flagGroup.init(c.app.defaultEnvarPrefix()); err != nil { + return err + } + if c.argGroup.have() && c.cmdGroup.have() { + return fmt.Errorf("can't mix Arg()s with Command()s") + } + if err := c.argGroup.init(); err != nil { + return err + } + if err := c.cmdGroup.init(); err != nil { + return err + } + return nil +} + +func (c *CmdClause) Hidden() *CmdClause { + c.hidden = true + return c +} diff --git a/vendor/gopkg.in/alecthomas/kingpin.v2/completions.go b/vendor/gopkg.in/alecthomas/kingpin.v2/completions.go new file mode 100644 index 00000000..6e7b409f --- /dev/null +++ b/vendor/gopkg.in/alecthomas/kingpin.v2/completions.go @@ -0,0 +1,33 @@ +package kingpin + +// HintAction is a function type who is expected to return a slice of possible +// command line arguments. +type HintAction func() []string +type completionsMixin struct { + hintActions []HintAction + builtinHintActions []HintAction +} + +func (a *completionsMixin) addHintAction(action HintAction) { + a.hintActions = append(a.hintActions, action) +} + +// Allow adding of HintActions which are added internally, ie, EnumVar +func (a *completionsMixin) addHintActionBuiltin(action HintAction) { + a.builtinHintActions = append(a.builtinHintActions, action) +} + +func (a *completionsMixin) resolveCompletions() []string { + var hints []string + + options := a.builtinHintActions + if len(a.hintActions) > 0 { + // User specified their own hintActions. Use those instead. + options = a.hintActions + } + + for _, hintAction := range options { + hints = append(hints, hintAction()...) + } + return hints +} diff --git a/vendor/gopkg.in/alecthomas/kingpin.v2/doc.go b/vendor/gopkg.in/alecthomas/kingpin.v2/doc.go new file mode 100644 index 00000000..cb951a80 --- /dev/null +++ b/vendor/gopkg.in/alecthomas/kingpin.v2/doc.go @@ -0,0 +1,68 @@ +// Package kingpin provides command line interfaces like this: +// +// $ chat +// usage: chat [] [] [ ...] +// +// Flags: +// --debug enable debug mode +// --help Show help. +// --server=127.0.0.1 server address +// +// Commands: +// help +// Show help for a command. +// +// post [] +// Post a message to a channel. +// +// register +// Register a new user. +// +// $ chat help post +// usage: chat [] post [] [] +// +// Post a message to a channel. +// +// Flags: +// --image=IMAGE image to post +// +// Args: +// channel to post to +// [] text to post +// $ chat post --image=~/Downloads/owls.jpg pics +// +// From code like this: +// +// package main +// +// import "gopkg.in/alecthomas/kingpin.v2" +// +// var ( +// debug = kingpin.Flag("debug", "enable debug mode").Default("false").Bool() +// serverIP = kingpin.Flag("server", "server address").Default("127.0.0.1").IP() +// +// register = kingpin.Command("register", "Register a new user.") +// registerNick = register.Arg("nick", "nickname for user").Required().String() +// registerName = register.Arg("name", "name of user").Required().String() +// +// post = kingpin.Command("post", "Post a message to a channel.") +// postImage = post.Flag("image", "image to post").ExistingFile() +// postChannel = post.Arg("channel", "channel to post to").Required().String() +// postText = post.Arg("text", "text to post").String() +// ) +// +// func main() { +// switch kingpin.Parse() { +// // Register user +// case "register": +// println(*registerNick) +// +// // Post message +// case "post": +// if *postImage != nil { +// } +// if *postText != "" { +// } +// } +// } +package kingpin diff --git a/vendor/gopkg.in/alecthomas/kingpin.v2/envar.go b/vendor/gopkg.in/alecthomas/kingpin.v2/envar.go new file mode 100644 index 00000000..c01a27df --- /dev/null +++ b/vendor/gopkg.in/alecthomas/kingpin.v2/envar.go @@ -0,0 +1,45 @@ +package kingpin + +import ( + "os" + "regexp" +) + +var ( + envVarValuesSeparator = "\r?\n" + envVarValuesTrimmer = regexp.MustCompile(envVarValuesSeparator + "$") + envVarValuesSplitter = regexp.MustCompile(envVarValuesSeparator) +) + +type envarMixin struct { + envar string + noEnvar bool +} + +func (e *envarMixin) HasEnvarValue() bool { + return e.GetEnvarValue() != "" +} + +func (e *envarMixin) GetEnvarValue() string { + if e.noEnvar || e.envar == "" { + return "" + } + return os.Getenv(e.envar) +} + +func (e *envarMixin) GetSplitEnvarValue() []string { + values := make([]string, 0) + + envarValue := e.GetEnvarValue() + if envarValue == "" { + return values + } + + // Split by new line to extract multiple values, if any. + trimmed := envVarValuesTrimmer.ReplaceAllString(envarValue, "") + for _, value := range envVarValuesSplitter.Split(trimmed, -1) { + values = append(values, value) + } + + return values +} diff --git a/vendor/gopkg.in/alecthomas/kingpin.v2/flags.go b/vendor/gopkg.in/alecthomas/kingpin.v2/flags.go new file mode 100644 index 00000000..8f33721f --- /dev/null +++ b/vendor/gopkg.in/alecthomas/kingpin.v2/flags.go @@ -0,0 +1,308 @@ +package kingpin + +import ( + "fmt" + "strings" +) + +type flagGroup struct { + short map[string]*FlagClause + long map[string]*FlagClause + flagOrder []*FlagClause +} + +func newFlagGroup() *flagGroup { + return &flagGroup{ + short: map[string]*FlagClause{}, + long: map[string]*FlagClause{}, + } +} + +// GetFlag gets a flag definition. +// +// This allows existing flags to be modified after definition but before parsing. Useful for +// modular applications. +func (f *flagGroup) GetFlag(name string) *FlagClause { + return f.long[name] +} + +// Flag defines a new flag with the given long name and help. +func (f *flagGroup) Flag(name, help string) *FlagClause { + flag := newFlag(name, help) + f.long[name] = flag + f.flagOrder = append(f.flagOrder, flag) + return flag +} + +func (f *flagGroup) init(defaultEnvarPrefix string) error { + if err := f.checkDuplicates(); err != nil { + return err + } + for _, flag := range f.long { + if defaultEnvarPrefix != "" && !flag.noEnvar && flag.envar == "" { + flag.envar = envarTransform(defaultEnvarPrefix + "_" + flag.name) + } + if err := flag.init(); err != nil { + return err + } + if flag.shorthand != 0 { + f.short[string(flag.shorthand)] = flag + } + } + return nil +} + +func (f *flagGroup) checkDuplicates() error { + seenShort := map[rune]bool{} + seenLong := map[string]bool{} + for _, flag := range f.flagOrder { + if flag.shorthand != 0 { + if _, ok := seenShort[flag.shorthand]; ok { + return fmt.Errorf("duplicate short flag -%c", flag.shorthand) + } + seenShort[flag.shorthand] = true + } + if _, ok := seenLong[flag.name]; ok { + return fmt.Errorf("duplicate long flag --%s", flag.name) + } + seenLong[flag.name] = true + } + return nil +} + +func (f *flagGroup) parse(context *ParseContext) (*FlagClause, error) { + var token *Token + +loop: + for { + token = context.Peek() + switch token.Type { + case TokenEOL: + break loop + + case TokenLong, TokenShort: + flagToken := token + defaultValue := "" + var flag *FlagClause + var ok bool + invert := false + + name := token.Value + if token.Type == TokenLong { + flag, ok = f.long[name] + if !ok { + if strings.HasPrefix(name, "no-") { + name = name[3:] + invert = true + } + flag, ok = f.long[name] + } + if !ok { + return nil, fmt.Errorf("unknown long flag '%s'", flagToken) + } + } else { + flag, ok = f.short[name] + if !ok { + return nil, fmt.Errorf("unknown short flag '%s'", flagToken) + } + } + + context.Next() + + fb, ok := flag.value.(boolFlag) + if ok && fb.IsBoolFlag() { + if invert { + defaultValue = "false" + } else { + defaultValue = "true" + } + } else { + if invert { + context.Push(token) + return nil, fmt.Errorf("unknown long flag '%s'", flagToken) + } + token = context.Peek() + if token.Type != TokenArg { + context.Push(token) + return nil, fmt.Errorf("expected argument for flag '%s'", flagToken) + } + context.Next() + defaultValue = token.Value + } + + context.matchedFlag(flag, defaultValue) + return flag, nil + + default: + break loop + } + } + return nil, nil +} + +// FlagClause is a fluid interface used to build flags. +type FlagClause struct { + parserMixin + actionMixin + completionsMixin + envarMixin + name string + shorthand rune + help string + defaultValues []string + placeholder string + hidden bool +} + +func newFlag(name, help string) *FlagClause { + f := &FlagClause{ + name: name, + help: help, + } + return f +} + +func (f *FlagClause) setDefault() error { + if f.HasEnvarValue() { + if v, ok := f.value.(repeatableFlag); !ok || !v.IsCumulative() { + // Use the value as-is + return f.value.Set(f.GetEnvarValue()) + } else { + for _, value := range f.GetSplitEnvarValue() { + if err := f.value.Set(value); err != nil { + return err + } + } + return nil + } + } + + if len(f.defaultValues) > 0 { + for _, defaultValue := range f.defaultValues { + if err := f.value.Set(defaultValue); err != nil { + return err + } + } + return nil + } + + return nil +} + +func (f *FlagClause) needsValue() bool { + haveDefault := len(f.defaultValues) > 0 + return f.required && !(haveDefault || f.HasEnvarValue()) +} + +func (f *FlagClause) init() error { + if f.required && len(f.defaultValues) > 0 { + return fmt.Errorf("required flag '--%s' with default value that will never be used", f.name) + } + if f.value == nil { + return fmt.Errorf("no type defined for --%s (eg. .String())", f.name) + } + if v, ok := f.value.(repeatableFlag); (!ok || !v.IsCumulative()) && len(f.defaultValues) > 1 { + return fmt.Errorf("invalid default for '--%s', expecting single value", f.name) + } + return nil +} + +// Dispatch to the given function after the flag is parsed and validated. +func (f *FlagClause) Action(action Action) *FlagClause { + f.addAction(action) + return f +} + +func (f *FlagClause) PreAction(action Action) *FlagClause { + f.addPreAction(action) + return f +} + +// HintAction registers a HintAction (function) for the flag to provide completions +func (a *FlagClause) HintAction(action HintAction) *FlagClause { + a.addHintAction(action) + return a +} + +// HintOptions registers any number of options for the flag to provide completions +func (a *FlagClause) HintOptions(options ...string) *FlagClause { + a.addHintAction(func() []string { + return options + }) + return a +} + +func (a *FlagClause) EnumVar(target *string, options ...string) { + a.parserMixin.EnumVar(target, options...) + a.addHintActionBuiltin(func() []string { + return options + }) +} + +func (a *FlagClause) Enum(options ...string) (target *string) { + a.addHintActionBuiltin(func() []string { + return options + }) + return a.parserMixin.Enum(options...) +} + +// Default values for this flag. They *must* be parseable by the value of the flag. +func (f *FlagClause) Default(values ...string) *FlagClause { + f.defaultValues = values + return f +} + +// DEPRECATED: Use Envar(name) instead. +func (f *FlagClause) OverrideDefaultFromEnvar(envar string) *FlagClause { + return f.Envar(envar) +} + +// Envar overrides the default value(s) for a flag from an environment variable, +// if it is set. Several default values can be provided by using new lines to +// separate them. +func (f *FlagClause) Envar(name string) *FlagClause { + f.envar = name + f.noEnvar = false + return f +} + +// NoEnvar forces environment variable defaults to be disabled for this flag. +// Most useful in conjunction with app.DefaultEnvars(). +func (f *FlagClause) NoEnvar() *FlagClause { + f.envar = "" + f.noEnvar = true + return f +} + +// PlaceHolder sets the place-holder string used for flag values in the help. The +// default behaviour is to use the value provided by Default() if provided, +// then fall back on the capitalized flag name. +func (f *FlagClause) PlaceHolder(placeholder string) *FlagClause { + f.placeholder = placeholder + return f +} + +// Hidden hides a flag from usage but still allows it to be used. +func (f *FlagClause) Hidden() *FlagClause { + f.hidden = true + return f +} + +// Required makes the flag required. You can not provide a Default() value to a Required() flag. +func (f *FlagClause) Required() *FlagClause { + f.required = true + return f +} + +// Short sets the short flag name. +func (f *FlagClause) Short(name rune) *FlagClause { + f.shorthand = name + return f +} + +// Bool makes this flag a boolean flag. +func (f *FlagClause) Bool() (target *bool) { + target = new(bool) + f.SetValue(newBoolValue(target)) + return +} diff --git a/vendor/gopkg.in/alecthomas/kingpin.v2/global.go b/vendor/gopkg.in/alecthomas/kingpin.v2/global.go new file mode 100644 index 00000000..10a29137 --- /dev/null +++ b/vendor/gopkg.in/alecthomas/kingpin.v2/global.go @@ -0,0 +1,94 @@ +package kingpin + +import ( + "os" + "path/filepath" +) + +var ( + // CommandLine is the default Kingpin parser. + CommandLine = New(filepath.Base(os.Args[0]), "") + // Global help flag. Exposed for user customisation. + HelpFlag = CommandLine.HelpFlag + // Top-level help command. Exposed for user customisation. May be nil. + HelpCommand = CommandLine.HelpCommand + // Global version flag. Exposed for user customisation. May be nil. + VersionFlag = CommandLine.VersionFlag +) + +// Command adds a new command to the default parser. +func Command(name, help string) *CmdClause { + return CommandLine.Command(name, help) +} + +// Flag adds a new flag to the default parser. +func Flag(name, help string) *FlagClause { + return CommandLine.Flag(name, help) +} + +// Arg adds a new argument to the top-level of the default parser. +func Arg(name, help string) *ArgClause { + return CommandLine.Arg(name, help) +} + +// Parse and return the selected command. Will call the termination handler if +// an error is encountered. +func Parse() string { + selected := MustParse(CommandLine.Parse(os.Args[1:])) + if selected == "" && CommandLine.cmdGroup.have() { + Usage() + CommandLine.terminate(0) + } + return selected +} + +// Errorf prints an error message to stderr. +func Errorf(format string, args ...interface{}) { + CommandLine.Errorf(format, args...) +} + +// Fatalf prints an error message to stderr and exits. +func Fatalf(format string, args ...interface{}) { + CommandLine.Fatalf(format, args...) +} + +// FatalIfError prints an error and exits if err is not nil. The error is printed +// with the given prefix. +func FatalIfError(err error, format string, args ...interface{}) { + CommandLine.FatalIfError(err, format, args...) +} + +// FatalUsage prints an error message followed by usage information, then +// exits with a non-zero status. +func FatalUsage(format string, args ...interface{}) { + CommandLine.FatalUsage(format, args...) +} + +// FatalUsageContext writes a printf formatted error message to stderr, then +// usage information for the given ParseContext, before exiting. +func FatalUsageContext(context *ParseContext, format string, args ...interface{}) { + CommandLine.FatalUsageContext(context, format, args...) +} + +// Usage prints usage to stderr. +func Usage() { + CommandLine.Usage(os.Args[1:]) +} + +// Set global usage template to use (defaults to DefaultUsageTemplate). +func UsageTemplate(template string) *Application { + return CommandLine.UsageTemplate(template) +} + +// MustParse can be used with app.Parse(args) to exit with an error if parsing fails. +func MustParse(command string, err error) string { + if err != nil { + Fatalf("%s, try --help", err) + } + return command +} + +// Version adds a flag for displaying the application version number. +func Version(version string) *Application { + return CommandLine.Version(version) +} diff --git a/vendor/gopkg.in/alecthomas/kingpin.v2/guesswidth.go b/vendor/gopkg.in/alecthomas/kingpin.v2/guesswidth.go new file mode 100644 index 00000000..a269531c --- /dev/null +++ b/vendor/gopkg.in/alecthomas/kingpin.v2/guesswidth.go @@ -0,0 +1,9 @@ +// +build appengine !linux,!freebsd,!darwin,!dragonfly,!netbsd,!openbsd + +package kingpin + +import "io" + +func guessWidth(w io.Writer) int { + return 80 +} diff --git a/vendor/gopkg.in/alecthomas/kingpin.v2/guesswidth_unix.go b/vendor/gopkg.in/alecthomas/kingpin.v2/guesswidth_unix.go new file mode 100644 index 00000000..ad8163f5 --- /dev/null +++ b/vendor/gopkg.in/alecthomas/kingpin.v2/guesswidth_unix.go @@ -0,0 +1,38 @@ +// +build !appengine,linux freebsd darwin dragonfly netbsd openbsd + +package kingpin + +import ( + "io" + "os" + "strconv" + "syscall" + "unsafe" +) + +func guessWidth(w io.Writer) int { + // check if COLUMNS env is set to comply with + // http://pubs.opengroup.org/onlinepubs/009604499/basedefs/xbd_chap08.html + colsStr := os.Getenv("COLUMNS") + if colsStr != "" { + if cols, err := strconv.Atoi(colsStr); err == nil { + return cols + } + } + + if t, ok := w.(*os.File); ok { + fd := t.Fd() + var dimensions [4]uint16 + + if _, _, err := syscall.Syscall6( + syscall.SYS_IOCTL, + uintptr(fd), + uintptr(syscall.TIOCGWINSZ), + uintptr(unsafe.Pointer(&dimensions)), + 0, 0, 0, + ); err == 0 { + return int(dimensions[1]) + } + } + return 80 +} diff --git a/vendor/gopkg.in/alecthomas/kingpin.v2/model.go b/vendor/gopkg.in/alecthomas/kingpin.v2/model.go new file mode 100644 index 00000000..a4ee83b4 --- /dev/null +++ b/vendor/gopkg.in/alecthomas/kingpin.v2/model.go @@ -0,0 +1,227 @@ +package kingpin + +import ( + "fmt" + "strconv" + "strings" +) + +// Data model for Kingpin command-line structure. + +type FlagGroupModel struct { + Flags []*FlagModel +} + +func (f *FlagGroupModel) FlagSummary() string { + out := []string{} + count := 0 + for _, flag := range f.Flags { + if flag.Name != "help" { + count++ + } + if flag.Required { + if flag.IsBoolFlag() { + out = append(out, fmt.Sprintf("--[no-]%s", flag.Name)) + } else { + out = append(out, fmt.Sprintf("--%s=%s", flag.Name, flag.FormatPlaceHolder())) + } + } + } + if count != len(out) { + out = append(out, "[]") + } + return strings.Join(out, " ") +} + +type FlagModel struct { + Name string + Help string + Short rune + Default []string + Envar string + PlaceHolder string + Required bool + Hidden bool + Value Value +} + +func (f *FlagModel) String() string { + return f.Value.String() +} + +func (f *FlagModel) IsBoolFlag() bool { + if fl, ok := f.Value.(boolFlag); ok { + return fl.IsBoolFlag() + } + return false +} + +func (f *FlagModel) FormatPlaceHolder() string { + if f.PlaceHolder != "" { + return f.PlaceHolder + } + if len(f.Default) > 0 { + ellipsis := "" + if len(f.Default) > 1 { + ellipsis = "..." + } + if _, ok := f.Value.(*stringValue); ok { + return strconv.Quote(f.Default[0]) + ellipsis + } + return f.Default[0] + ellipsis + } + return strings.ToUpper(f.Name) +} + +type ArgGroupModel struct { + Args []*ArgModel +} + +func (a *ArgGroupModel) ArgSummary() string { + depth := 0 + out := []string{} + for _, arg := range a.Args { + h := "<" + arg.Name + ">" + if !arg.Required { + h = "[" + h + depth++ + } + out = append(out, h) + } + out[len(out)-1] = out[len(out)-1] + strings.Repeat("]", depth) + return strings.Join(out, " ") +} + +type ArgModel struct { + Name string + Help string + Default []string + Envar string + Required bool + Value Value +} + +func (a *ArgModel) String() string { + return a.Value.String() +} + +type CmdGroupModel struct { + Commands []*CmdModel +} + +func (c *CmdGroupModel) FlattenedCommands() (out []*CmdModel) { + for _, cmd := range c.Commands { + if len(cmd.Commands) == 0 { + out = append(out, cmd) + } + out = append(out, cmd.FlattenedCommands()...) + } + return +} + +type CmdModel struct { + Name string + Aliases []string + Help string + FullCommand string + Depth int + Hidden bool + Default bool + *FlagGroupModel + *ArgGroupModel + *CmdGroupModel +} + +func (c *CmdModel) String() string { + return c.FullCommand +} + +type ApplicationModel struct { + Name string + Help string + Version string + Author string + *ArgGroupModel + *CmdGroupModel + *FlagGroupModel +} + +func (a *Application) Model() *ApplicationModel { + return &ApplicationModel{ + Name: a.Name, + Help: a.Help, + Version: a.version, + Author: a.author, + FlagGroupModel: a.flagGroup.Model(), + ArgGroupModel: a.argGroup.Model(), + CmdGroupModel: a.cmdGroup.Model(), + } +} + +func (a *argGroup) Model() *ArgGroupModel { + m := &ArgGroupModel{} + for _, arg := range a.args { + m.Args = append(m.Args, arg.Model()) + } + return m +} + +func (a *ArgClause) Model() *ArgModel { + return &ArgModel{ + Name: a.name, + Help: a.help, + Default: a.defaultValues, + Envar: a.envar, + Required: a.required, + Value: a.value, + } +} + +func (f *flagGroup) Model() *FlagGroupModel { + m := &FlagGroupModel{} + for _, fl := range f.flagOrder { + m.Flags = append(m.Flags, fl.Model()) + } + return m +} + +func (f *FlagClause) Model() *FlagModel { + return &FlagModel{ + Name: f.name, + Help: f.help, + Short: rune(f.shorthand), + Default: f.defaultValues, + Envar: f.envar, + PlaceHolder: f.placeholder, + Required: f.required, + Hidden: f.hidden, + Value: f.value, + } +} + +func (c *cmdGroup) Model() *CmdGroupModel { + m := &CmdGroupModel{} + for _, cm := range c.commandOrder { + m.Commands = append(m.Commands, cm.Model()) + } + return m +} + +func (c *CmdClause) Model() *CmdModel { + depth := 0 + for i := c; i != nil; i = i.parent { + depth++ + } + return &CmdModel{ + Name: c.name, + Aliases: c.aliases, + Help: c.help, + Depth: depth, + Hidden: c.hidden, + Default: c.isDefault, + FullCommand: c.FullCommand(), + FlagGroupModel: c.flagGroup.Model(), + ArgGroupModel: c.argGroup.Model(), + CmdGroupModel: c.cmdGroup.Model(), + } +} diff --git a/vendor/gopkg.in/alecthomas/kingpin.v2/parser.go b/vendor/gopkg.in/alecthomas/kingpin.v2/parser.go new file mode 100644 index 00000000..2a183519 --- /dev/null +++ b/vendor/gopkg.in/alecthomas/kingpin.v2/parser.go @@ -0,0 +1,396 @@ +package kingpin + +import ( + "bufio" + "fmt" + "os" + "strings" + "unicode/utf8" +) + +type TokenType int + +// Token types. +const ( + TokenShort TokenType = iota + TokenLong + TokenArg + TokenError + TokenEOL +) + +func (t TokenType) String() string { + switch t { + case TokenShort: + return "short flag" + case TokenLong: + return "long flag" + case TokenArg: + return "argument" + case TokenError: + return "error" + case TokenEOL: + return "" + } + return "?" +} + +var ( + TokenEOLMarker = Token{-1, TokenEOL, ""} +) + +type Token struct { + Index int + Type TokenType + Value string +} + +func (t *Token) Equal(o *Token) bool { + return t.Index == o.Index +} + +func (t *Token) IsFlag() bool { + return t.Type == TokenShort || t.Type == TokenLong +} + +func (t *Token) IsEOF() bool { + return t.Type == TokenEOL +} + +func (t *Token) String() string { + switch t.Type { + case TokenShort: + return "-" + t.Value + case TokenLong: + return "--" + t.Value + case TokenArg: + return t.Value + case TokenError: + return "error: " + t.Value + case TokenEOL: + return "" + default: + panic("unhandled type") + } +} + +// A union of possible elements in a parse stack. +type ParseElement struct { + // Clause is either *CmdClause, *ArgClause or *FlagClause. + Clause interface{} + // Value is corresponding value for an ArgClause or FlagClause (if any). + Value *string +} + +// ParseContext holds the current context of the parser. When passed to +// Action() callbacks Elements will be fully populated with *FlagClause, +// *ArgClause and *CmdClause values and their corresponding arguments (if +// any). +type ParseContext struct { + SelectedCommand *CmdClause + ignoreDefault bool + argsOnly bool + peek []*Token + argi int // Index of current command-line arg we're processing. + args []string + rawArgs []string + flags *flagGroup + arguments *argGroup + argumenti int // Cursor into arguments + // Flags, arguments and commands encountered and collected during parse. + Elements []*ParseElement +} + +func (p *ParseContext) nextArg() *ArgClause { + if p.argumenti >= len(p.arguments.args) { + return nil + } + arg := p.arguments.args[p.argumenti] + if !arg.consumesRemainder() { + p.argumenti++ + } + return arg +} + +func (p *ParseContext) next() { + p.argi++ + p.args = p.args[1:] +} + +// HasTrailingArgs returns true if there are unparsed command-line arguments. +// This can occur if the parser can not match remaining arguments. +func (p *ParseContext) HasTrailingArgs() bool { + return len(p.args) > 0 +} + +func tokenize(args []string, ignoreDefault bool) *ParseContext { + return &ParseContext{ + ignoreDefault: ignoreDefault, + args: args, + rawArgs: args, + flags: newFlagGroup(), + arguments: newArgGroup(), + } +} + +func (p *ParseContext) mergeFlags(flags *flagGroup) { + for _, flag := range flags.flagOrder { + if flag.shorthand != 0 { + p.flags.short[string(flag.shorthand)] = flag + } + p.flags.long[flag.name] = flag + p.flags.flagOrder = append(p.flags.flagOrder, flag) + } +} + +func (p *ParseContext) mergeArgs(args *argGroup) { + for _, arg := range args.args { + p.arguments.args = append(p.arguments.args, arg) + } +} + +func (p *ParseContext) EOL() bool { + return p.Peek().Type == TokenEOL +} + +func (p *ParseContext) Error() bool { + return p.Peek().Type == TokenError +} + +// Next token in the parse context. +func (p *ParseContext) Next() *Token { + if len(p.peek) > 0 { + return p.pop() + } + + // End of tokens. + if len(p.args) == 0 { + return &Token{Index: p.argi, Type: TokenEOL} + } + + arg := p.args[0] + p.next() + + if p.argsOnly { + return &Token{p.argi, TokenArg, arg} + } + + // All remaining args are passed directly. + if arg == "--" { + p.argsOnly = true + return p.Next() + } + + if strings.HasPrefix(arg, "--") { + parts := strings.SplitN(arg[2:], "=", 2) + token := &Token{p.argi, TokenLong, parts[0]} + if len(parts) == 2 { + p.Push(&Token{p.argi, TokenArg, parts[1]}) + } + return token + } + + if strings.HasPrefix(arg, "-") { + if len(arg) == 1 { + return &Token{Index: p.argi, Type: TokenShort} + } + shortRune, size := utf8.DecodeRuneInString(arg[1:]) + short := string(shortRune) + flag, ok := p.flags.short[short] + // Not a known short flag, we'll just return it anyway. + if !ok { + } else if fb, ok := flag.value.(boolFlag); ok && fb.IsBoolFlag() { + // Bool short flag. + } else { + // Short flag with combined argument: -fARG + token := &Token{p.argi, TokenShort, short} + if len(arg) > size+1 { + p.Push(&Token{p.argi, TokenArg, arg[size+1:]}) + } + return token + } + + if len(arg) > size+1 { + p.args = append([]string{"-" + arg[size+1:]}, p.args...) + } + return &Token{p.argi, TokenShort, short} + } else if strings.HasPrefix(arg, "@") { + expanded, err := ExpandArgsFromFile(arg[1:]) + if err != nil { + return &Token{p.argi, TokenError, err.Error()} + } + if len(p.args) == 0 { + p.args = expanded + } else { + p.args = append(expanded, p.args...) + } + return p.Next() + } + + return &Token{p.argi, TokenArg, arg} +} + +func (p *ParseContext) Peek() *Token { + if len(p.peek) == 0 { + return p.Push(p.Next()) + } + return p.peek[len(p.peek)-1] +} + +func (p *ParseContext) Push(token *Token) *Token { + p.peek = append(p.peek, token) + return token +} + +func (p *ParseContext) pop() *Token { + end := len(p.peek) - 1 + token := p.peek[end] + p.peek = p.peek[0:end] + return token +} + +func (p *ParseContext) String() string { + return p.SelectedCommand.FullCommand() +} + +func (p *ParseContext) matchedFlag(flag *FlagClause, value string) { + p.Elements = append(p.Elements, &ParseElement{Clause: flag, Value: &value}) +} + +func (p *ParseContext) matchedArg(arg *ArgClause, value string) { + p.Elements = append(p.Elements, &ParseElement{Clause: arg, Value: &value}) +} + +func (p *ParseContext) matchedCmd(cmd *CmdClause) { + p.Elements = append(p.Elements, &ParseElement{Clause: cmd}) + p.mergeFlags(cmd.flagGroup) + p.mergeArgs(cmd.argGroup) + p.SelectedCommand = cmd +} + +// Expand arguments from a file. Lines starting with # will be treated as comments. +func ExpandArgsFromFile(filename string) (out []string, err error) { + if filename == "" { + return nil, fmt.Errorf("expected @ file to expand arguments from") + } + r, err := os.Open(filename) + if err != nil { + return nil, fmt.Errorf("failed to open arguments file %q: %s", filename, err) + } + defer r.Close() + scanner := bufio.NewScanner(r) + for scanner.Scan() { + line := scanner.Text() + if strings.HasPrefix(line, "#") { + continue + } + out = append(out, line) + } + err = scanner.Err() + if err != nil { + return nil, fmt.Errorf("failed to read arguments from %q: %s", filename, err) + } + return +} + +func parse(context *ParseContext, app *Application) (err error) { + context.mergeFlags(app.flagGroup) + context.mergeArgs(app.argGroup) + + cmds := app.cmdGroup + ignoreDefault := context.ignoreDefault + +loop: + for !context.EOL() && !context.Error() { + token := context.Peek() + + switch token.Type { + case TokenLong, TokenShort: + if flag, err := context.flags.parse(context); err != nil { + if !ignoreDefault { + if cmd := cmds.defaultSubcommand(); cmd != nil { + cmd.completionAlts = cmds.cmdNames() + context.matchedCmd(cmd) + cmds = cmd.cmdGroup + break + } + } + return err + } else if flag == HelpFlag { + ignoreDefault = true + } + + case TokenArg: + if cmds.have() { + selectedDefault := false + cmd, ok := cmds.commands[token.String()] + if !ok { + if !ignoreDefault { + if cmd = cmds.defaultSubcommand(); cmd != nil { + cmd.completionAlts = cmds.cmdNames() + selectedDefault = true + } + } + if cmd == nil { + return fmt.Errorf("expected command but got %q", token) + } + } + if cmd == HelpCommand { + ignoreDefault = true + } + cmd.completionAlts = nil + context.matchedCmd(cmd) + cmds = cmd.cmdGroup + if !selectedDefault { + context.Next() + } + } else if context.arguments.have() { + if app.noInterspersed { + // no more flags + context.argsOnly = true + } + arg := context.nextArg() + if arg == nil { + break loop + } + context.matchedArg(arg, token.String()) + context.Next() + } else { + break loop + } + + case TokenEOL: + break loop + } + } + + // Move to innermost default command. + for !ignoreDefault { + if cmd := cmds.defaultSubcommand(); cmd != nil { + cmd.completionAlts = cmds.cmdNames() + context.matchedCmd(cmd) + cmds = cmd.cmdGroup + } else { + break + } + } + + if context.Error() { + return fmt.Errorf("%s", context.Peek().Value) + } + + if !context.EOL() { + return fmt.Errorf("unexpected %s", context.Peek()) + } + + // Set defaults for all remaining args. + for arg := context.nextArg(); arg != nil && !arg.consumesRemainder(); arg = context.nextArg() { + for _, defaultValue := range arg.defaultValues { + if err := arg.value.Set(defaultValue); err != nil { + return fmt.Errorf("invalid default value '%s' for argument '%s'", defaultValue, arg.name) + } + } + } + + return +} diff --git a/vendor/gopkg.in/alecthomas/kingpin.v2/parsers.go b/vendor/gopkg.in/alecthomas/kingpin.v2/parsers.go new file mode 100644 index 00000000..d9ad57e5 --- /dev/null +++ b/vendor/gopkg.in/alecthomas/kingpin.v2/parsers.go @@ -0,0 +1,212 @@ +package kingpin + +import ( + "net" + "net/url" + "os" + "time" + + "github.com/alecthomas/units" +) + +type Settings interface { + SetValue(value Value) +} + +type parserMixin struct { + value Value + required bool +} + +func (p *parserMixin) SetValue(value Value) { + p.value = value +} + +// StringMap provides key=value parsing into a map. +func (p *parserMixin) StringMap() (target *map[string]string) { + target = &(map[string]string{}) + p.StringMapVar(target) + return +} + +// Duration sets the parser to a time.Duration parser. +func (p *parserMixin) Duration() (target *time.Duration) { + target = new(time.Duration) + p.DurationVar(target) + return +} + +// Bytes parses numeric byte units. eg. 1.5KB +func (p *parserMixin) Bytes() (target *units.Base2Bytes) { + target = new(units.Base2Bytes) + p.BytesVar(target) + return +} + +// IP sets the parser to a net.IP parser. +func (p *parserMixin) IP() (target *net.IP) { + target = new(net.IP) + p.IPVar(target) + return +} + +// TCP (host:port) address. +func (p *parserMixin) TCP() (target **net.TCPAddr) { + target = new(*net.TCPAddr) + p.TCPVar(target) + return +} + +// TCPVar (host:port) address. +func (p *parserMixin) TCPVar(target **net.TCPAddr) { + p.SetValue(newTCPAddrValue(target)) +} + +// ExistingFile sets the parser to one that requires and returns an existing file. +func (p *parserMixin) ExistingFile() (target *string) { + target = new(string) + p.ExistingFileVar(target) + return +} + +// ExistingDir sets the parser to one that requires and returns an existing directory. +func (p *parserMixin) ExistingDir() (target *string) { + target = new(string) + p.ExistingDirVar(target) + return +} + +// ExistingFileOrDir sets the parser to one that requires and returns an existing file OR directory. +func (p *parserMixin) ExistingFileOrDir() (target *string) { + target = new(string) + p.ExistingFileOrDirVar(target) + return +} + +// File returns an os.File against an existing file. +func (p *parserMixin) File() (target **os.File) { + target = new(*os.File) + p.FileVar(target) + return +} + +// File attempts to open a File with os.OpenFile(flag, perm). +func (p *parserMixin) OpenFile(flag int, perm os.FileMode) (target **os.File) { + target = new(*os.File) + p.OpenFileVar(target, flag, perm) + return +} + +// URL provides a valid, parsed url.URL. +func (p *parserMixin) URL() (target **url.URL) { + target = new(*url.URL) + p.URLVar(target) + return +} + +// StringMap provides key=value parsing into a map. +func (p *parserMixin) StringMapVar(target *map[string]string) { + p.SetValue(newStringMapValue(target)) +} + +// Float sets the parser to a float64 parser. +func (p *parserMixin) Float() (target *float64) { + return p.Float64() +} + +// Float sets the parser to a float64 parser. +func (p *parserMixin) FloatVar(target *float64) { + p.Float64Var(target) +} + +// Duration sets the parser to a time.Duration parser. +func (p *parserMixin) DurationVar(target *time.Duration) { + p.SetValue(newDurationValue(target)) +} + +// BytesVar parses numeric byte units. eg. 1.5KB +func (p *parserMixin) BytesVar(target *units.Base2Bytes) { + p.SetValue(newBytesValue(target)) +} + +// IP sets the parser to a net.IP parser. +func (p *parserMixin) IPVar(target *net.IP) { + p.SetValue(newIPValue(target)) +} + +// ExistingFile sets the parser to one that requires and returns an existing file. +func (p *parserMixin) ExistingFileVar(target *string) { + p.SetValue(newExistingFileValue(target)) +} + +// ExistingDir sets the parser to one that requires and returns an existing directory. +func (p *parserMixin) ExistingDirVar(target *string) { + p.SetValue(newExistingDirValue(target)) +} + +// ExistingDir sets the parser to one that requires and returns an existing directory. +func (p *parserMixin) ExistingFileOrDirVar(target *string) { + p.SetValue(newExistingFileOrDirValue(target)) +} + +// FileVar opens an existing file. +func (p *parserMixin) FileVar(target **os.File) { + p.SetValue(newFileValue(target, os.O_RDONLY, 0)) +} + +// OpenFileVar calls os.OpenFile(flag, perm) +func (p *parserMixin) OpenFileVar(target **os.File, flag int, perm os.FileMode) { + p.SetValue(newFileValue(target, flag, perm)) +} + +// URL provides a valid, parsed url.URL. +func (p *parserMixin) URLVar(target **url.URL) { + p.SetValue(newURLValue(target)) +} + +// URLList provides a parsed list of url.URL values. +func (p *parserMixin) URLList() (target *[]*url.URL) { + target = new([]*url.URL) + p.URLListVar(target) + return +} + +// URLListVar provides a parsed list of url.URL values. +func (p *parserMixin) URLListVar(target *[]*url.URL) { + p.SetValue(newURLListValue(target)) +} + +// Enum allows a value from a set of options. +func (p *parserMixin) Enum(options ...string) (target *string) { + target = new(string) + p.EnumVar(target, options...) + return +} + +// EnumVar allows a value from a set of options. +func (p *parserMixin) EnumVar(target *string, options ...string) { + p.SetValue(newEnumFlag(target, options...)) +} + +// Enums allows a set of values from a set of options. +func (p *parserMixin) Enums(options ...string) (target *[]string) { + target = new([]string) + p.EnumsVar(target, options...) + return +} + +// EnumVar allows a value from a set of options. +func (p *parserMixin) EnumsVar(target *[]string, options ...string) { + p.SetValue(newEnumsFlag(target, options...)) +} + +// A Counter increments a number each time it is encountered. +func (p *parserMixin) Counter() (target *int) { + target = new(int) + p.CounterVar(target) + return +} + +func (p *parserMixin) CounterVar(target *int) { + p.SetValue(newCounterValue(target)) +} diff --git a/vendor/gopkg.in/alecthomas/kingpin.v2/templates.go b/vendor/gopkg.in/alecthomas/kingpin.v2/templates.go new file mode 100644 index 00000000..97b5c9fc --- /dev/null +++ b/vendor/gopkg.in/alecthomas/kingpin.v2/templates.go @@ -0,0 +1,262 @@ +package kingpin + +// Default usage template. +var DefaultUsageTemplate = `{{define "FormatCommand"}}\ +{{if .FlagSummary}} {{.FlagSummary}}{{end}}\ +{{range .Args}} {{if not .Required}}[{{end}}<{{.Name}}>{{if .Value|IsCumulative}}...{{end}}{{if not .Required}}]{{end}}{{end}}\ +{{end}}\ + +{{define "FormatCommands"}}\ +{{range .FlattenedCommands}}\ +{{if not .Hidden}}\ + {{.FullCommand}}{{if .Default}}*{{end}}{{template "FormatCommand" .}} +{{.Help|Wrap 4}} +{{end}}\ +{{end}}\ +{{end}}\ + +{{define "FormatUsage"}}\ +{{template "FormatCommand" .}}{{if .Commands}} [ ...]{{end}} +{{if .Help}} +{{.Help|Wrap 0}}\ +{{end}}\ + +{{end}}\ + +{{if .Context.SelectedCommand}}\ +usage: {{.App.Name}} {{.Context.SelectedCommand}}{{template "FormatUsage" .Context.SelectedCommand}} +{{else}}\ +usage: {{.App.Name}}{{template "FormatUsage" .App}} +{{end}}\ +{{if .Context.Flags}}\ +Flags: +{{.Context.Flags|FlagsToTwoColumns|FormatTwoColumns}} +{{end}}\ +{{if .Context.Args}}\ +Args: +{{.Context.Args|ArgsToTwoColumns|FormatTwoColumns}} +{{end}}\ +{{if .Context.SelectedCommand}}\ +{{if len .Context.SelectedCommand.Commands}}\ +Subcommands: +{{template "FormatCommands" .Context.SelectedCommand}} +{{end}}\ +{{else if .App.Commands}}\ +Commands: +{{template "FormatCommands" .App}} +{{end}}\ +` + +// Usage template where command's optional flags are listed separately +var SeparateOptionalFlagsUsageTemplate = `{{define "FormatCommand"}}\ +{{if .FlagSummary}} {{.FlagSummary}}{{end}}\ +{{range .Args}} {{if not .Required}}[{{end}}<{{.Name}}>{{if .Value|IsCumulative}}...{{end}}{{if not .Required}}]{{end}}{{end}}\ +{{end}}\ + +{{define "FormatCommands"}}\ +{{range .FlattenedCommands}}\ +{{if not .Hidden}}\ + {{.FullCommand}}{{if .Default}}*{{end}}{{template "FormatCommand" .}} +{{.Help|Wrap 4}} +{{end}}\ +{{end}}\ +{{end}}\ + +{{define "FormatUsage"}}\ +{{template "FormatCommand" .}}{{if .Commands}} [ ...]{{end}} +{{if .Help}} +{{.Help|Wrap 0}}\ +{{end}}\ + +{{end}}\ +{{if .Context.SelectedCommand}}\ +usage: {{.App.Name}} {{.Context.SelectedCommand}}{{template "FormatUsage" .Context.SelectedCommand}} +{{else}}\ +usage: {{.App.Name}}{{template "FormatUsage" .App}} +{{end}}\ + +{{if .Context.Flags|RequiredFlags}}\ +Required flags: +{{.Context.Flags|RequiredFlags|FlagsToTwoColumns|FormatTwoColumns}} +{{end}}\ +{{if .Context.Flags|OptionalFlags}}\ +Optional flags: +{{.Context.Flags|OptionalFlags|FlagsToTwoColumns|FormatTwoColumns}} +{{end}}\ +{{if .Context.Args}}\ +Args: +{{.Context.Args|ArgsToTwoColumns|FormatTwoColumns}} +{{end}}\ +{{if .Context.SelectedCommand}}\ +Subcommands: +{{if .Context.SelectedCommand.Commands}}\ +{{template "FormatCommands" .Context.SelectedCommand}} +{{end}}\ +{{else if .App.Commands}}\ +Commands: +{{template "FormatCommands" .App}} +{{end}}\ +` + +// Usage template with compactly formatted commands. +var CompactUsageTemplate = `{{define "FormatCommand"}}\ +{{if .FlagSummary}} {{.FlagSummary}}{{end}}\ +{{range .Args}} {{if not .Required}}[{{end}}<{{.Name}}>{{if .Value|IsCumulative}}...{{end}}{{if not .Required}}]{{end}}{{end}}\ +{{end}}\ + +{{define "FormatCommandList"}}\ +{{range .}}\ +{{if not .Hidden}}\ +{{.Depth|Indent}}{{.Name}}{{if .Default}}*{{end}}{{template "FormatCommand" .}} +{{end}}\ +{{template "FormatCommandList" .Commands}}\ +{{end}}\ +{{end}}\ + +{{define "FormatUsage"}}\ +{{template "FormatCommand" .}}{{if .Commands}} [ ...]{{end}} +{{if .Help}} +{{.Help|Wrap 0}}\ +{{end}}\ + +{{end}}\ + +{{if .Context.SelectedCommand}}\ +usage: {{.App.Name}} {{.Context.SelectedCommand}}{{template "FormatUsage" .Context.SelectedCommand}} +{{else}}\ +usage: {{.App.Name}}{{template "FormatUsage" .App}} +{{end}}\ +{{if .Context.Flags}}\ +Flags: +{{.Context.Flags|FlagsToTwoColumns|FormatTwoColumns}} +{{end}}\ +{{if .Context.Args}}\ +Args: +{{.Context.Args|ArgsToTwoColumns|FormatTwoColumns}} +{{end}}\ +{{if .Context.SelectedCommand}}\ +{{if .Context.SelectedCommand.Commands}}\ +Commands: + {{.Context.SelectedCommand}} +{{template "FormatCommandList" .Context.SelectedCommand.Commands}} +{{end}}\ +{{else if .App.Commands}}\ +Commands: +{{template "FormatCommandList" .App.Commands}} +{{end}}\ +` + +var ManPageTemplate = `{{define "FormatFlags"}}\ +{{range .Flags}}\ +{{if not .Hidden}}\ +.TP +\fB{{if .Short}}-{{.Short|Char}}, {{end}}--{{.Name}}{{if not .IsBoolFlag}}={{.FormatPlaceHolder}}{{end}}\\fR +{{.Help}} +{{end}}\ +{{end}}\ +{{end}}\ + +{{define "FormatCommand"}}\ +{{if .FlagSummary}} {{.FlagSummary}}{{end}}\ +{{range .Args}} {{if not .Required}}[{{end}}<{{.Name}}{{if .Default}}*{{end}}>{{if .Value|IsCumulative}}...{{end}}{{if not .Required}}]{{end}}{{end}}\ +{{end}}\ + +{{define "FormatCommands"}}\ +{{range .FlattenedCommands}}\ +{{if not .Hidden}}\ +.SS +\fB{{.FullCommand}}{{template "FormatCommand" .}}\\fR +.PP +{{.Help}} +{{template "FormatFlags" .}}\ +{{end}}\ +{{end}}\ +{{end}}\ + +{{define "FormatUsage"}}\ +{{template "FormatCommand" .}}{{if .Commands}} [ ...]{{end}}\\fR +{{end}}\ + +.TH {{.App.Name}} 1 {{.App.Version}} "{{.App.Author}}" +.SH "NAME" +{{.App.Name}} +.SH "SYNOPSIS" +.TP +\fB{{.App.Name}}{{template "FormatUsage" .App}} +.SH "DESCRIPTION" +{{.App.Help}} +.SH "OPTIONS" +{{template "FormatFlags" .App}}\ +{{if .App.Commands}}\ +.SH "COMMANDS" +{{template "FormatCommands" .App}}\ +{{end}}\ +` + +// Default usage template. +var LongHelpTemplate = `{{define "FormatCommand"}}\ +{{if .FlagSummary}} {{.FlagSummary}}{{end}}\ +{{range .Args}} {{if not .Required}}[{{end}}<{{.Name}}>{{if .Value|IsCumulative}}...{{end}}{{if not .Required}}]{{end}}{{end}}\ +{{end}}\ + +{{define "FormatCommands"}}\ +{{range .FlattenedCommands}}\ +{{if not .Hidden}}\ + {{.FullCommand}}{{template "FormatCommand" .}} +{{.Help|Wrap 4}} +{{with .Flags|FlagsToTwoColumns}}{{FormatTwoColumnsWithIndent . 4 2}}{{end}} +{{end}}\ +{{end}}\ +{{end}}\ + +{{define "FormatUsage"}}\ +{{template "FormatCommand" .}}{{if .Commands}} [ ...]{{end}} +{{if .Help}} +{{.Help|Wrap 0}}\ +{{end}}\ + +{{end}}\ + +usage: {{.App.Name}}{{template "FormatUsage" .App}} +{{if .Context.Flags}}\ +Flags: +{{.Context.Flags|FlagsToTwoColumns|FormatTwoColumns}} +{{end}}\ +{{if .Context.Args}}\ +Args: +{{.Context.Args|ArgsToTwoColumns|FormatTwoColumns}} +{{end}}\ +{{if .App.Commands}}\ +Commands: +{{template "FormatCommands" .App}} +{{end}}\ +` + +var BashCompletionTemplate = ` +_{{.App.Name}}_bash_autocomplete() { + local cur prev opts base + COMPREPLY=() + cur="${COMP_WORDS[COMP_CWORD]}" + opts=$( ${COMP_WORDS[0]} --completion-bash ${COMP_WORDS[@]:1:$COMP_CWORD} ) + COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) + return 0 +} +complete -F _{{.App.Name}}_bash_autocomplete {{.App.Name}} + +` + +var ZshCompletionTemplate = ` +#compdef {{.App.Name}} +autoload -U compinit && compinit +autoload -U bashcompinit && bashcompinit + +_{{.App.Name}}_bash_autocomplete() { + local cur prev opts base + COMPREPLY=() + cur="${COMP_WORDS[COMP_CWORD]}" + opts=$( ${COMP_WORDS[0]} --completion-bash ${COMP_WORDS[@]:1:$COMP_CWORD} ) + COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) + return 0 +} +complete -F _{{.App.Name}}_bash_autocomplete {{.App.Name}} +` diff --git a/vendor/gopkg.in/alecthomas/kingpin.v2/usage.go b/vendor/gopkg.in/alecthomas/kingpin.v2/usage.go new file mode 100644 index 00000000..44af6f65 --- /dev/null +++ b/vendor/gopkg.in/alecthomas/kingpin.v2/usage.go @@ -0,0 +1,211 @@ +package kingpin + +import ( + "bytes" + "fmt" + "go/doc" + "io" + "strings" + + "github.com/alecthomas/template" +) + +var ( + preIndent = " " +) + +func formatTwoColumns(w io.Writer, indent, padding, width int, rows [][2]string) { + // Find size of first column. + s := 0 + for _, row := range rows { + if c := len(row[0]); c > s && c < 30 { + s = c + } + } + + indentStr := strings.Repeat(" ", indent) + offsetStr := strings.Repeat(" ", s+padding) + + for _, row := range rows { + buf := bytes.NewBuffer(nil) + doc.ToText(buf, row[1], "", preIndent, width-s-padding-indent) + lines := strings.Split(strings.TrimRight(buf.String(), "\n"), "\n") + fmt.Fprintf(w, "%s%-*s%*s", indentStr, s, row[0], padding, "") + if len(row[0]) >= 30 { + fmt.Fprintf(w, "\n%s%s", indentStr, offsetStr) + } + fmt.Fprintf(w, "%s\n", lines[0]) + for _, line := range lines[1:] { + fmt.Fprintf(w, "%s%s%s\n", indentStr, offsetStr, line) + } + } +} + +// Usage writes application usage to w. It parses args to determine +// appropriate help context, such as which command to show help for. +func (a *Application) Usage(args []string) { + context, err := a.parseContext(true, args) + a.FatalIfError(err, "") + if err := a.UsageForContextWithTemplate(context, 2, a.usageTemplate); err != nil { + panic(err) + } +} + +func formatAppUsage(app *ApplicationModel) string { + s := []string{app.Name} + if len(app.Flags) > 0 { + s = append(s, app.FlagSummary()) + } + if len(app.Args) > 0 { + s = append(s, app.ArgSummary()) + } + return strings.Join(s, " ") +} + +func formatCmdUsage(app *ApplicationModel, cmd *CmdModel) string { + s := []string{app.Name, cmd.String()} + if len(app.Flags) > 0 { + s = append(s, app.FlagSummary()) + } + if len(app.Args) > 0 { + s = append(s, app.ArgSummary()) + } + return strings.Join(s, " ") +} + +func formatFlag(haveShort bool, flag *FlagModel) string { + flagString := "" + if flag.Short != 0 { + flagString += fmt.Sprintf("-%c, --%s", flag.Short, flag.Name) + } else { + if haveShort { + flagString += fmt.Sprintf(" --%s", flag.Name) + } else { + flagString += fmt.Sprintf("--%s", flag.Name) + } + } + if !flag.IsBoolFlag() { + flagString += fmt.Sprintf("=%s", flag.FormatPlaceHolder()) + } + if v, ok := flag.Value.(repeatableFlag); ok && v.IsCumulative() { + flagString += " ..." + } + return flagString +} + +type templateParseContext struct { + SelectedCommand *CmdModel + *FlagGroupModel + *ArgGroupModel +} + +type templateContext struct { + App *ApplicationModel + Width int + Context *templateParseContext +} + +// UsageForContext displays usage information from a ParseContext (obtained from +// Application.ParseContext() or Action(f) callbacks). +func (a *Application) UsageForContext(context *ParseContext) error { + return a.UsageForContextWithTemplate(context, 2, a.usageTemplate) +} + +// UsageForContextWithTemplate is the base usage function. You generally don't need to use this. +func (a *Application) UsageForContextWithTemplate(context *ParseContext, indent int, tmpl string) error { + width := guessWidth(a.usageWriter) + funcs := template.FuncMap{ + "Indent": func(level int) string { + return strings.Repeat(" ", level*indent) + }, + "Wrap": func(indent int, s string) string { + buf := bytes.NewBuffer(nil) + indentText := strings.Repeat(" ", indent) + doc.ToText(buf, s, indentText, " "+indentText, width-indent) + return buf.String() + }, + "FormatFlag": formatFlag, + "FlagsToTwoColumns": func(f []*FlagModel) [][2]string { + rows := [][2]string{} + haveShort := false + for _, flag := range f { + if flag.Short != 0 { + haveShort = true + break + } + } + for _, flag := range f { + if !flag.Hidden { + rows = append(rows, [2]string{formatFlag(haveShort, flag), flag.Help}) + } + } + return rows + }, + "RequiredFlags": func(f []*FlagModel) []*FlagModel { + requiredFlags := []*FlagModel{} + for _, flag := range f { + if flag.Required { + requiredFlags = append(requiredFlags, flag) + } + } + return requiredFlags + }, + "OptionalFlags": func(f []*FlagModel) []*FlagModel { + optionalFlags := []*FlagModel{} + for _, flag := range f { + if !flag.Required { + optionalFlags = append(optionalFlags, flag) + } + } + return optionalFlags + }, + "ArgsToTwoColumns": func(a []*ArgModel) [][2]string { + rows := [][2]string{} + for _, arg := range a { + s := "<" + arg.Name + ">" + if !arg.Required { + s = "[" + s + "]" + } + rows = append(rows, [2]string{s, arg.Help}) + } + return rows + }, + "FormatTwoColumns": func(rows [][2]string) string { + buf := bytes.NewBuffer(nil) + formatTwoColumns(buf, indent, indent, width, rows) + return buf.String() + }, + "FormatTwoColumnsWithIndent": func(rows [][2]string, indent, padding int) string { + buf := bytes.NewBuffer(nil) + formatTwoColumns(buf, indent, padding, width, rows) + return buf.String() + }, + "FormatAppUsage": formatAppUsage, + "FormatCommandUsage": formatCmdUsage, + "IsCumulative": func(value Value) bool { + r, ok := value.(remainderArg) + return ok && r.IsCumulative() + }, + "Char": func(c rune) string { + return string(c) + }, + } + t, err := template.New("usage").Funcs(funcs).Parse(tmpl) + if err != nil { + return err + } + var selectedCommand *CmdModel + if context.SelectedCommand != nil { + selectedCommand = context.SelectedCommand.Model() + } + ctx := templateContext{ + App: a.Model(), + Width: width, + Context: &templateParseContext{ + SelectedCommand: selectedCommand, + FlagGroupModel: context.flags.Model(), + ArgGroupModel: context.arguments.Model(), + }, + } + return t.Execute(a.usageWriter, ctx) +} diff --git a/vendor/gopkg.in/alecthomas/kingpin.v2/values.go b/vendor/gopkg.in/alecthomas/kingpin.v2/values.go new file mode 100644 index 00000000..7ee9a3b3 --- /dev/null +++ b/vendor/gopkg.in/alecthomas/kingpin.v2/values.go @@ -0,0 +1,470 @@ +package kingpin + +//go:generate go run ./cmd/genvalues/main.go + +import ( + "fmt" + "net" + "net/url" + "os" + "reflect" + "regexp" + "strings" + "time" + + "github.com/alecthomas/units" +) + +// NOTE: Most of the base type values were lifted from: +// http://golang.org/src/pkg/flag/flag.go?s=20146:20222 + +// Value is the interface to the dynamic value stored in a flag. +// (The default value is represented as a string.) +// +// If a Value has an IsBoolFlag() bool method returning true, the command-line +// parser makes --name equivalent to -name=true rather than using the next +// command-line argument, and adds a --no-name counterpart for negating the +// flag. +type Value interface { + String() string + Set(string) error +} + +// Getter is an interface that allows the contents of a Value to be retrieved. +// It wraps the Value interface, rather than being part of it, because it +// appeared after Go 1 and its compatibility rules. All Value types provided +// by this package satisfy the Getter interface. +type Getter interface { + Value + Get() interface{} +} + +// Optional interface to indicate boolean flags that don't accept a value, and +// implicitly have a --no- negation counterpart. +type boolFlag interface { + Value + IsBoolFlag() bool +} + +// Optional interface for arguments that cumulatively consume all remaining +// input. +type remainderArg interface { + Value + IsCumulative() bool +} + +// Optional interface for flags that can be repeated. +type repeatableFlag interface { + Value + IsCumulative() bool +} + +type accumulator struct { + element func(value interface{}) Value + typ reflect.Type + slice reflect.Value +} + +// Use reflection to accumulate values into a slice. +// +// target := []string{} +// newAccumulator(&target, func (value interface{}) Value { +// return newStringValue(value.(*string)) +// }) +func newAccumulator(slice interface{}, element func(value interface{}) Value) *accumulator { + typ := reflect.TypeOf(slice) + if typ.Kind() != reflect.Ptr || typ.Elem().Kind() != reflect.Slice { + panic("expected a pointer to a slice") + } + return &accumulator{ + element: element, + typ: typ.Elem().Elem(), + slice: reflect.ValueOf(slice), + } +} + +func (a *accumulator) String() string { + out := []string{} + s := a.slice.Elem() + for i := 0; i < s.Len(); i++ { + out = append(out, a.element(s.Index(i).Addr().Interface()).String()) + } + return strings.Join(out, ",") +} + +func (a *accumulator) Set(value string) error { + e := reflect.New(a.typ) + if err := a.element(e.Interface()).Set(value); err != nil { + return err + } + slice := reflect.Append(a.slice.Elem(), e.Elem()) + a.slice.Elem().Set(slice) + return nil +} + +func (a *accumulator) Get() interface{} { + return a.slice.Interface() +} + +func (a *accumulator) IsCumulative() bool { + return true +} + +func (b *boolValue) IsBoolFlag() bool { return true } + +// -- time.Duration Value +type durationValue time.Duration + +func newDurationValue(p *time.Duration) *durationValue { + return (*durationValue)(p) +} + +func (d *durationValue) Set(s string) error { + v, err := time.ParseDuration(s) + *d = durationValue(v) + return err +} + +func (d *durationValue) Get() interface{} { return time.Duration(*d) } + +func (d *durationValue) String() string { return (*time.Duration)(d).String() } + +// -- map[string]string Value +type stringMapValue map[string]string + +func newStringMapValue(p *map[string]string) *stringMapValue { + return (*stringMapValue)(p) +} + +var stringMapRegex = regexp.MustCompile("[:=]") + +func (s *stringMapValue) Set(value string) error { + parts := stringMapRegex.Split(value, 2) + if len(parts) != 2 { + return fmt.Errorf("expected KEY=VALUE got '%s'", value) + } + (*s)[parts[0]] = parts[1] + return nil +} + +func (s *stringMapValue) Get() interface{} { + return (map[string]string)(*s) +} + +func (s *stringMapValue) String() string { + return fmt.Sprintf("%s", map[string]string(*s)) +} + +func (s *stringMapValue) IsCumulative() bool { + return true +} + +// -- net.IP Value +type ipValue net.IP + +func newIPValue(p *net.IP) *ipValue { + return (*ipValue)(p) +} + +func (i *ipValue) Set(value string) error { + if ip := net.ParseIP(value); ip == nil { + return fmt.Errorf("'%s' is not an IP address", value) + } else { + *i = *(*ipValue)(&ip) + return nil + } +} + +func (i *ipValue) Get() interface{} { + return (net.IP)(*i) +} + +func (i *ipValue) String() string { + return (*net.IP)(i).String() +} + +// -- *net.TCPAddr Value +type tcpAddrValue struct { + addr **net.TCPAddr +} + +func newTCPAddrValue(p **net.TCPAddr) *tcpAddrValue { + return &tcpAddrValue{p} +} + +func (i *tcpAddrValue) Set(value string) error { + if addr, err := net.ResolveTCPAddr("tcp", value); err != nil { + return fmt.Errorf("'%s' is not a valid TCP address: %s", value, err) + } else { + *i.addr = addr + return nil + } +} + +func (t *tcpAddrValue) Get() interface{} { + return (*net.TCPAddr)(*t.addr) +} + +func (i *tcpAddrValue) String() string { + return (*i.addr).String() +} + +// -- existingFile Value + +type fileStatValue struct { + path *string + predicate func(os.FileInfo) error +} + +func newFileStatValue(p *string, predicate func(os.FileInfo) error) *fileStatValue { + return &fileStatValue{ + path: p, + predicate: predicate, + } +} + +func (e *fileStatValue) Set(value string) error { + if s, err := os.Stat(value); os.IsNotExist(err) { + return fmt.Errorf("path '%s' does not exist", value) + } else if err != nil { + return err + } else if err := e.predicate(s); err != nil { + return err + } + *e.path = value + return nil +} + +func (f *fileStatValue) Get() interface{} { + return (string)(*f.path) +} + +func (e *fileStatValue) String() string { + return *e.path +} + +// -- os.File value + +type fileValue struct { + f **os.File + flag int + perm os.FileMode +} + +func newFileValue(p **os.File, flag int, perm os.FileMode) *fileValue { + return &fileValue{p, flag, perm} +} + +func (f *fileValue) Set(value string) error { + if fd, err := os.OpenFile(value, f.flag, f.perm); err != nil { + return err + } else { + *f.f = fd + return nil + } +} + +func (f *fileValue) Get() interface{} { + return (*os.File)(*f.f) +} + +func (f *fileValue) String() string { + if *f.f == nil { + return "" + } + return (*f.f).Name() +} + +// -- url.URL Value +type urlValue struct { + u **url.URL +} + +func newURLValue(p **url.URL) *urlValue { + return &urlValue{p} +} + +func (u *urlValue) Set(value string) error { + if url, err := url.Parse(value); err != nil { + return fmt.Errorf("invalid URL: %s", err) + } else { + *u.u = url + return nil + } +} + +func (u *urlValue) Get() interface{} { + return (*url.URL)(*u.u) +} + +func (u *urlValue) String() string { + if *u.u == nil { + return "" + } + return (*u.u).String() +} + +// -- []*url.URL Value +type urlListValue []*url.URL + +func newURLListValue(p *[]*url.URL) *urlListValue { + return (*urlListValue)(p) +} + +func (u *urlListValue) Set(value string) error { + if url, err := url.Parse(value); err != nil { + return fmt.Errorf("invalid URL: %s", err) + } else { + *u = append(*u, url) + return nil + } +} + +func (u *urlListValue) Get() interface{} { + return ([]*url.URL)(*u) +} + +func (u *urlListValue) String() string { + out := []string{} + for _, url := range *u { + out = append(out, url.String()) + } + return strings.Join(out, ",") +} + +func (u *urlListValue) IsCumulative() bool { + return true +} + +// A flag whose value must be in a set of options. +type enumValue struct { + value *string + options []string +} + +func newEnumFlag(target *string, options ...string) *enumValue { + return &enumValue{ + value: target, + options: options, + } +} + +func (a *enumValue) String() string { + return *a.value +} + +func (a *enumValue) Set(value string) error { + for _, v := range a.options { + if v == value { + *a.value = value + return nil + } + } + return fmt.Errorf("enum value must be one of %s, got '%s'", strings.Join(a.options, ","), value) +} + +func (e *enumValue) Get() interface{} { + return (string)(*e.value) +} + +// -- []string Enum Value +type enumsValue struct { + value *[]string + options []string +} + +func newEnumsFlag(target *[]string, options ...string) *enumsValue { + return &enumsValue{ + value: target, + options: options, + } +} + +func (s *enumsValue) Set(value string) error { + for _, v := range s.options { + if v == value { + *s.value = append(*s.value, value) + return nil + } + } + return fmt.Errorf("enum value must be one of %s, got '%s'", strings.Join(s.options, ","), value) +} + +func (e *enumsValue) Get() interface{} { + return ([]string)(*e.value) +} + +func (s *enumsValue) String() string { + return strings.Join(*s.value, ",") +} + +func (s *enumsValue) IsCumulative() bool { + return true +} + +// -- units.Base2Bytes Value +type bytesValue units.Base2Bytes + +func newBytesValue(p *units.Base2Bytes) *bytesValue { + return (*bytesValue)(p) +} + +func (d *bytesValue) Set(s string) error { + v, err := units.ParseBase2Bytes(s) + *d = bytesValue(v) + return err +} + +func (d *bytesValue) Get() interface{} { return units.Base2Bytes(*d) } + +func (d *bytesValue) String() string { return (*units.Base2Bytes)(d).String() } + +func newExistingFileValue(target *string) *fileStatValue { + return newFileStatValue(target, func(s os.FileInfo) error { + if s.IsDir() { + return fmt.Errorf("'%s' is a directory", s.Name()) + } + return nil + }) +} + +func newExistingDirValue(target *string) *fileStatValue { + return newFileStatValue(target, func(s os.FileInfo) error { + if !s.IsDir() { + return fmt.Errorf("'%s' is a file", s.Name()) + } + return nil + }) +} + +func newExistingFileOrDirValue(target *string) *fileStatValue { + return newFileStatValue(target, func(s os.FileInfo) error { return nil }) +} + +type counterValue int + +func newCounterValue(n *int) *counterValue { + return (*counterValue)(n) +} + +func (c *counterValue) Set(s string) error { + *c++ + return nil +} + +func (c *counterValue) Get() interface{} { return (int)(*c) } +func (c *counterValue) IsBoolFlag() bool { return true } +func (c *counterValue) String() string { return fmt.Sprintf("%d", *c) } +func (c *counterValue) IsCumulative() bool { return true } + +func resolveHost(value string) (net.IP, error) { + if ip := net.ParseIP(value); ip != nil { + return ip, nil + } else { + if addr, err := net.ResolveIPAddr("ip", value); err != nil { + return nil, err + } else { + return addr.IP, nil + } + } +} diff --git a/vendor/gopkg.in/alecthomas/kingpin.v2/values.json b/vendor/gopkg.in/alecthomas/kingpin.v2/values.json new file mode 100644 index 00000000..23c67448 --- /dev/null +++ b/vendor/gopkg.in/alecthomas/kingpin.v2/values.json @@ -0,0 +1,25 @@ +[ + {"type": "bool", "parser": "strconv.ParseBool(s)"}, + {"type": "string", "parser": "s, error(nil)", "format": "string(*f.v)", "plural": "Strings"}, + {"type": "uint", "parser": "strconv.ParseUint(s, 0, 64)", "plural": "Uints"}, + {"type": "uint8", "parser": "strconv.ParseUint(s, 0, 8)"}, + {"type": "uint16", "parser": "strconv.ParseUint(s, 0, 16)"}, + {"type": "uint32", "parser": "strconv.ParseUint(s, 0, 32)"}, + {"type": "uint64", "parser": "strconv.ParseUint(s, 0, 64)"}, + {"type": "int", "parser": "strconv.ParseFloat(s, 64)", "plural": "Ints"}, + {"type": "int8", "parser": "strconv.ParseInt(s, 0, 8)"}, + {"type": "int16", "parser": "strconv.ParseInt(s, 0, 16)"}, + {"type": "int32", "parser": "strconv.ParseInt(s, 0, 32)"}, + {"type": "int64", "parser": "strconv.ParseInt(s, 0, 64)"}, + {"type": "float64", "parser": "strconv.ParseFloat(s, 64)"}, + {"type": "float32", "parser": "strconv.ParseFloat(s, 32)"}, + {"name": "Duration", "type": "time.Duration", "no_value_parser": true}, + {"name": "IP", "type": "net.IP", "no_value_parser": true}, + {"name": "TCPAddr", "Type": "*net.TCPAddr", "plural": "TCPList", "no_value_parser": true}, + {"name": "ExistingFile", "Type": "string", "plural": "ExistingFiles", "no_value_parser": true}, + {"name": "ExistingDir", "Type": "string", "plural": "ExistingDirs", "no_value_parser": true}, + {"name": "ExistingFileOrDir", "Type": "string", "plural": "ExistingFilesOrDirs", "no_value_parser": true}, + {"name": "Regexp", "Type": "*regexp.Regexp", "parser": "regexp.Compile(s)"}, + {"name": "ResolvedIP", "Type": "net.IP", "parser": "resolveHost(s)", "help": "Resolve a hostname or IP to an IP."}, + {"name": "HexBytes", "Type": "[]byte", "parser": "hex.DecodeString(s)", "help": "Bytes as a hex string."} +] diff --git a/vendor/gopkg.in/alecthomas/kingpin.v2/values_generated.go b/vendor/gopkg.in/alecthomas/kingpin.v2/values_generated.go new file mode 100644 index 00000000..8d492bf9 --- /dev/null +++ b/vendor/gopkg.in/alecthomas/kingpin.v2/values_generated.go @@ -0,0 +1,821 @@ +package kingpin + +import ( + "encoding/hex" + "fmt" + "net" + "regexp" + "strconv" + "time" +) + +// This file is autogenerated by "go generate .". Do not modify. + +// -- bool Value +type boolValue struct{ v *bool } + +func newBoolValue(p *bool) *boolValue { + return &boolValue{p} +} + +func (f *boolValue) Set(s string) error { + v, err := strconv.ParseBool(s) + if err == nil { + *f.v = (bool)(v) + } + return err +} + +func (f *boolValue) Get() interface{} { return (bool)(*f.v) } + +func (f *boolValue) String() string { return fmt.Sprintf("%v", *f.v) } + +// Bool parses the next command-line value as bool. +func (p *parserMixin) Bool() (target *bool) { + target = new(bool) + p.BoolVar(target) + return +} + +func (p *parserMixin) BoolVar(target *bool) { + p.SetValue(newBoolValue(target)) +} + +// BoolList accumulates bool values into a slice. +func (p *parserMixin) BoolList() (target *[]bool) { + target = new([]bool) + p.BoolListVar(target) + return +} + +func (p *parserMixin) BoolListVar(target *[]bool) { + p.SetValue(newAccumulator(target, func(v interface{}) Value { + return newBoolValue(v.(*bool)) + })) +} + +// -- string Value +type stringValue struct{ v *string } + +func newStringValue(p *string) *stringValue { + return &stringValue{p} +} + +func (f *stringValue) Set(s string) error { + v, err := s, error(nil) + if err == nil { + *f.v = (string)(v) + } + return err +} + +func (f *stringValue) Get() interface{} { return (string)(*f.v) } + +func (f *stringValue) String() string { return string(*f.v) } + +// String parses the next command-line value as string. +func (p *parserMixin) String() (target *string) { + target = new(string) + p.StringVar(target) + return +} + +func (p *parserMixin) StringVar(target *string) { + p.SetValue(newStringValue(target)) +} + +// Strings accumulates string values into a slice. +func (p *parserMixin) Strings() (target *[]string) { + target = new([]string) + p.StringsVar(target) + return +} + +func (p *parserMixin) StringsVar(target *[]string) { + p.SetValue(newAccumulator(target, func(v interface{}) Value { + return newStringValue(v.(*string)) + })) +} + +// -- uint Value +type uintValue struct{ v *uint } + +func newUintValue(p *uint) *uintValue { + return &uintValue{p} +} + +func (f *uintValue) Set(s string) error { + v, err := strconv.ParseUint(s, 0, 64) + if err == nil { + *f.v = (uint)(v) + } + return err +} + +func (f *uintValue) Get() interface{} { return (uint)(*f.v) } + +func (f *uintValue) String() string { return fmt.Sprintf("%v", *f.v) } + +// Uint parses the next command-line value as uint. +func (p *parserMixin) Uint() (target *uint) { + target = new(uint) + p.UintVar(target) + return +} + +func (p *parserMixin) UintVar(target *uint) { + p.SetValue(newUintValue(target)) +} + +// Uints accumulates uint values into a slice. +func (p *parserMixin) Uints() (target *[]uint) { + target = new([]uint) + p.UintsVar(target) + return +} + +func (p *parserMixin) UintsVar(target *[]uint) { + p.SetValue(newAccumulator(target, func(v interface{}) Value { + return newUintValue(v.(*uint)) + })) +} + +// -- uint8 Value +type uint8Value struct{ v *uint8 } + +func newUint8Value(p *uint8) *uint8Value { + return &uint8Value{p} +} + +func (f *uint8Value) Set(s string) error { + v, err := strconv.ParseUint(s, 0, 8) + if err == nil { + *f.v = (uint8)(v) + } + return err +} + +func (f *uint8Value) Get() interface{} { return (uint8)(*f.v) } + +func (f *uint8Value) String() string { return fmt.Sprintf("%v", *f.v) } + +// Uint8 parses the next command-line value as uint8. +func (p *parserMixin) Uint8() (target *uint8) { + target = new(uint8) + p.Uint8Var(target) + return +} + +func (p *parserMixin) Uint8Var(target *uint8) { + p.SetValue(newUint8Value(target)) +} + +// Uint8List accumulates uint8 values into a slice. +func (p *parserMixin) Uint8List() (target *[]uint8) { + target = new([]uint8) + p.Uint8ListVar(target) + return +} + +func (p *parserMixin) Uint8ListVar(target *[]uint8) { + p.SetValue(newAccumulator(target, func(v interface{}) Value { + return newUint8Value(v.(*uint8)) + })) +} + +// -- uint16 Value +type uint16Value struct{ v *uint16 } + +func newUint16Value(p *uint16) *uint16Value { + return &uint16Value{p} +} + +func (f *uint16Value) Set(s string) error { + v, err := strconv.ParseUint(s, 0, 16) + if err == nil { + *f.v = (uint16)(v) + } + return err +} + +func (f *uint16Value) Get() interface{} { return (uint16)(*f.v) } + +func (f *uint16Value) String() string { return fmt.Sprintf("%v", *f.v) } + +// Uint16 parses the next command-line value as uint16. +func (p *parserMixin) Uint16() (target *uint16) { + target = new(uint16) + p.Uint16Var(target) + return +} + +func (p *parserMixin) Uint16Var(target *uint16) { + p.SetValue(newUint16Value(target)) +} + +// Uint16List accumulates uint16 values into a slice. +func (p *parserMixin) Uint16List() (target *[]uint16) { + target = new([]uint16) + p.Uint16ListVar(target) + return +} + +func (p *parserMixin) Uint16ListVar(target *[]uint16) { + p.SetValue(newAccumulator(target, func(v interface{}) Value { + return newUint16Value(v.(*uint16)) + })) +} + +// -- uint32 Value +type uint32Value struct{ v *uint32 } + +func newUint32Value(p *uint32) *uint32Value { + return &uint32Value{p} +} + +func (f *uint32Value) Set(s string) error { + v, err := strconv.ParseUint(s, 0, 32) + if err == nil { + *f.v = (uint32)(v) + } + return err +} + +func (f *uint32Value) Get() interface{} { return (uint32)(*f.v) } + +func (f *uint32Value) String() string { return fmt.Sprintf("%v", *f.v) } + +// Uint32 parses the next command-line value as uint32. +func (p *parserMixin) Uint32() (target *uint32) { + target = new(uint32) + p.Uint32Var(target) + return +} + +func (p *parserMixin) Uint32Var(target *uint32) { + p.SetValue(newUint32Value(target)) +} + +// Uint32List accumulates uint32 values into a slice. +func (p *parserMixin) Uint32List() (target *[]uint32) { + target = new([]uint32) + p.Uint32ListVar(target) + return +} + +func (p *parserMixin) Uint32ListVar(target *[]uint32) { + p.SetValue(newAccumulator(target, func(v interface{}) Value { + return newUint32Value(v.(*uint32)) + })) +} + +// -- uint64 Value +type uint64Value struct{ v *uint64 } + +func newUint64Value(p *uint64) *uint64Value { + return &uint64Value{p} +} + +func (f *uint64Value) Set(s string) error { + v, err := strconv.ParseUint(s, 0, 64) + if err == nil { + *f.v = (uint64)(v) + } + return err +} + +func (f *uint64Value) Get() interface{} { return (uint64)(*f.v) } + +func (f *uint64Value) String() string { return fmt.Sprintf("%v", *f.v) } + +// Uint64 parses the next command-line value as uint64. +func (p *parserMixin) Uint64() (target *uint64) { + target = new(uint64) + p.Uint64Var(target) + return +} + +func (p *parserMixin) Uint64Var(target *uint64) { + p.SetValue(newUint64Value(target)) +} + +// Uint64List accumulates uint64 values into a slice. +func (p *parserMixin) Uint64List() (target *[]uint64) { + target = new([]uint64) + p.Uint64ListVar(target) + return +} + +func (p *parserMixin) Uint64ListVar(target *[]uint64) { + p.SetValue(newAccumulator(target, func(v interface{}) Value { + return newUint64Value(v.(*uint64)) + })) +} + +// -- int Value +type intValue struct{ v *int } + +func newIntValue(p *int) *intValue { + return &intValue{p} +} + +func (f *intValue) Set(s string) error { + v, err := strconv.ParseFloat(s, 64) + if err == nil { + *f.v = (int)(v) + } + return err +} + +func (f *intValue) Get() interface{} { return (int)(*f.v) } + +func (f *intValue) String() string { return fmt.Sprintf("%v", *f.v) } + +// Int parses the next command-line value as int. +func (p *parserMixin) Int() (target *int) { + target = new(int) + p.IntVar(target) + return +} + +func (p *parserMixin) IntVar(target *int) { + p.SetValue(newIntValue(target)) +} + +// Ints accumulates int values into a slice. +func (p *parserMixin) Ints() (target *[]int) { + target = new([]int) + p.IntsVar(target) + return +} + +func (p *parserMixin) IntsVar(target *[]int) { + p.SetValue(newAccumulator(target, func(v interface{}) Value { + return newIntValue(v.(*int)) + })) +} + +// -- int8 Value +type int8Value struct{ v *int8 } + +func newInt8Value(p *int8) *int8Value { + return &int8Value{p} +} + +func (f *int8Value) Set(s string) error { + v, err := strconv.ParseInt(s, 0, 8) + if err == nil { + *f.v = (int8)(v) + } + return err +} + +func (f *int8Value) Get() interface{} { return (int8)(*f.v) } + +func (f *int8Value) String() string { return fmt.Sprintf("%v", *f.v) } + +// Int8 parses the next command-line value as int8. +func (p *parserMixin) Int8() (target *int8) { + target = new(int8) + p.Int8Var(target) + return +} + +func (p *parserMixin) Int8Var(target *int8) { + p.SetValue(newInt8Value(target)) +} + +// Int8List accumulates int8 values into a slice. +func (p *parserMixin) Int8List() (target *[]int8) { + target = new([]int8) + p.Int8ListVar(target) + return +} + +func (p *parserMixin) Int8ListVar(target *[]int8) { + p.SetValue(newAccumulator(target, func(v interface{}) Value { + return newInt8Value(v.(*int8)) + })) +} + +// -- int16 Value +type int16Value struct{ v *int16 } + +func newInt16Value(p *int16) *int16Value { + return &int16Value{p} +} + +func (f *int16Value) Set(s string) error { + v, err := strconv.ParseInt(s, 0, 16) + if err == nil { + *f.v = (int16)(v) + } + return err +} + +func (f *int16Value) Get() interface{} { return (int16)(*f.v) } + +func (f *int16Value) String() string { return fmt.Sprintf("%v", *f.v) } + +// Int16 parses the next command-line value as int16. +func (p *parserMixin) Int16() (target *int16) { + target = new(int16) + p.Int16Var(target) + return +} + +func (p *parserMixin) Int16Var(target *int16) { + p.SetValue(newInt16Value(target)) +} + +// Int16List accumulates int16 values into a slice. +func (p *parserMixin) Int16List() (target *[]int16) { + target = new([]int16) + p.Int16ListVar(target) + return +} + +func (p *parserMixin) Int16ListVar(target *[]int16) { + p.SetValue(newAccumulator(target, func(v interface{}) Value { + return newInt16Value(v.(*int16)) + })) +} + +// -- int32 Value +type int32Value struct{ v *int32 } + +func newInt32Value(p *int32) *int32Value { + return &int32Value{p} +} + +func (f *int32Value) Set(s string) error { + v, err := strconv.ParseInt(s, 0, 32) + if err == nil { + *f.v = (int32)(v) + } + return err +} + +func (f *int32Value) Get() interface{} { return (int32)(*f.v) } + +func (f *int32Value) String() string { return fmt.Sprintf("%v", *f.v) } + +// Int32 parses the next command-line value as int32. +func (p *parserMixin) Int32() (target *int32) { + target = new(int32) + p.Int32Var(target) + return +} + +func (p *parserMixin) Int32Var(target *int32) { + p.SetValue(newInt32Value(target)) +} + +// Int32List accumulates int32 values into a slice. +func (p *parserMixin) Int32List() (target *[]int32) { + target = new([]int32) + p.Int32ListVar(target) + return +} + +func (p *parserMixin) Int32ListVar(target *[]int32) { + p.SetValue(newAccumulator(target, func(v interface{}) Value { + return newInt32Value(v.(*int32)) + })) +} + +// -- int64 Value +type int64Value struct{ v *int64 } + +func newInt64Value(p *int64) *int64Value { + return &int64Value{p} +} + +func (f *int64Value) Set(s string) error { + v, err := strconv.ParseInt(s, 0, 64) + if err == nil { + *f.v = (int64)(v) + } + return err +} + +func (f *int64Value) Get() interface{} { return (int64)(*f.v) } + +func (f *int64Value) String() string { return fmt.Sprintf("%v", *f.v) } + +// Int64 parses the next command-line value as int64. +func (p *parserMixin) Int64() (target *int64) { + target = new(int64) + p.Int64Var(target) + return +} + +func (p *parserMixin) Int64Var(target *int64) { + p.SetValue(newInt64Value(target)) +} + +// Int64List accumulates int64 values into a slice. +func (p *parserMixin) Int64List() (target *[]int64) { + target = new([]int64) + p.Int64ListVar(target) + return +} + +func (p *parserMixin) Int64ListVar(target *[]int64) { + p.SetValue(newAccumulator(target, func(v interface{}) Value { + return newInt64Value(v.(*int64)) + })) +} + +// -- float64 Value +type float64Value struct{ v *float64 } + +func newFloat64Value(p *float64) *float64Value { + return &float64Value{p} +} + +func (f *float64Value) Set(s string) error { + v, err := strconv.ParseFloat(s, 64) + if err == nil { + *f.v = (float64)(v) + } + return err +} + +func (f *float64Value) Get() interface{} { return (float64)(*f.v) } + +func (f *float64Value) String() string { return fmt.Sprintf("%v", *f.v) } + +// Float64 parses the next command-line value as float64. +func (p *parserMixin) Float64() (target *float64) { + target = new(float64) + p.Float64Var(target) + return +} + +func (p *parserMixin) Float64Var(target *float64) { + p.SetValue(newFloat64Value(target)) +} + +// Float64List accumulates float64 values into a slice. +func (p *parserMixin) Float64List() (target *[]float64) { + target = new([]float64) + p.Float64ListVar(target) + return +} + +func (p *parserMixin) Float64ListVar(target *[]float64) { + p.SetValue(newAccumulator(target, func(v interface{}) Value { + return newFloat64Value(v.(*float64)) + })) +} + +// -- float32 Value +type float32Value struct{ v *float32 } + +func newFloat32Value(p *float32) *float32Value { + return &float32Value{p} +} + +func (f *float32Value) Set(s string) error { + v, err := strconv.ParseFloat(s, 32) + if err == nil { + *f.v = (float32)(v) + } + return err +} + +func (f *float32Value) Get() interface{} { return (float32)(*f.v) } + +func (f *float32Value) String() string { return fmt.Sprintf("%v", *f.v) } + +// Float32 parses the next command-line value as float32. +func (p *parserMixin) Float32() (target *float32) { + target = new(float32) + p.Float32Var(target) + return +} + +func (p *parserMixin) Float32Var(target *float32) { + p.SetValue(newFloat32Value(target)) +} + +// Float32List accumulates float32 values into a slice. +func (p *parserMixin) Float32List() (target *[]float32) { + target = new([]float32) + p.Float32ListVar(target) + return +} + +func (p *parserMixin) Float32ListVar(target *[]float32) { + p.SetValue(newAccumulator(target, func(v interface{}) Value { + return newFloat32Value(v.(*float32)) + })) +} + +// DurationList accumulates time.Duration values into a slice. +func (p *parserMixin) DurationList() (target *[]time.Duration) { + target = new([]time.Duration) + p.DurationListVar(target) + return +} + +func (p *parserMixin) DurationListVar(target *[]time.Duration) { + p.SetValue(newAccumulator(target, func(v interface{}) Value { + return newDurationValue(v.(*time.Duration)) + })) +} + +// IPList accumulates net.IP values into a slice. +func (p *parserMixin) IPList() (target *[]net.IP) { + target = new([]net.IP) + p.IPListVar(target) + return +} + +func (p *parserMixin) IPListVar(target *[]net.IP) { + p.SetValue(newAccumulator(target, func(v interface{}) Value { + return newIPValue(v.(*net.IP)) + })) +} + +// TCPList accumulates *net.TCPAddr values into a slice. +func (p *parserMixin) TCPList() (target *[]*net.TCPAddr) { + target = new([]*net.TCPAddr) + p.TCPListVar(target) + return +} + +func (p *parserMixin) TCPListVar(target *[]*net.TCPAddr) { + p.SetValue(newAccumulator(target, func(v interface{}) Value { + return newTCPAddrValue(v.(**net.TCPAddr)) + })) +} + +// ExistingFiles accumulates string values into a slice. +func (p *parserMixin) ExistingFiles() (target *[]string) { + target = new([]string) + p.ExistingFilesVar(target) + return +} + +func (p *parserMixin) ExistingFilesVar(target *[]string) { + p.SetValue(newAccumulator(target, func(v interface{}) Value { + return newExistingFileValue(v.(*string)) + })) +} + +// ExistingDirs accumulates string values into a slice. +func (p *parserMixin) ExistingDirs() (target *[]string) { + target = new([]string) + p.ExistingDirsVar(target) + return +} + +func (p *parserMixin) ExistingDirsVar(target *[]string) { + p.SetValue(newAccumulator(target, func(v interface{}) Value { + return newExistingDirValue(v.(*string)) + })) +} + +// ExistingFilesOrDirs accumulates string values into a slice. +func (p *parserMixin) ExistingFilesOrDirs() (target *[]string) { + target = new([]string) + p.ExistingFilesOrDirsVar(target) + return +} + +func (p *parserMixin) ExistingFilesOrDirsVar(target *[]string) { + p.SetValue(newAccumulator(target, func(v interface{}) Value { + return newExistingFileOrDirValue(v.(*string)) + })) +} + +// -- *regexp.Regexp Value +type regexpValue struct{ v **regexp.Regexp } + +func newRegexpValue(p **regexp.Regexp) *regexpValue { + return ®expValue{p} +} + +func (f *regexpValue) Set(s string) error { + v, err := regexp.Compile(s) + if err == nil { + *f.v = (*regexp.Regexp)(v) + } + return err +} + +func (f *regexpValue) Get() interface{} { return (*regexp.Regexp)(*f.v) } + +func (f *regexpValue) String() string { return fmt.Sprintf("%v", *f.v) } + +// Regexp parses the next command-line value as *regexp.Regexp. +func (p *parserMixin) Regexp() (target **regexp.Regexp) { + target = new(*regexp.Regexp) + p.RegexpVar(target) + return +} + +func (p *parserMixin) RegexpVar(target **regexp.Regexp) { + p.SetValue(newRegexpValue(target)) +} + +// RegexpList accumulates *regexp.Regexp values into a slice. +func (p *parserMixin) RegexpList() (target *[]*regexp.Regexp) { + target = new([]*regexp.Regexp) + p.RegexpListVar(target) + return +} + +func (p *parserMixin) RegexpListVar(target *[]*regexp.Regexp) { + p.SetValue(newAccumulator(target, func(v interface{}) Value { + return newRegexpValue(v.(**regexp.Regexp)) + })) +} + +// -- net.IP Value +type resolvedIPValue struct{ v *net.IP } + +func newResolvedIPValue(p *net.IP) *resolvedIPValue { + return &resolvedIPValue{p} +} + +func (f *resolvedIPValue) Set(s string) error { + v, err := resolveHost(s) + if err == nil { + *f.v = (net.IP)(v) + } + return err +} + +func (f *resolvedIPValue) Get() interface{} { return (net.IP)(*f.v) } + +func (f *resolvedIPValue) String() string { return fmt.Sprintf("%v", *f.v) } + +// Resolve a hostname or IP to an IP. +func (p *parserMixin) ResolvedIP() (target *net.IP) { + target = new(net.IP) + p.ResolvedIPVar(target) + return +} + +func (p *parserMixin) ResolvedIPVar(target *net.IP) { + p.SetValue(newResolvedIPValue(target)) +} + +// ResolvedIPList accumulates net.IP values into a slice. +func (p *parserMixin) ResolvedIPList() (target *[]net.IP) { + target = new([]net.IP) + p.ResolvedIPListVar(target) + return +} + +func (p *parserMixin) ResolvedIPListVar(target *[]net.IP) { + p.SetValue(newAccumulator(target, func(v interface{}) Value { + return newResolvedIPValue(v.(*net.IP)) + })) +} + +// -- []byte Value +type hexBytesValue struct{ v *[]byte } + +func newHexBytesValue(p *[]byte) *hexBytesValue { + return &hexBytesValue{p} +} + +func (f *hexBytesValue) Set(s string) error { + v, err := hex.DecodeString(s) + if err == nil { + *f.v = ([]byte)(v) + } + return err +} + +func (f *hexBytesValue) Get() interface{} { return ([]byte)(*f.v) } + +func (f *hexBytesValue) String() string { return fmt.Sprintf("%v", *f.v) } + +// Bytes as a hex string. +func (p *parserMixin) HexBytes() (target *[]byte) { + target = new([]byte) + p.HexBytesVar(target) + return +} + +func (p *parserMixin) HexBytesVar(target *[]byte) { + p.SetValue(newHexBytesValue(target)) +} + +// HexBytesList accumulates []byte values into a slice. +func (p *parserMixin) HexBytesList() (target *[][]byte) { + target = new([][]byte) + p.HexBytesListVar(target) + return +} + +func (p *parserMixin) HexBytesListVar(target *[][]byte) { + p.SetValue(newAccumulator(target, func(v interface{}) Value { + return newHexBytesValue(v.(*[]byte)) + })) +} diff --git a/vendor/knative.dev/pkg/apis/duck/v1/addressable_types.go b/vendor/knative.dev/pkg/apis/duck/v1/addressable_types.go index 3cfcac33..db39ab86 100644 --- a/vendor/knative.dev/pkg/apis/duck/v1/addressable_types.go +++ b/vendor/knative.dev/pkg/apis/duck/v1/addressable_types.go @@ -63,8 +63,10 @@ type AddressStatus struct { Address *Addressable `json:"address,omitempty"` } +// Verify AddressableType resources meet duck contracts. var ( - _ apis.Listable = (*AddressableType)(nil) + _ apis.Listable = (*AddressableType)(nil) + _ ducktypes.Populatable = (*AddressableType)(nil) ) // GetFullType implements duck.Implementable diff --git a/vendor/knative.dev/pkg/apis/duck/v1/kresource_type.go b/vendor/knative.dev/pkg/apis/duck/v1/kresource_type.go index 78d8ece6..491c13f1 100644 --- a/vendor/knative.dev/pkg/apis/duck/v1/kresource_type.go +++ b/vendor/knative.dev/pkg/apis/duck/v1/kresource_type.go @@ -23,6 +23,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" + "knative.dev/pkg/apis/duck/ducktypes" "knative.dev/pkg/apis" ) @@ -66,6 +67,12 @@ func (t *KResource) Populate() { }} } +// Verify KResource resources meet duck contracts. +var ( + _ apis.Listable = (*KResource)(nil) + _ ducktypes.Populatable = (*KResource)(nil) +) + // GetListType implements apis.Listable func (*KResource) GetListType() runtime.Object { return &KResourceList{} diff --git a/vendor/knative.dev/pkg/apis/duck/v1/podspec_types.go b/vendor/knative.dev/pkg/apis/duck/v1/podspec_types.go index 92ed7f1c..a8d17241 100644 --- a/vendor/knative.dev/pkg/apis/duck/v1/podspec_types.go +++ b/vendor/knative.dev/pkg/apis/duck/v1/podspec_types.go @@ -31,6 +31,9 @@ import ( // in the manner of ReplicaSet, Deployment, DaemonSet, StatefulSet. type PodSpecable corev1.PodTemplateSpec +// PodSpecable is an Implementable duck type. +var _ ducktypes.Implementable = (*PodSpecable)(nil) + // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object // WithPod is the shell that demonstrates how PodSpecable types wrap @@ -47,10 +50,10 @@ type WithPodSpec struct { Template PodSpecable `json:"template,omitempty"` } -// Assert that we implement the interfaces necessary to -// use duck.VerifyType. +// Verify WithPod resources meet duck contracts. var ( - _ apis.Listable = (*WithPod)(nil) + _ apis.Listable = (*WithPod)(nil) + _ ducktypes.Populatable = (*WithPod)(nil) ) // GetFullType implements duck.Implementable diff --git a/vendor/knative.dev/pkg/apis/duck/v1/source_types.go b/vendor/knative.dev/pkg/apis/duck/v1/source_types.go index 03c4d40b..2f1afe24 100644 --- a/vendor/knative.dev/pkg/apis/duck/v1/source_types.go +++ b/vendor/knative.dev/pkg/apis/duck/v1/source_types.go @@ -67,7 +67,7 @@ type CloudEventOverrides struct { // SourceStatus shows how we expect folks to embed Addressable in // their Status field. type SourceStatus struct { - // inherits duck/v1beta1 Status, which currently provides: + // inherits Status, which currently provides: // * ObservedGeneration - the 'Generation' of the Service that was last // processed by the controller. // * Conditions - the latest available observations of a resource's current @@ -88,7 +88,6 @@ type SourceStatus struct { // CloudEventAttributes specifies the attributes that a Source // uses as part of its CloudEvents. type CloudEventAttributes struct { - // Type refers to the CloudEvent type attribute. Type string `json:"type,omitempty"` @@ -109,8 +108,11 @@ func (ss *SourceStatus) IsReady() bool { return false } +// Verify Source resources meet duck contracts. var ( - _ apis.Listable = (*Source)(nil) + _ apis.Listable = (*Source)(nil) + _ ducktypes.Implementable = (*Source)(nil) + _ ducktypes.Populatable = (*Source)(nil) ) const ( diff --git a/vendor/knative.dev/pkg/apis/duck/v1/status_types.go b/vendor/knative.dev/pkg/apis/duck/v1/status_types.go index f25212e5..5ea75769 100644 --- a/vendor/knative.dev/pkg/apis/duck/v1/status_types.go +++ b/vendor/knative.dev/pkg/apis/duck/v1/status_types.go @@ -29,6 +29,9 @@ import ( // Conditions is a simple wrapper around apis.Conditions to implement duck.Implementable. type Conditions apis.Conditions +// Conditions is an Implementable duck type. +var _ ducktypes.Implementable = (*Conditions)(nil) + // Status shows how we expect folks to embed Conditions in // their Status field. // WARNING: Adding fields to this struct will add them to all Knative resources. diff --git a/vendor/knative.dev/pkg/controller/controller.go b/vendor/knative.dev/pkg/controller/controller.go index 6024ec25..03f67b6e 100644 --- a/vendor/knative.dev/pkg/controller/controller.go +++ b/vendor/knative.dev/pkg/controller/controller.go @@ -452,7 +452,7 @@ func (c *Impl) processNextWorkItem() bool { if err != nil { status = falseString } - c.statsReporter.ReportReconcile(time.Since(startTime), keyStr, status) + c.statsReporter.ReportReconcile(time.Since(startTime), status) }() // Embed the key into the logger and attach that to the context we pass diff --git a/vendor/knative.dev/pkg/controller/stats_reporter.go b/vendor/knative.dev/pkg/controller/stats_reporter.go index c3445ed3..6efaeca0 100644 --- a/vendor/knative.dev/pkg/controller/stats_reporter.go +++ b/vendor/knative.dev/pkg/controller/stats_reporter.go @@ -46,7 +46,6 @@ var ( // - length between 1 and 255 inclusive // - characters are printable US-ASCII reconcilerTagKey = tag.MustNewKey("reconciler") - keyTagKey = tag.MustNewKey("key") successTagKey = tag.MustNewKey("success") ) @@ -167,12 +166,12 @@ func init() { Description: "Number of reconcile operations", Measure: reconcileCountStat, Aggregation: view.Count(), - TagKeys: []tag.Key{reconcilerTagKey, keyTagKey, successTagKey}, + TagKeys: []tag.Key{reconcilerTagKey, successTagKey}, }, { Description: "Latency of reconcile operations", Measure: reconcileLatencyStat, Aggregation: reconcileDistribution, - TagKeys: []tag.Key{reconcilerTagKey, keyTagKey, successTagKey}, + TagKeys: []tag.Key{reconcilerTagKey, successTagKey}, }} views = append(views, wp.DefaultViews()...) views = append(views, rp.DefaultViews()...) @@ -192,7 +191,7 @@ type StatsReporter interface { ReportQueueDepth(v int64) error // ReportReconcile reports the count and latency metrics for a reconcile operation - ReportReconcile(duration time.Duration, key, success string) error + ReportReconcile(duration time.Duration, success string) error } // Reporter holds cached metric objects to report metrics @@ -234,11 +233,10 @@ func (r *reporter) ReportQueueDepth(v int64) error { } // ReportReconcile reports the count and latency metrics for a reconcile operation -func (r *reporter) ReportReconcile(duration time.Duration, key, success string) error { +func (r *reporter) ReportReconcile(duration time.Duration, success string) error { ctx, err := tag.New( context.Background(), tag.Insert(reconcilerTagKey, r.reconciler), - tag.Insert(keyTagKey, key), tag.Insert(successTagKey, success)) if err != nil { return err diff --git a/vendor/knative.dev/pkg/hash/bucketer.go b/vendor/knative.dev/pkg/hash/bucketer.go new file mode 100644 index 00000000..bdeeadb4 --- /dev/null +++ b/vendor/knative.dev/pkg/hash/bucketer.go @@ -0,0 +1,121 @@ +/* +Copyright 2020 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// This file contains the utilities to make bucketing decisions. + +package hash + +import ( + "sync" + + lru "github.com/hashicorp/golang-lru" + "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/sets" + "knative.dev/pkg/reconciler" +) + +var _ reconciler.Bucket = (*Bucket)(nil) + +// BucketSet answers to what bucket does key X belong in a +// consistent manner (consistent as in consistent hashing). +type BucketSet struct { + // Stores the cached lookups. cache is internally thread safe. + cache *lru.Cache + + // mu guards buckets. + mu sync.RWMutex + // All the bucket names. Needed for building hash universe. + buckets sets.String +} + +// Bucket implements reconciler.Bucket and wraps around BuketSet +// for bucketing functions. +type Bucket struct { + name string + // `name` must be in this BucketSet.buckets. + buckets *BucketSet +} + +// Scientifically inferred preferred cache size. +const cacheSize = 4096 + +func newCache() *lru.Cache { + c, _ := lru.New(cacheSize) + return c +} + +// NewBucketSet creates a new bucket set with the given universe +// of bucket names. +func NewBucketSet(bucketList sets.String) *BucketSet { + return &BucketSet{ + cache: newCache(), + buckets: bucketList, + } +} + +// NewBucket creates a new bucket. Caller MUST make sure that +// the given `name` is in the given `bl.buckets`. +func NewBucket(name string, bl *BucketSet) *Bucket { + return &Bucket{ + name: name, + buckets: bl, + } +} + +// Name implements Bucket. +func (b *Bucket) Name() string { + return b.name +} + +// Has returns true if this bucket owns the key and +// implements reconciler.Bucket interface. +func (b *Bucket) Has(nn types.NamespacedName) bool { + return b.buckets.Owner(nn.String()) == b.name +} + +// Owner returns the owner of the key. +// Owner will cache the results for faster lookup. +func (b *BucketSet) Owner(key string) string { + if v, ok := b.cache.Get(key); ok { + return v.(string) + } + b.mu.RLock() + defer b.mu.RUnlock() + l := ChooseSubset(b.buckets, 1 /*single query wanted*/, key) + ret := l.UnsortedList()[0] + b.cache.Add(key, ret) + return ret +} + +// BucketList returns the bucket names of this BucketSet in random order. +func (b *BucketSet) BucketList() []string { + b.mu.RLock() + defer b.mu.RUnlock() + + return b.buckets.UnsortedList() +} + +// Update updates the universe of buckets. +func (b *BucketSet) Update(newB sets.String) { + b.mu.Lock() + defer b.mu.Unlock() + // In theory we can iterate over the map and + // purge only the keys that moved to a new shard. + // But this might be more expensive than re-build + // the cache as reconciliations happen. + b.cache.Purge() + b.buckets = newB +} diff --git a/vendor/knative.dev/pkg/hash/doc.go b/vendor/knative.dev/pkg/hash/doc.go new file mode 100644 index 00000000..5cb01faa --- /dev/null +++ b/vendor/knative.dev/pkg/hash/doc.go @@ -0,0 +1,27 @@ +/* +Copyright 2020 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Package hash contains various Knative specific hashing utilities. +// +// - ChooseSubset is a consistent hashing/mapping function providing +// a consistent selection of N keys from M (N<=M) keys for a given +// target. +// - BucketSet is a bucketer library which uses ChooseSubset under the +// the hood in order to implement consistent mapping between keys and +// set of buckets, indentified by unique names. Compared to basic bucket +// implementtion which just does hash%num_buckets, when the number of +// buckets change only a small subset of keys are supposed to migrate. +package hash diff --git a/vendor/knative.dev/pkg/hash/hash.go b/vendor/knative.dev/pkg/hash/hash.go new file mode 100644 index 00000000..b6b64212 --- /dev/null +++ b/vendor/knative.dev/pkg/hash/hash.go @@ -0,0 +1,166 @@ +/* +Copyright 2020 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package hash + +// This file contains the implementation of the subsetting algorithm for +// choosing a subset of input values in a consistent manner. + +import ( + "bytes" + "hash" + "hash/fnv" + "sort" + "strconv" + + "k8s.io/apimachinery/pkg/util/sets" +) + +const ( + startSalt = "start-angle-salt" + stepSalt = "step-angle-salt" + + // universe represents the possible range of angles [0, universe). + // We want to have universe divide total range evenly to reduce bias. + universe = (1 << 11) +) + +// computeAngle returns a uint64 number which represents +// a hash built off the given `n` string for consistent selection +// algorithm. +// We return uint64 here and cast after computing modulo, since +// int might 32 bits on 32 platforms and that would trim result. +func computeHash(n []byte, h hash.Hash64) uint64 { + h.Reset() + h.Write(n) + return h.Sum64() +} + +type hashData struct { + // The set of all hashes for fast lookup and to name mapping + nameLookup map[int]string + // Sorted set of hashes for selection algorithm. + hashPool []int + // start angle + start int + // step angle + step int +} + +func (hd *hashData) fromIndexSet(s sets.Int) sets.String { + ret := make(sets.String, len(s)) + for v := range s { + ret.Insert(hd.nameForHIndex(v)) + } + return ret +} + +func (hd *hashData) nameForHIndex(hi int) string { + return hd.nameLookup[hd.hashPool[hi]] +} + +func buildHashes(in sets.String, target string) *hashData { + // Any one changing this function must execute + // `go test -run=TestOverlay -count=200`. + // This is to ensure there is no regression in the selection + // algorithm. + + // Sorted list to ensure consistent results every time. + from := in.List() + // Write in two pieces, so we don't allocate temp string which is sum of both. + buf := bytes.NewBufferString(target) + buf.WriteString(startSalt) + hasher := fnv.New64a() + hd := &hashData{ + nameLookup: make(map[int]string, len(from)), + hashPool: make([]int, len(from)), + start: int(computeHash(buf.Bytes(), hasher) % universe), + } + buf.Truncate(len(target)) // Discard the angle salt. + buf.WriteString(stepSalt) + hd.step = int(computeHash(buf.Bytes(), hasher) % universe) + + for i, f := range from { + buf.Reset() // This retains the storage. + // Make unique sets for every target. + buf.WriteString(f) + buf.WriteString(target) + h := computeHash(buf.Bytes(), hasher) + hs := int(h % universe) + // Two values slotted to the same bucket. + // On average should happen with 1/universe probability. + _, ok := hd.nameLookup[hs] + for ok { + // Feed the hash as salt. + buf.WriteString(strconv.FormatUint(h, 16 /*append hex strings for shortness*/)) + h = computeHash(buf.Bytes(), hasher) + hs = int(h % universe) + _, ok = hd.nameLookup[hs] + } + + hd.hashPool[i] = hs + hd.nameLookup[hs] = f + } + // Sort for consistent mapping later. + sort.Slice(hd.hashPool, func(i, j int) bool { + return hd.hashPool[i] < hd.hashPool[j] + }) + return hd +} + +// ChooseSubset consistently chooses n items from `from`, using +// `target` as a seed value. +// ChooseSubset is an internal function and presumes sanitized inputs. +// TODO(vagababov): once initial impl is ready, think about how to cache +// the prepared data. +func ChooseSubset(from sets.String, n int, target string) sets.String { + if n >= len(from) { + return from + } + + hashData := buildHashes(from, target) + + // The algorithm for selection does the following: + // 0. Select angle to be the start angle + // 1. While n candidates are not selected + // 2. Find the index for that angle. + // 2.1. While that index is already selected pick next index + // 3. Advance angle by `step` + // 4. Goto 1. + selection := sets.NewInt() + angle := hashData.start + hpl := len(hashData.hashPool) + for len(selection) < n { + root := sort.Search(hpl, func(i int) bool { + return hashData.hashPool[i] >= angle + }) + // Wrap around. + if root == hpl { + root = 0 + } + // Already matched this one. Continue to the next index. + for selection.Has(root) { + root += 1 + if root == hpl { + root = 0 + } + } + selection.Insert(root) + angle = (angle + hashData.step) % universe + } + + return hashData.fromIndexSet(selection) +} diff --git a/vendor/knative.dev/pkg/leaderelection/config.go b/vendor/knative.dev/pkg/leaderelection/config.go index b694c4fd..32695679 100644 --- a/vendor/knative.dev/pkg/leaderelection/config.go +++ b/vendor/knative.dev/pkg/leaderelection/config.go @@ -32,21 +32,21 @@ import ( cm "knative.dev/pkg/configmap" ) -const configMapNameEnv = "CONFIG_LEADERELECTION_NAME" +const ( + configMapNameEnv = "CONFIG_LEADERELECTION_NAME" + // KnativeResourceLock is the only supported lock mechanism for Knative. + KnativeResourceLock = resourcelock.LeasesResourceLock +) // MaxBuckets is the maximum number of buckets to allow users to define. // This is a variable so that it may be customized in the binary entrypoint. var MaxBuckets uint32 = 10 -var validResourceLocks = sets.NewString(resourcelock.LeasesResourceLock) - // NewConfigFromMap returns a Config for the given map, or an error. func NewConfigFromMap(data map[string]string) (*Config, error) { config := defaultConfig() if err := cm.Parse(data, - cm.AsString("resourceLock", &config.ResourceLock), - cm.AsDuration("leaseDuration", &config.LeaseDuration), cm.AsDuration("renewDeadline", &config.RenewDeadline), cm.AsDuration("retryPeriod", &config.RetryPeriod), @@ -59,10 +59,6 @@ func NewConfigFromMap(data map[string]string) (*Config, error) { if config.Buckets < 1 || config.Buckets > MaxBuckets { return nil, fmt.Errorf("buckets: value must be between %d <= %d <= %d", 1, config.Buckets, MaxBuckets) } - if !validResourceLocks.Has(config.ResourceLock) { - return nil, fmt.Errorf(`resourceLock: invalid value %q: valid values are "leases"`, config.ResourceLock) - } - return config, nil } @@ -79,7 +75,6 @@ func NewConfigFromConfigMap(configMap *corev1.ConfigMap) (*Config, error) { // contained within a single namespace. Typically these will correspond to a // single source repository, viz: serving or eventing. type Config struct { - ResourceLock string Buckets uint32 LeaseDuration time.Duration RenewDeadline time.Duration @@ -95,7 +90,6 @@ func (c *Config) GetComponentConfig(name string) ComponentConfig { return ComponentConfig{ Component: name, Buckets: c.Buckets, - ResourceLock: c.ResourceLock, LeaseDuration: c.LeaseDuration, RenewDeadline: c.RenewDeadline, RetryPeriod: c.RetryPeriod, @@ -104,7 +98,6 @@ func (c *Config) GetComponentConfig(name string) ComponentConfig { func defaultConfig() *Config { return &Config{ - ResourceLock: "leases", Buckets: 1, LeaseDuration: 15 * time.Second, RenewDeadline: 10 * time.Second, @@ -116,7 +109,6 @@ func defaultConfig() *Config { type ComponentConfig struct { Component string Buckets uint32 - ResourceLock string LeaseDuration time.Duration RenewDeadline time.Duration RetryPeriod time.Duration diff --git a/vendor/knative.dev/pkg/leaderelection/context.go b/vendor/knative.dev/pkg/leaderelection/context.go index 57b0586f..eaae67a7 100644 --- a/vendor/knative.dev/pkg/leaderelection/context.go +++ b/vendor/knative.dev/pkg/leaderelection/context.go @@ -24,9 +24,11 @@ import ( "sync" "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/sets" "k8s.io/client-go/kubernetes" "k8s.io/client-go/tools/leaderelection" "k8s.io/client-go/tools/leaderelection/resourcelock" + "knative.dev/pkg/hash" "knative.dev/pkg/logging" "knative.dev/pkg/network" "knative.dev/pkg/reconciler" @@ -37,10 +39,10 @@ import ( // falling back on the standard elector. func WithDynamicLeaderElectorBuilder(ctx context.Context, kc kubernetes.Interface, cc ComponentConfig) context.Context { logger := logging.FromContext(ctx) - ssc, err := newStatefulSetConfig() + b, _, err := NewStatefulSetBucketAndSet(int(cc.Buckets)) if err == nil { logger.Info("Running with StatefulSet leader election") - return withStatefulSetElectorBuilder(ctx, cc, *ssc) + return withStatefulSetElectorBuilder(ctx, cc, b) } logger.Info("Running with Standard leader election") return WithStandardLeaderElectorBuilder(ctx, kc, cc) @@ -59,10 +61,10 @@ func WithStandardLeaderElectorBuilder(ctx context.Context, kc kubernetes.Interfa // withStatefulSetElectorBuilder infuses a context with the ability to build // Electors which are assigned leadership based on the StatefulSet ordinal from // the provided component configuration. -func withStatefulSetElectorBuilder(ctx context.Context, cc ComponentConfig, ssc statefulSetConfig) context.Context { +func withStatefulSetElectorBuilder(ctx context.Context, cc ComponentConfig, bkt reconciler.Bucket) context.Context { return context.WithValue(ctx, builderKey{}, &statefulSetBuilder{ lec: cc, - ssc: ssc, + bkt: bkt, }) } @@ -123,7 +125,7 @@ func (b *standardBuilder) buildElector(ctx context.Context, la reconciler.Leader total: b.lec.Buckets, } - rl, err := resourcelock.New(b.lec.ResourceLock, + rl, err := resourcelock.New(KnativeResourceLock, system.Namespace(), // use namespace we are running in bkt.Name(), b.kc.CoreV1(), @@ -173,29 +175,50 @@ func (b *standardBuilder) buildElector(ctx context.Context, la reconciler.Leader type statefulSetBuilder struct { lec ComponentConfig - ssc statefulSetConfig + bkt reconciler.Bucket } func (b *statefulSetBuilder) buildElector(ctx context.Context, la reconciler.LeaderAware, enq func(reconciler.Bucket, types.NamespacedName)) (Elector, error) { logger := logging.FromContext(ctx) - ordinal := uint32(b.ssc.StatefulSetID.ordinal) - logger.Infof("%s will run in StatefulSet ordinal assignement mode with ordinal %d", - b.lec.Component, ordinal) + logger.Infof("%s will run in StatefulSet ordinal assignement mode with bucket name %s", + b.lec.Component, b.bkt.Name()) return &unopposedElector{ - bkt: &bucket{ - // The name is the full pod DNS of the owner pod of this bucket. - name: fmt.Sprintf("%s://%s-%d.%s.%s.svc.%s:%s", b.ssc.Protocol, - b.ssc.StatefulSetID.ssName, ordinal, b.ssc.ServiceName, - system.Namespace(), network.GetClusterDomainName(), b.ssc.Port), - index: ordinal, - total: b.lec.Buckets, - }, + bkt: b.bkt, la: la, enq: enq, }, nil } +// NewStatefulSetBucketAndSet creates a BucketSet for StatefulSet controller with +// the given bucket size and the information from environment variables. Then uses +// the created BucketSet to create a Bucket for this StatefulSet Pod. +func NewStatefulSetBucketAndSet(buckets int) (reconciler.Bucket, *hash.BucketSet, error) { + ssc, err := newStatefulSetConfig() + if err != nil { + return nil, nil, err + } + + if ssc.StatefulSetID.ordinal >= buckets { + return nil, nil, fmt.Errorf("ordinal %d is out of range [0, %d)", + ssc.StatefulSetID.ordinal, buckets) + } + + names := make(sets.String, buckets) + for i := 0; i < buckets; i++ { + names.Insert(statefulSetPodDNS(i, ssc)) + } + + bs := hash.NewBucketSet(names) + return hash.NewBucket(statefulSetPodDNS(ssc.StatefulSetID.ordinal, ssc), bs), bs, nil +} + +func statefulSetPodDNS(ordinal int, ssc *statefulSetConfig) string { + return fmt.Sprintf("%s://%s-%d.%s.%s.svc.%s:%s", ssc.Protocol, + ssc.StatefulSetID.ssName, ordinal, ssc.ServiceName, + system.Namespace(), network.GetClusterDomainName(), ssc.Port) +} + // unopposedElector promotes when run without needing to be elected. type unopposedElector struct { bkt reconciler.Bucket diff --git a/vendor/knative.dev/pkg/leaderelection/ordinal.go b/vendor/knative.dev/pkg/leaderelection/ordinal.go deleted file mode 100644 index 6ed95738..00000000 --- a/vendor/knative.dev/pkg/leaderelection/ordinal.go +++ /dev/null @@ -1,27 +0,0 @@ -/* -Copyright 2020 The Knative Authors - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package leaderelection - -// ControllerOrdinal tries to get ordinal from the pod name of a StatefulSet, -// which is provided from the environment variable CONTROLLER_ORDINAL. -func ControllerOrdinal() (int, error) { - ssc, err := newStatefulSetConfig() - if err != nil { - return 0, err - } - return ssc.StatefulSetID.ordinal, nil -} diff --git a/vendor/knative.dev/pkg/metrics/config.go b/vendor/knative.dev/pkg/metrics/config.go index d44a22f6..a1573cda 100644 --- a/vendor/knative.dev/pkg/metrics/config.go +++ b/vendor/knative.dev/pkg/metrics/config.go @@ -161,7 +161,14 @@ func (mc *metricsConfig) record(ctx context.Context, mss []stats.Measurement, ro // At this point, it's unclear whether should record or not. return nil } + if mc.recorder == nil { + opt, err := optionForResource(metricskey.GetResource(ctx)) + if err != nil { + return err + } + ros = append(ros, opt) + return stats.RecordWithOptions(ctx, append(ros, stats.WithMeasurements(mss...))...) } return mc.recorder(ctx, mss, ros...) @@ -260,29 +267,7 @@ func createMetricsConfig(ops ExporterOptions, logger *zap.SugaredLogger) (*metri } } - if !allowCustomMetrics { - servingOrEventing := metricskey.KnativeRevisionMetrics.Union( - metricskey.KnativeTriggerMetrics).Union(metricskey.KnativeBrokerMetrics) - mc.recorder = func(ctx context.Context, mss []stats.Measurement, ros ...stats.Options) error { - // Perform array filtering in place using two indices: w(rite)Index and r(ead)Index. - wIdx := 0 - for rIdx := 0; rIdx < len(mss); rIdx++ { - metricType := path.Join(mc.stackdriverMetricTypePrefix, mss[rIdx].Measure().Name()) - if servingOrEventing.Has(metricType) { - mss[wIdx] = mss[rIdx] - wIdx++ - } - // Otherwise, skip the measurement (because it won't be accepted). - } - // Found no matched metrics. - if wIdx == 0 { - return nil - } - // Trim the list to the number of written objects. - mss = mss[:wIdx] - return stats.RecordWithOptions(ctx, append(ros, stats.WithMeasurements(mss...))...) - } - } + mc.recorder = sdCustomMetricsRecorder(mc, allowCustomMetrics) if scc.UseSecret { secret, err := getStackdriverSecret(ops.Secrets) diff --git a/vendor/knative.dev/pkg/metrics/exporter.go b/vendor/knative.dev/pkg/metrics/exporter.go index 4c247938..59ed8364 100644 --- a/vendor/knative.dev/pkg/metrics/exporter.go +++ b/vendor/knative.dev/pkg/metrics/exporter.go @@ -85,26 +85,6 @@ func UpdateExporterFromConfigMap(component string, logger *zap.SugaredLogger) fu return ConfigMapWatcher(component, nil, logger) } -// RegisterResourceView is similar to view.Register(), except that it will -// register the view across all Resources tracked by the system, rather than -// simply the default view. -// this is a placeholder for real implementation in https://github.com/knative/pkg/pull/1392. -// That PR will introduce a breaking change, as we need to convert some view.Register to RegisterResourceView in -// all callers, in serving, eventing, and eventing-contrib. -// Since that PR is huge, a better approach is to introduce the breaking change up front, by creating this -// passthrough method, and fixing all the callers before merging that PR. -func RegisterResourceView(views ...*view.View) error { - return view.Register(views...) -} - -// UnregisterResourceView is similar to view.unRegister(), except that it will -// unregister the view across all Resources tracked by the system, rather than -// simply the default view. -// this is a placeholder for real implementation in https://github.com/knative/pkg/pull/1392. -func UnregisterResourceView(views ...*view.View) { - view.Unregister(views...) -} - // ConfigMapWatcher returns a helper func which updates the exporter configuration based on // values in the supplied ConfigMap. This method captures a corev1.SecretLister which is used // to configure mTLS with the opencensus agent. @@ -171,13 +151,17 @@ func UpdateExporter(ops ExporterOptions, logger *zap.SugaredLogger) error { if isNewExporterRequired(newConfig) { logger.Info("Flushing the existing exporter before setting up the new exporter.") flushGivenExporter(curMetricsExporter) - e, err := newMetricsExporter(newConfig, logger) + e, f, err := newMetricsExporter(newConfig, logger) if err != nil { - logger.Errorf("Failed to update a new metrics exporter based on metric config %v. error: %v", newConfig, err) + logger.Errorw("Failed to update a new metrics exporter based on metric config", newConfig, zap.Error(err)) return err } existingConfig := curMetricsConfig curMetricsExporter = e + if err := setFactory(f); err != nil { + logger.Errorw("Failed to update metrics factory when loading metric config", newConfig, zap.Error(err)) + return err + } logger.Infof("Successfully updated the metrics exporter; old config: %v; new config %v", existingConfig, newConfig) } @@ -205,7 +189,7 @@ func isNewExporterRequired(newConfig *metricsConfig) bool { // newMetricsExporter gets a metrics exporter based on the config. // This function must be called with the metricsMux reader (or writer) locked. -func newMetricsExporter(config *metricsConfig, logger *zap.SugaredLogger) (view.Exporter, error) { +func newMetricsExporter(config *metricsConfig, logger *zap.SugaredLogger) (view.Exporter, ResourceExporterFactory, error) { // If there is a Prometheus Exporter server running, stop it. resetCurPromSrv() @@ -215,24 +199,20 @@ func newMetricsExporter(config *metricsConfig, logger *zap.SugaredLogger) (view. se.StopMetricsExporter() } - var err error - var e view.Exporter - switch config.backendDestination { - case openCensus: - e, err = newOpenCensusExporter(config, logger) - case stackdriver: - e, err = newStackdriverExporter(config, logger) - case prometheus: - e, err = newPrometheusExporter(config, logger) - case none: - e, err = nil, nil - default: - err = fmt.Errorf("unsupported metrics backend %v", config.backendDestination) + factory := map[metricsBackend]func(*metricsConfig, *zap.SugaredLogger) (view.Exporter, ResourceExporterFactory, error){ + stackdriver: newStackdriverExporter, + openCensus: newOpenCensusExporter, + prometheus: newPrometheusExporter, + none: func(*metricsConfig, *zap.SugaredLogger) (view.Exporter, ResourceExporterFactory, error) { + return nil, nil, nil + }, } - if err != nil { - return nil, err + + ff := factory[config.backendDestination] + if ff == nil { + return nil, nil, fmt.Errorf("unsuppored metrics backend %v", config.backendDestination) } - return e, nil + return ff(config, logger) } func getCurMetricsExporter() view.Exporter { @@ -274,6 +254,7 @@ func setCurMetricsConfigUnlocked(c *metricsConfig) { // Return value indicates whether the exporter is flushable or not. func FlushExporter() bool { e := getCurMetricsExporter() + flushResourceExporters() return flushGivenExporter(e) } diff --git a/vendor/knative.dev/pkg/metrics/metricskey/constants.go b/vendor/knative.dev/pkg/metrics/metricskey/constants.go index 56906768..2eec8985 100644 --- a/vendor/knative.dev/pkg/metrics/metricskey/constants.go +++ b/vendor/knative.dev/pkg/metrics/metricskey/constants.go @@ -16,6 +16,12 @@ limitations under the License. package metricskey +import ( + "context" + + "go.opencensus.io/resource" +) + const ( // LabelProject is the label for project (e.g. GCP GAIA ID, AWS project name) LabelProject = "project_id" @@ -51,3 +57,21 @@ const ( // is not running on GKE. ValueUnknown = "unknown" ) + +type resourceKey struct{} + +// WithResource associates the given monitoring Resource with the current +// context. Note that Resources do not "stack" or merge -- the closest enclosing +// Resource is the one that all measurements are associated with. +func WithResource(ctx context.Context, r resource.Resource) context.Context { + return context.WithValue(ctx, resourceKey{}, &r) +} + +// GetResource extracts a resource from the current context, if present. +func GetResource(ctx context.Context) *resource.Resource { + r := ctx.Value(resourceKey{}) + if r == nil { + return nil + } + return r.(*resource.Resource) +} diff --git a/vendor/knative.dev/pkg/metrics/monitored_resources.go b/vendor/knative.dev/pkg/metrics/monitored_resources.go deleted file mode 100644 index 41819e0a..00000000 --- a/vendor/knative.dev/pkg/metrics/monitored_resources.go +++ /dev/null @@ -1,34 +0,0 @@ -/* -Copyright 2018 The Knative Authors - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package metrics - -import ( - "knative.dev/pkg/metrics/metricskey" -) - -type Global struct{} - -func (g *Global) MonitoredResource() (resType string, labels map[string]string) { - return "global", nil -} - -func valueOrUnknown(key string, tagsMap map[string]string) string { - if value, ok := tagsMap[key]; ok { - return value - } - return metricskey.ValueUnknown -} diff --git a/vendor/knative.dev/pkg/metrics/monitored_resources_eventing.go b/vendor/knative.dev/pkg/metrics/monitored_resources_eventing.go deleted file mode 100644 index 32742e77..00000000 --- a/vendor/knative.dev/pkg/metrics/monitored_resources_eventing.go +++ /dev/null @@ -1,159 +0,0 @@ -/* -Copyright 2019 The Knative Authors - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package metrics - -// TODO should be moved to eventing. See https://github.com/knative/pkg/issues/608 - -import ( - "contrib.go.opencensus.io/exporter/stackdriver/monitoredresource" - "go.opencensus.io/metric/metricdata" - "knative.dev/pkg/metrics/metricskey" -) - -type KnativeTrigger struct { - Project string - Location string - ClusterName string - NamespaceName string - TriggerName string - BrokerName string - TypeFilterAttribute string - SourceFilterAttribute string -} - -type KnativeBroker struct { - Project string - Location string - ClusterName string - NamespaceName string - BrokerName string -} - -type KnativeSource struct { - Project string - Location string - ClusterName string - NamespaceName string - SourceName string - SourceResourceGroup string -} - -func (kt *KnativeTrigger) MonitoredResource() (resType string, labels map[string]string) { - labels = map[string]string{ - metricskey.LabelProject: kt.Project, - metricskey.LabelLocation: kt.Location, - metricskey.LabelClusterName: kt.ClusterName, - metricskey.LabelNamespaceName: kt.NamespaceName, - metricskey.LabelBrokerName: kt.BrokerName, - metricskey.LabelTriggerName: kt.TriggerName, - } - return metricskey.ResourceTypeKnativeTrigger, labels -} - -func (kb *KnativeBroker) MonitoredResource() (resType string, labels map[string]string) { - labels = map[string]string{ - metricskey.LabelProject: kb.Project, - metricskey.LabelLocation: kb.Location, - metricskey.LabelClusterName: kb.ClusterName, - metricskey.LabelNamespaceName: kb.NamespaceName, - metricskey.LabelBrokerName: kb.BrokerName, - } - return metricskey.ResourceTypeKnativeBroker, labels -} - -func (ki *KnativeSource) MonitoredResource() (resType string, labels map[string]string) { - labels = map[string]string{ - metricskey.LabelProject: ki.Project, - metricskey.LabelLocation: ki.Location, - metricskey.LabelClusterName: ki.ClusterName, - metricskey.LabelNamespaceName: ki.NamespaceName, - metricskey.LabelName: ki.SourceName, - metricskey.LabelResourceGroup: ki.SourceResourceGroup, - } - return metricskey.ResourceTypeKnativeSource, labels -} - -func GetKnativeBrokerMonitoredResource( - des *metricdata.Descriptor, tags map[string]string, gm *gcpMetadata) (map[string]string, monitoredresource.Interface) { - kb := &KnativeBroker{ - // The first three resource labels are from metadata. - Project: gm.project, - Location: gm.location, - ClusterName: gm.cluster, - // The rest resource labels are from metrics labels. - NamespaceName: valueOrUnknown(metricskey.LabelNamespaceName, tags), - BrokerName: valueOrUnknown(metricskey.LabelBrokerName, tags), - } - - metricLabels := map[string]string{} - for k, v := range tags { - // Keep the metrics labels that are not resource labels - if !metricskey.KnativeBrokerLabels.Has(k) { - metricLabels[k] = v - } - } - - return metricLabels, kb -} - -func GetKnativeTriggerMonitoredResource( - des *metricdata.Descriptor, tags map[string]string, gm *gcpMetadata) (map[string]string, monitoredresource.Interface) { - kt := &KnativeTrigger{ - // The first three resource labels are from metadata. - Project: gm.project, - Location: gm.location, - ClusterName: gm.cluster, - // The rest resource labels are from metrics labels. - NamespaceName: valueOrUnknown(metricskey.LabelNamespaceName, tags), - BrokerName: valueOrUnknown(metricskey.LabelBrokerName, tags), - TriggerName: valueOrUnknown(metricskey.LabelTriggerName, tags), - } - - metricLabels := map[string]string{} - for k, v := range tags { - // Keep the metrics labels that are not resource labels - if !metricskey.KnativeTriggerLabels.Has(k) { - metricLabels[k] = v - } - } - - return metricLabels, kt -} - -func GetKnativeSourceMonitoredResource( - des *metricdata.Descriptor, tags map[string]string, gm *gcpMetadata) (map[string]string, monitoredresource.Interface) { - ks := &KnativeSource{ - // The first three resource labels are from metadata. - Project: gm.project, - Location: gm.location, - ClusterName: gm.cluster, - // The rest resource labels are from metrics labels. - NamespaceName: valueOrUnknown(metricskey.LabelNamespaceName, tags), - SourceName: valueOrUnknown(metricskey.LabelName, tags), - SourceResourceGroup: valueOrUnknown(metricskey.LabelResourceGroup, tags), - } - - metricLabels := map[string]string{} - for k, v := range tags { - // Keep the metrics labels that are not resource labels - if !metricskey.KnativeSourceLabels.Has(k) { - metricLabels[k] = v - } - } - - return metricLabels, ks -} diff --git a/vendor/knative.dev/pkg/metrics/monitored_resources_serving.go b/vendor/knative.dev/pkg/metrics/monitored_resources_serving.go deleted file mode 100644 index eae42408..00000000 --- a/vendor/knative.dev/pkg/metrics/monitored_resources_serving.go +++ /dev/null @@ -1,73 +0,0 @@ -/* -Copyright 2019 The Knative Authors - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package metrics - -import ( - "contrib.go.opencensus.io/exporter/stackdriver/monitoredresource" - "go.opencensus.io/metric/metricdata" - "knative.dev/pkg/metrics/metricskey" -) - -// TODO should be moved to serving. See https://github.com/knative/pkg/issues/608 - -type KnativeRevision struct { - Project string - Location string - ClusterName string - NamespaceName string - ServiceName string - ConfigurationName string - RevisionName string -} - -func (kr *KnativeRevision) MonitoredResource() (resType string, labels map[string]string) { - labels = map[string]string{ - metricskey.LabelProject: kr.Project, - metricskey.LabelLocation: kr.Location, - metricskey.LabelClusterName: kr.ClusterName, - metricskey.LabelNamespaceName: kr.NamespaceName, - metricskey.LabelServiceName: kr.ServiceName, - metricskey.LabelConfigurationName: kr.ConfigurationName, - metricskey.LabelRevisionName: kr.RevisionName, - } - return metricskey.ResourceTypeKnativeRevision, labels -} - -func GetKnativeRevisionMonitoredResource( - des *metricdata.Descriptor, tags map[string]string, gm *gcpMetadata) (map[string]string, monitoredresource.Interface) { - kr := &KnativeRevision{ - // The first three resource labels are from metadata. - Project: gm.project, - Location: gm.location, - ClusterName: gm.cluster, - // The rest resource labels are from metrics labels. - NamespaceName: valueOrUnknown(metricskey.LabelNamespaceName, tags), - ServiceName: valueOrUnknown(metricskey.LabelServiceName, tags), - ConfigurationName: valueOrUnknown(metricskey.LabelConfigurationName, tags), - RevisionName: valueOrUnknown(metricskey.LabelRevisionName, tags), - } - - metricLabels := map[string]string{} - for k, v := range tags { - // Keep the metrics labels that are not resource labels - if !metricskey.KnativeRevisionLabels.Has(k) { - metricLabels[k] = v - } - } - - return metricLabels, kr -} diff --git a/vendor/knative.dev/pkg/metrics/opencensus_exporter.go b/vendor/knative.dev/pkg/metrics/opencensus_exporter.go index dd80c855..22b1c5be 100644 --- a/vendor/knative.dev/pkg/metrics/opencensus_exporter.go +++ b/vendor/knative.dev/pkg/metrics/opencensus_exporter.go @@ -17,10 +17,12 @@ limitations under the License. package metrics import ( + "context" "crypto/tls" "fmt" "contrib.go.opencensus.io/exporter/ocagent" + "go.opencensus.io/resource" "go.opencensus.io/stats/view" "go.uber.org/zap" "google.golang.org/grpc/credentials" @@ -28,7 +30,7 @@ import ( "k8s.io/apimachinery/pkg/api/errors" ) -func newOpenCensusExporter(config *metricsConfig, logger *zap.SugaredLogger) (view.Exporter, error) { +func newOpenCensusExporter(config *metricsConfig, logger *zap.SugaredLogger) (view.Exporter, ResourceExporterFactory, error) { opts := []ocagent.ExporterOption{ocagent.WithServiceName(config.component)} if config.collectorAddress != "" { opts = append(opts, ocagent.WithAddress(config.collectorAddress)) @@ -40,12 +42,22 @@ func newOpenCensusExporter(config *metricsConfig, logger *zap.SugaredLogger) (vi } e, err := ocagent.NewExporter(opts...) if err != nil { - logger.Errorw("failed to create the OpenCensus exporter.", zap.Error(err)) - return nil, err + logger.Errorw("Failed to create the OpenCensus exporter.", zap.Error(err)) + return nil, nil, err } - logger.Infof("created OpenCensus exporter with config: %+v.", *config) + logger.Infow("Created OpenCensus exporter with config:", zap.Any("config", *config)) view.RegisterExporter(e) - return e, nil + return e, getFactory(opts), nil +} + +func getFactory(stored []ocagent.ExporterOption) ResourceExporterFactory { + return func(r *resource.Resource) (view.Exporter, error) { + opts := append(stored, ocagent.WithResourceDetector( + func(context.Context) (*resource.Resource, error) { + return r, nil + })) + return ocagent.NewExporter(opts...) + } } // getOpenCensusSecret attempts to locate a secret containing TLS credentials @@ -71,7 +83,7 @@ func getOpenCensusSecret(component string, lister SecretFetcher) (*corev1.Secret // for communicating with the OpenCensus Agent. func getCredentials(component string, secret *corev1.Secret, logger *zap.SugaredLogger) credentials.TransportCredentials { if secret == nil { - logger.Errorf("no secret provided for component %q; cannot use requireSecure=true", component) + logger.Errorf("No secret provided for component %q; cannot use requireSecure=true", component) return nil } return credentials.NewTLS(&tls.Config{ diff --git a/vendor/knative.dev/pkg/metrics/prometheus_exporter.go b/vendor/knative.dev/pkg/metrics/prometheus_exporter.go index c8a7b73d..d60fe8f0 100644 --- a/vendor/knative.dev/pkg/metrics/prometheus_exporter.go +++ b/vendor/knative.dev/pkg/metrics/prometheus_exporter.go @@ -22,6 +22,7 @@ import ( "sync" prom "contrib.go.opencensus.io/exporter/prometheus" + "go.opencensus.io/resource" "go.opencensus.io/stats/view" "go.uber.org/zap" ) @@ -31,11 +32,20 @@ var ( curPromSrvMux sync.Mutex ) -func newPrometheusExporter(config *metricsConfig, logger *zap.SugaredLogger) (view.Exporter, error) { +type emptyPromExporter struct{} + +var _ view.Exporter = (*emptyPromExporter)(nil) + +func (emptyPromExporter) ExportView(viewData *view.Data) { + // Prometheus runs a loop to read stats via ReadAndExport, so this is just + // a signal to enrich the internal Meters with Resource information. +} + +func newPrometheusExporter(config *metricsConfig, logger *zap.SugaredLogger) (view.Exporter, ResourceExporterFactory, error) { e, err := prom.NewExporter(prom.Options{Namespace: config.component}) if err != nil { logger.Errorw("Failed to create the Prometheus exporter.", zap.Error(err)) - return nil, err + return nil, nil, err } logger.Infof("Created Opencensus Prometheus exporter with config: %v. Start the server for Prometheus exporter.", config) // Start the server for Prometheus scraping @@ -43,7 +53,9 @@ func newPrometheusExporter(config *metricsConfig, logger *zap.SugaredLogger) (vi srv := startNewPromSrv(e, config.prometheusPort) srv.ListenAndServe() }() - return e, nil + return e, + func(r *resource.Resource) (view.Exporter, error) { return &emptyPromExporter{}, nil }, + nil } func getCurPromSrv() *http.Server { diff --git a/vendor/knative.dev/pkg/metrics/record.go b/vendor/knative.dev/pkg/metrics/record.go index c4627917..c16b0c9b 100644 --- a/vendor/knative.dev/pkg/metrics/record.go +++ b/vendor/knative.dev/pkg/metrics/record.go @@ -31,6 +31,7 @@ func Record(ctx context.Context, ms stats.Measurement, ros ...stats.Options) { } // RecordBatch stores the given Measurements from `mss` in the current metrics backend. +// All metrics should be reported using the same Resource. func RecordBatch(ctx context.Context, mss ...stats.Measurement) { getCurMetricsConfig().record(ctx, mss) } diff --git a/vendor/knative.dev/pkg/metrics/resource_view.go b/vendor/knative.dev/pkg/metrics/resource_view.go new file mode 100644 index 00000000..ff42f9d6 --- /dev/null +++ b/vendor/knative.dev/pkg/metrics/resource_view.go @@ -0,0 +1,323 @@ +/* +Copyright 2020 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package metrics + +import ( + "errors" + "fmt" + "sort" + "strings" + "sync" + "time" + + "go.opencensus.io/metric/metricdata" + "go.opencensus.io/resource" + "go.opencensus.io/stats" + "go.opencensus.io/stats/view" + "go.opencensus.io/tag" +) + +type storedViews struct { + views []*view.View + lock sync.Mutex +} + +type meterExporter struct { + m view.Meter // NOTE: DO NOT RETURN THIS DIRECTLY; the view.Meter will not work for the empty Resource + o stats.Options // Cache the option to reduce allocations + e view.Exporter +} + +// ResourceExporterFactory provides a hook for producing separate view.Exporters +// for each observed Resource. This is needed because OpenCensus support for +// Resources is a bit tacked-on rather than being a first-class component like +// Tags are. +type ResourceExporterFactory func(*resource.Resource) (view.Exporter, error) +type meters struct { + meters map[string]*meterExporter + factory ResourceExporterFactory + // Cache of Resource pointers from metricskey to Meters, to avoid + // unnecessary stringify operations + resourceToKey map[*resource.Resource]string + lock sync.Mutex +} + +// Lock regime: lock allMeters before resourceViews. The critical path is in +// optionForResource, which must lock allMeters, but only needs to lock +// resourceViews if a new meter needs to be created. +var resourceViews = storedViews{} +var allMeters = meters{ + meters: map[string]*meterExporter{"": &defaultMeter}, + resourceToKey: map[*resource.Resource]string{nil: ""}, +} + +// RegisterResourceView is similar to view.Register(), except that it will +// register the view across all Resources tracked by the system, rather than +// simply the default view. +func RegisterResourceView(views ...*view.View) error { + var err error + allMeters.lock.Lock() + defer allMeters.lock.Unlock() + resourceViews.lock.Lock() + defer resourceViews.lock.Unlock() + for _, meter := range allMeters.meters { + // make a copy of views to avoid data races + viewCopy := make([]*view.View, 0, len(views)) + for _, v := range views { + c := *v + viewCopy = append(viewCopy, &c) + } + if e := meter.m.Register(viewCopy...); e != nil { + err = e + } + } + if err != nil { + return err + } + resourceViews.views = append(resourceViews.views, views...) + return nil +} + +// UnregisterResourceView is similar to view.Unregiste(), except that it will +// unregister the view across all Resources tracked byt he system, rather than +// simply the default view. +func UnregisterResourceView(views ...*view.View) { + allMeters.lock.Lock() + defer allMeters.lock.Unlock() + + resourceViews.lock.Lock() + defer resourceViews.lock.Unlock() + + for _, meter := range allMeters.meters { + // Since we make a defensive copy of all views in RegisterResourceView, + // the caller might not have the same view pointer that was registered. + // Use Meter.Find() to find the view with a matching name. + for _, v := range views { + name := v.Name + if v.Name == "" { + name = v.Measure.Name() + } + if v := meter.m.Find(name); v != nil { + meter.m.Unregister(v) + } + } + } + + j := 0 + for _, view := range resourceViews.views { + toRemove := false + for _, viewToRemove := range views { + if view == viewToRemove { + toRemove = true + break + } + } + if !toRemove { + resourceViews.views[j] = view + j++ + } + } + resourceViews.views = resourceViews.views[:j] +} + +func setFactory(f ResourceExporterFactory) error { + if f == nil { + return errors.New("do not setFactory(nil)") + } + + allMeters.lock.Lock() + defer allMeters.lock.Unlock() + + allMeters.factory = f + + var retErr error + + for r, meter := range allMeters.meters { + e, err := f(resourceFromKey(r)) + if err != nil { + retErr = err + continue // Keep trying to clean up remaining Meters. + } + meter.m.UnregisterExporter(meter.e) + meter.m.RegisterExporter(e) + meter.e = e + } + return retErr +} + +func flushResourceExporters() { + allMeters.lock.Lock() + defer allMeters.lock.Unlock() + + for _, meter := range allMeters.meters { + flushGivenExporter(meter.e) + } +} + +func meterExporterForResource(r *resource.Resource) *meterExporter { + key, ok := allMeters.resourceToKey[r] + if !ok { + key = resourceToKey(r) + allMeters.resourceToKey[r] = key + } + + mE := allMeters.meters[key] + if mE == nil { + mE = &meterExporter{} + allMeters.meters[key] = mE + } + if mE.o != nil { + return mE + } + mE.m = view.NewMeter() + mE.m.SetResource(r) + mE.m.Start() + resourceViews.lock.Lock() + defer resourceViews.lock.Unlock() + // make a copy of views to avoid data races + viewsCopy := make([]*view.View, 0, len(resourceViews.views)) + for _, v := range resourceViews.views { + c := *v + viewsCopy = append(viewsCopy, &c) + } + mE.m.Register(viewsCopy...) + mE.o = stats.WithRecorder(mE.m) + allMeters.meters[key] = mE + return mE +} + +// optionForResource finds or creates a stats exporter for the resource, and +// returns a stats.Option indicating which meter to record to. +func optionForResource(r *resource.Resource) (stats.Options, error) { + allMeters.lock.Lock() + defer allMeters.lock.Unlock() + + mE := meterExporterForResource(r) + if mE == nil { + return nil, fmt.Errorf("failed lookup for resource %v", r) + } + + if mE.e != nil { + // Assume the exporter is already started. + return mE.o, nil + } + + if allMeters.factory == nil { + if mE.o != nil { + // If we can't create exporters but we have a Meter, return that. + return mE.o, nil + } + return nil, fmt.Errorf("whoops, allMeters.factory is nil") + } + exporter, err := allMeters.factory(r) + if err != nil { + return nil, err + } + + mE.m.RegisterExporter(exporter) + mE.e = exporter + return mE.o, nil +} + +func resourceToKey(r *resource.Resource) string { + var s strings.Builder + l := len(r.Type) + kvs := make([]string, 0, len(r.Labels)) + for k, v := range r.Labels { + l += len(k) + len(v) + 2 + // We use byte values 1 and 2 to avoid colliding with valid resource labels + // and to make unpacking easy + kvs = append(kvs, fmt.Sprintf("\x01%s\x02%s", k, v)) + } + s.Grow(l) + s.WriteString(r.Type) + + sort.Strings(kvs) // Go maps are unsorted, so sort by key to produce stable output. + for _, kv := range kvs { + s.WriteString(kv) + } + + return s.String() +} + +func resourceFromKey(s string) *resource.Resource { + if s == "" { + return nil + } + r := &resource.Resource{Labels: map[string]string{}} + parts := strings.Split(s, "\x01") + r.Type = parts[0] + for _, label := range parts[1:] { + keyValue := strings.SplitN(label, "\x02", 2) + r.Labels[keyValue[0]] = keyValue[1] + } + return r +} + +// defaultMeterImpl is a pass-through to the default worker in OpenCensus. +type defaultMeterImpl struct{} + +var _ view.Meter = (*defaultMeterImpl)(nil) + +// defaultMeter is a custom meterExporter which passes through to the default +// worker in OpenCensus. This allows legacy code that uses OpenCensus and does +// not store a Resource in the context to continue to interoperate. +var defaultMeter = meterExporter{ + m: &defaultMeterImpl{}, + o: stats.WithRecorder(nil), +} + +func (*defaultMeterImpl) Record(*tag.Map, interface{}, map[string]interface{}) { + // using an empty option prevents this from being called +} + +// Find calls view.Find +func (*defaultMeterImpl) Find(name string) *view.View { + return view.Find(name) +} + +// Register calls view.Register +func (*defaultMeterImpl) Register(views ...*view.View) error { + return view.Register(views...) +} +func (*defaultMeterImpl) Unregister(views ...*view.View) { + view.Unregister(views...) +} +func (*defaultMeterImpl) SetReportingPeriod(t time.Duration) { + view.SetReportingPeriod(t) +} +func (*defaultMeterImpl) RegisterExporter(e view.Exporter) { + view.RegisterExporter(e) +} +func (*defaultMeterImpl) UnregisterExporter(e view.Exporter) { + view.UnregisterExporter(e) +} +func (*defaultMeterImpl) Start() {} +func (*defaultMeterImpl) Stop() {} +func (*defaultMeterImpl) RetrieveData(viewName string) ([]*view.Row, error) { + return view.RetrieveData(viewName) +} +func (*defaultMeterImpl) SetResource(*resource.Resource) { +} + +// Read is implemented to support casting defaultMeterImpl to a metricproducer.Producer, +// but returns no values because the prometheus exporter (which is the only consumer) +// already has a built in path which collects these metrics via metricexport, which calls +// concat(x.Read() for x in metricproducer.GlobalManager.GetAll()). +func (*defaultMeterImpl) Read() []*metricdata.Metric { + return []*metricdata.Metric{} +} diff --git a/vendor/knative.dev/pkg/metrics/stackdriver_exporter.go b/vendor/knative.dev/pkg/metrics/stackdriver_exporter.go index 5974e6bc..1e1a85ae 100644 --- a/vendor/knative.dev/pkg/metrics/stackdriver_exporter.go +++ b/vendor/knative.dev/pkg/metrics/stackdriver_exporter.go @@ -17,16 +17,19 @@ limitations under the License. package metrics import ( + "context" "fmt" "path" "sync" sd "contrib.go.opencensus.io/exporter/stackdriver" - "contrib.go.opencensus.io/exporter/stackdriver/monitoredresource" - "go.opencensus.io/metric/metricdata" + "go.opencensus.io/resource" + "go.opencensus.io/stats" "go.opencensus.io/stats/view" + "go.opencensus.io/tag" "go.uber.org/zap" "google.golang.org/api/option" + "k8s.io/apimachinery/pkg/util/sets" "knative.dev/pkg/metrics/metricskey" corev1 "k8s.io/api/core/v1" @@ -78,8 +81,17 @@ var ( // useStackdriverSecretEnabled specifies whether or not the exporter can be configured with a Secret. // Consuming packages must do explicitly enable this by calling SetStackdriverSecretLocation. useStackdriverSecretEnabled = false + + // metricToResourceLabels provides a quick lookup from a custom metric name to the set of tags + // which should be promoted to Stackdriver Resource labels via opencensus resources. + metricToResourceLabels = map[string]*resourceTemplate{} ) +type resourceTemplate struct { + Type string + LabelKeys sets.String +} + // SetStackdriverSecretLocation sets the name and namespace of the Secret that can be used to authenticate with Stackdriver. // The Secret is only used if both: // 1. This function has been explicitly called to set the name and namespace @@ -98,6 +110,50 @@ func init() { newStackdriverExporterFunc = newOpencensusSDExporter kubeclientInitErr = nil + + metricsToTemplates := []struct { + metrics sets.String + template resourceTemplate + }{ + {metricskey.KnativeRevisionMetrics, resourceTemplate{metricskey.ResourceTypeKnativeRevision, metricskey.KnativeRevisionLabels}}, + {metricskey.KnativeTriggerMetrics, resourceTemplate{metricskey.ResourceTypeKnativeTrigger, metricskey.KnativeTriggerLabels}}, + {metricskey.KnativeBrokerMetrics, resourceTemplate{metricskey.ResourceTypeKnativeBroker, metricskey.KnativeBrokerLabels}}, + {metricskey.KnativeSourceMetrics, resourceTemplate{metricskey.ResourceTypeKnativeSource, metricskey.KnativeSourceLabels}}, + } + + for _, item := range metricsToTemplates { + t := item.template + for k := range item.metrics { + metricToResourceLabels[k] = &t + } + } +} + +type pollOnlySDExporter struct { + internalExporter view.Exporter +} + +var _ (view.Exporter) = (*pollOnlySDExporter)(nil) +var _ (flushable) = (*pollOnlySDExporter)(nil) + +func (e *pollOnlySDExporter) ExportView(viewData *view.Data) { + // Stackdriver will run an internal loop to bundle stats, so ignore this. +} + +func (e *pollOnlySDExporter) Flush() { + if e.internalExporter != nil { + if f, ok := e.internalExporter.(flushable); ok { + f.Flush() + } + } +} + +func (e *pollOnlySDExporter) StopMetricsExporter() { + if e.internalExporter != nil { + if f, ok := e.internalExporter.(stoppable); ok { + f.StopMetricsExporter() + } + } } func newOpencensusSDExporter(o sd.Options) (view.Exporter, error) { @@ -113,15 +169,14 @@ func newOpencensusSDExporter(o sd.Options) (view.Exporter, error) { return e, nil } -// TODO should be properly refactored to be able to inject the getResourceByDescriptorFunc function. -// See https://github.com/knative/pkg/issues/608 -func newStackdriverExporter(config *metricsConfig, logger *zap.SugaredLogger) (view.Exporter, error) { +func newStackdriverExporter(config *metricsConfig, logger *zap.SugaredLogger) (view.Exporter, ResourceExporterFactory, error) { gm := getMergedGCPMetadata(config) mpf := getMetricPrefixFunc(config.stackdriverMetricTypePrefix, config.stackdriverCustomMetricTypePrefix) co, err := getStackdriverExporterClientOptions(config) if err != nil { logger.Warnw("Issue configuring Stackdriver exporter client options, no additional client options will be used: ", zap.Error(err)) } + // Automatically fall back on Google application default credentials e, err := newStackdriverExporterFunc(sd.Options{ ProjectID: gm.project, @@ -129,16 +184,105 @@ func newStackdriverExporter(config *metricsConfig, logger *zap.SugaredLogger) (v MonitoringClientOptions: co, TraceClientOptions: co, GetMetricPrefix: mpf, - ResourceByDescriptor: getResourceByDescriptorFunc(config.stackdriverMetricTypePrefix, gm), ReportingInterval: config.reportingPeriod, DefaultMonitoringLabels: &sd.Labels{}, }) if err != nil { logger.Errorw("Failed to create the Stackdriver exporter: ", zap.Error(err)) - return nil, err + return nil, nil, err } logger.Infof("Created Opencensus Stackdriver exporter with config %v", config) - return e, nil + // We have to return a ResourceExporterFactory here to enable tracking resources, even though we always poll for them. + return &pollOnlySDExporter{e}, + func(r *resource.Resource) (view.Exporter, error) { return &pollOnlySDExporter{}, nil }, + nil +} + +func sdCustomMetricsRecorder(mc metricsConfig, allowCustomMetrics bool) func(context.Context, []stats.Measurement, ...stats.Options) error { + gm := getMergedGCPMetadata(&mc) + metadataMap := map[string]string{ + metricskey.LabelProject: gm.project, + metricskey.LabelLocation: gm.location, + metricskey.LabelClusterName: gm.cluster, + } + return func(ctx context.Context, mss []stats.Measurement, ros ...stats.Options) error { + i := 0 + var templ *resourceTemplate + templateFound := false + + // This loop serves two purpose, filtering out custom metrics when allowCustomMetrics set to false, + // and find the template for the system metrics. + for _, m := range mss { + metricType := path.Join(mc.stackdriverMetricTypePrefix, m.Measure().Name()) + t, ok := metricToResourceLabels[metricType] + if ok || allowCustomMetrics { + mss[i] = m + i++ + if templateFound && templ != t { + return fmt.Errorf("mixed resource type measurements in one report: %v", mss) + } + templ = t + templateFound = true + } + } + // Trim the array + mss = mss[:i] + if i == 0 { + return nil + } + + baseResource := metricskey.GetResource(ctx) + // Extract resource, if possible + if templ != nil { + tagMap := tag.FromContext(ctx) + r := resource.Resource{ + Type: templ.Type, + Labels: map[string]string{}, + } + tagMutations := make([]tag.Mutator, 0, len(templ.LabelKeys)) + if baseResource == nil { + baseResource = &resource.Resource{} + } + for k := range templ.LabelKeys { + if v, ok := baseResource.Labels[k]; ok { + r.Labels[k] = v + continue + } + tagKey := tag.MustNewKey(k) + if v, ok := tagMap.Value(tagKey); ok { + r.Labels[k] = v + tagMutations = append(tagMutations, tag.Delete(tagKey)) + continue + } + if v, ok := metadataMap[k]; ok { + r.Labels[k] = v + continue + } + r.Labels[k] = metricskey.ValueUnknown + } + var err error + ctx, err = tag.New(metricskey.WithResource(ctx, r), tagMutations...) + if err != nil { + return err + } + baseResource = &r + } + if baseResource != nil { + opt, err := optionForResource(baseResource) + if err != nil { + return err + } + ros = append(ros, opt) + } + + return stats.RecordWithOptions(ctx, append(ros, stats.WithMeasurements(mss...))...) + } +} + +// clientConfig stores the stackdriver configs for cerating additional connections. +type clientConfig struct { + storeConfig *metricsConfig + storeLogger *zap.SugaredLogger } // getStackdriverExporterClientOptions creates client options for the opencensus Stackdriver exporter from the given stackdriverClientConfig. @@ -181,27 +325,6 @@ func getMergedGCPMetadata(config *metricsConfig) *gcpMetadata { return gm } -func getResourceByDescriptorFunc(metricTypePrefix string, gm *gcpMetadata) func(*metricdata.Descriptor, map[string]string) (map[string]string, monitoredresource.Interface) { - return func(des *metricdata.Descriptor, tags map[string]string) (map[string]string, monitoredresource.Interface) { - metricType := path.Join(metricTypePrefix, des.Name) - if metricskey.KnativeRevisionMetrics.Has(metricType) { - return GetKnativeRevisionMonitoredResource(des, tags, gm) - } else if metricskey.KnativeBrokerMetrics.Has(metricType) { - return GetKnativeBrokerMonitoredResource(des, tags, gm) - } else if metricskey.KnativeTriggerMetrics.Has(metricType) { - return GetKnativeTriggerMonitoredResource(des, tags, gm) - } else if metricskey.KnativeSourceMetrics.Has(metricType) { - return GetKnativeSourceMonitoredResource(des, tags, gm) - } - // Unsupported metric by knative_revision, knative_broker, knative_trigger, and knative_source, use "global" resource type. - return getGlobalMonitoredResource(des, tags) - } -} - -func getGlobalMonitoredResource(des *metricdata.Descriptor, tags map[string]string) (map[string]string, monitoredresource.Interface) { - return tags, &Global{} -} - func getMetricPrefixFunc(metricTypePrefix, customMetricTypePrefix string) func(name string) string { return func(name string) string { metricType := path.Join(metricTypePrefix, name) diff --git a/vendor/modules.txt b/vendor/modules.txt index 337c1ce7..5cff9659 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -3,17 +3,24 @@ cloud.google.com/go/compute/metadata cloud.google.com/go/container/apiv1 cloud.google.com/go/monitoring/apiv3 cloud.google.com/go/trace/apiv2 -# contrib.go.opencensus.io/exporter/ocagent v0.6.0 +# contrib.go.opencensus.io/exporter/ocagent v0.6.1-0.20200121232419-b3d61dbd6252 contrib.go.opencensus.io/exporter/ocagent -# contrib.go.opencensus.io/exporter/prometheus v0.1.0 +# contrib.go.opencensus.io/exporter/prometheus v0.2.1-0.20200609204449-6bcf6f8577f0 contrib.go.opencensus.io/exporter/prometheus -# contrib.go.opencensus.io/exporter/stackdriver v0.12.9-0.20191108183826-59d068f8d8ff +# contrib.go.opencensus.io/exporter/stackdriver v0.13.2 contrib.go.opencensus.io/exporter/stackdriver contrib.go.opencensus.io/exporter/stackdriver/monitoredresource +contrib.go.opencensus.io/exporter/stackdriver/monitoredresource/aws +contrib.go.opencensus.io/exporter/stackdriver/monitoredresource/gcp # github.com/PuerkitoBio/purell v1.1.1 github.com/PuerkitoBio/purell # github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 github.com/PuerkitoBio/urlesc +# github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 +github.com/alecthomas/template +github.com/alecthomas/template/parse +# github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d +github.com/alecthomas/units # github.com/aws/aws-sdk-go v1.31.12 github.com/aws/aws-sdk-go/aws github.com/aws/aws-sdk-go/aws/awserr @@ -139,6 +146,8 @@ github.com/jmespath/go-jmespath github.com/json-iterator/go # github.com/kelseyhightower/envconfig v1.4.0 github.com/kelseyhightower/envconfig +# github.com/konsorten/go-windows-terminal-sequences v1.0.3 +github.com/konsorten/go-windows-terminal-sequences # github.com/mailru/easyjson v0.7.0 github.com/mailru/easyjson/buffer github.com/mailru/easyjson/jlexer @@ -162,13 +171,19 @@ github.com/prometheus/client_model/go # github.com/prometheus/common v0.9.1 github.com/prometheus/common/expfmt github.com/prometheus/common/internal/bitbucket.org/ww/goautoneg +github.com/prometheus/common/log github.com/prometheus/common/model # github.com/prometheus/procfs v0.0.11 github.com/prometheus/procfs github.com/prometheus/procfs/internal/fs github.com/prometheus/procfs/internal/util +# github.com/prometheus/statsd_exporter v0.15.0 +github.com/prometheus/statsd_exporter/pkg/mapper +github.com/prometheus/statsd_exporter/pkg/mapper/fsm # github.com/sergi/go-diff v1.1.0 github.com/sergi/go-diff/diffmatchpatch +# github.com/sirupsen/logrus v1.6.0 +github.com/sirupsen/logrus # github.com/spf13/pflag v1.0.5 github.com/spf13/pflag # github.com/tsenart/go-tsz v0.0.0-20180814235614-0bd30b3df1c3 @@ -240,6 +255,8 @@ golang.org/x/sync/semaphore golang.org/x/sys/internal/unsafeheader golang.org/x/sys/unix golang.org/x/sys/windows +golang.org/x/sys/windows/registry +golang.org/x/sys/windows/svc/eventlog # golang.org/x/text v0.3.3 golang.org/x/text/secure/bidirule golang.org/x/text/transform @@ -410,6 +427,8 @@ google.golang.org/protobuf/types/known/fieldmaskpb google.golang.org/protobuf/types/known/structpb google.golang.org/protobuf/types/known/timestamppb google.golang.org/protobuf/types/known/wrapperspb +# gopkg.in/alecthomas/kingpin.v2 v2.2.6 +gopkg.in/alecthomas/kingpin.v2 # gopkg.in/inf.v0 v0.9.1 gopkg.in/inf.v0 # gopkg.in/yaml.v2 v2.3.0 @@ -728,7 +747,7 @@ k8s.io/kube-openapi/pkg/util/sets k8s.io/utils/buffer k8s.io/utils/integer k8s.io/utils/trace -# knative.dev/pkg v0.0.0-20200714070918-ac02cac99b88 +# knative.dev/pkg v0.0.0-20200715082932-e863db03445f ## explicit knative.dev/pkg/apis knative.dev/pkg/apis/duck/ducktypes @@ -740,6 +759,7 @@ knative.dev/pkg/codegen/cmd/injection-gen/generators knative.dev/pkg/configmap knative.dev/pkg/controller knative.dev/pkg/hack +knative.dev/pkg/hash knative.dev/pkg/injection knative.dev/pkg/kmeta knative.dev/pkg/kmp