diff --git a/go.mod b/go.mod index 69bf1908995..f70654beee9 100644 --- a/go.mod +++ b/go.mod @@ -14,7 +14,7 @@ require ( golang.org/x/net v0.0.0-20210119194325-5f4716e94777 // indirect golang.org/x/oauth2 v0.0.0-20210210192628-66670185b0cd // indirect gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect - gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b + gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect ) // **WARNING** below are cortex replace directives, copied for compatibility diff --git a/go.sum b/go.sum index e10506de37c..d754633bc99 100644 --- a/go.sum +++ b/go.sum @@ -633,6 +633,7 @@ github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/ad github.com/gosimple/slug v1.9.0 h1:r5vDcYrFz9BmfIAMC829un9hq7hKM4cHUrsv36LbEqs= github.com/gosimple/slug v1.9.0/go.mod h1:AMZ+sOVe65uByN3kgEyf9WEBKBCSS+dJjMX9x4vDJbg= github.com/grafana/gocql v0.0.0-20200605141915-ba5dc39ece85/go.mod h1:crI9WX6p0IhrqB+DqIUHulRW853PaNFf7o4UprV//3I= +github.com/grafana/grafana v1.9.1 h1:4ykSOyQ+qImmZmILEgqsokVnWmvR8/A0zt7jN2RAQxM= github.com/grafana/grafana v1.9.2-0.20210217182004-6c4be29655a6 h1:qb6jgR/WKFRaISkkAbXU5YaZcp5oeElRspSzGNZBJZU= github.com/grafana/grafana v1.9.2-0.20210217182004-6c4be29655a6/go.mod h1:krMdqLnTBCyxzi+jRQ2LVlWngfWg2pihNlPD+VzzBww= github.com/grafana/grafana-aws-sdk v0.1.0 h1:25TSkS57lvVen3nTCNMu5SdI8ju5Z0f7cvCsukhlqJc= @@ -1770,6 +1771,7 @@ golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8T gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= gonum.org/v1/gonum v0.0.0-20181121035319-3f7ecaa7e8ca/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= gonum.org/v1/gonum v0.6.0/go.mod h1:9mxDZsDKxgMAuccQkewq682L+0eCu4dCN2yonUJTCLU= +gonum.org/v1/gonum v0.8.2 h1:CCXrcPKiGGotvnN6jfUsKk4rRqm7q09/YbKb5xCEvtM= gonum.org/v1/gonum v0.8.2/go.mod h1:oe/vMfY3deqTw+1EZJhuvEW2iwGF1bW9wwu7XCu0+v0= gonum.org/v1/netlib v0.0.0-20181029234149-ec6d1f5cefe6/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= diff --git a/pkg/api/cortex-ruler.go b/pkg/api/cortex-ruler.go index 8cbf9e44ba7..58d7fb10183 100644 --- a/pkg/api/cortex-ruler.go +++ b/pkg/api/cortex-ruler.go @@ -1,9 +1,9 @@ package api import ( + "github.com/grafana/grafana/pkg/services/ngalert/eval" "github.com/prometheus/common/model" "github.com/prometheus/prometheus/pkg/rulefmt" - "gopkg.in/yaml.v3" ) // swagger:route Get /api/v1/rules RulerConfig RouteGetRulesConfig @@ -99,5 +99,48 @@ type RuleGroupConfig struct { type ExtendedRuleNode struct { rulefmt.RuleNode - GrafanaManagedAlert yaml.Node `yaml:"alert,omitempty"` + //GrafanaManagedAlert yaml.Node `yaml:"grafana_alert,omitempty"` + GrafanaManagedAlert ExtendedUpsertAlertDefinitionCommand `yaml:"grafana_alert,omitempty"` +} + +// UpsertAlertDefinitionCommand is copy of the unexported struct: +https://github.com/grafana/grafana/blob/debb82e12417e82a0e2bd09e1a450065f884c1bc/pkg/services/ngalert/models.go#L85 +type UpsertAlertDefinitionCommand struct { + Title string `json:"title"` + OrgID int64 `json:"org_id"` + Condition string `json:"condition"` + Data []eval.AlertQuery `json:"data"` + IntervalSeconds *int64 `json:"intervalSeconds"` + // UID exists is set only for existing definitins + UID string `json:"uid"` + + Result *ngalert.AlertDefinition `json:"-"` +} + +// swagger:enum NoDataState +type NoDataState string + +const ( + Alerting NoDataState = "Alerting" + NoData NoDataState = "NoData" + KeepLastState NoDataState = "KeepLastState" + OK NoDataState = "OK" +) + +// swagger:enum ExecutionErrorState +type ExecutionErrorState string + +const ( + AlertingErrState ExecutionErrorState = "Alerting" + KeepLastStateErrState ExecutionErrorState = "KeepLastState" +) + +// ExtendedUpsertAlertDefinitionCommand extends UpsertAlertDefinitionCommand +// with properties of grafana dashboard alerts +// swagger:model +type ExtendedUpsertAlertDefinitionCommand struct { + UpsertAlertDefinitionCommand + NoDataState NoDataState `json:"no_data_state"` + ExecutionErrorState ExecutionErrorState `json:"exec_err_state"` + Settings map[string]interface{} `json:"settings"` } diff --git a/spec.json b/spec.json index 4821a2b856b..b04f428bba4 100644 --- a/spec.json +++ b/spec.json @@ -495,6 +495,31 @@ }, "x-go-package": "github.com/grafana/grafana/pkg/models" }, + "AlertQuery": { + "type": "object", + "title": "AlertQuery represents a single query associated with an alert definition.", + "properties": { + "model": { + "description": "JSON is the raw JSON query and includes the above properties as well as custom properties.", + "type": "object", + "x-go-name": "Model" + }, + "queryType": { + "description": "QueryType is an optional identifier for the type of query.\nIt can be used to distinguish different types of queries.", + "type": "string", + "x-go-name": "QueryType" + }, + "refId": { + "description": "RefID is the unique identifier of the query, set by the frontend call.", + "type": "string", + "x-go-name": "RefID" + }, + "relativeTimeRange": { + "$ref": "#/definitions/RelativeTimeRange" + } + }, + "x-go-package": "github.com/grafana/grafana/pkg/services/ngalert/eval" + }, "AlertResponse": { "type": "object", "properties": { @@ -802,9 +827,9 @@ "x-go-package": "github.com/grafana/grafana/pkg/models" }, "Duration": { - "description": "A Duration represents the elapsed time between two instants\nas an int64 nanosecond count. The representation limits the\nlargest representable duration to approximately 290 years.", "type": "integer", "format": "int64", + "title": "Duration is a type used for marshalling durations.", "$ref": "#/definitions/Duration" }, "EmailConfig": { @@ -906,7 +931,7 @@ "$ref": "#/definitions/Duration" }, "GrafanaManagedAlert": { - "$ref": "#/definitions/Node" + "$ref": "#/definitions/ExtendedUpsertAlertDefinitionCommand" }, "Labels": { "type": "object", @@ -920,6 +945,68 @@ }, "x-go-package": "github.com/grafana/alerting-api/pkg/api" }, + "ExtendedUpsertAlertDefinitionCommand": { + "description": "ExtendedUpsertAlertDefinitionCommand extends UpsertAlertDefinitionCommand\nwith properties of grafana dashboard alerts", + "type": "object", + "properties": { + "condition": { + "type": "string", + "x-go-name": "Condition" + }, + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/AlertQuery" + }, + "x-go-name": "Data" + }, + "exec_err_state": { + "type": "string", + "enum": [ + "Alerting", + "KeepLastState" + ], + "x-go-name": "ExecutionErrorState" + }, + "intervalSeconds": { + "type": "integer", + "format": "int64", + "x-go-name": "IntervalSeconds" + }, + "no_data_state": { + "type": "string", + "enum": [ + "Alerting", + "NoData", + "KeepLastState", + "OK" + ], + "x-go-name": "NoDataState" + }, + "org_id": { + "type": "integer", + "format": "int64", + "x-go-name": "OrgID" + }, + "settings": { + "type": "object", + "additionalProperties": { + "type": "object" + }, + "x-go-name": "Settings" + }, + "title": { + "type": "string", + "x-go-name": "Title" + }, + "uid": { + "description": "UID exists is set only for existing definitins", + "type": "string", + "x-go-name": "UID" + } + }, + "x-go-package": "github.com/grafana/alerting-api/pkg/api" + }, "GlobalConfig": { "description": "GlobalConfig defines configuration parameters that are valid globally\nunless overwritten.", "type": "object", @@ -1529,6 +1616,19 @@ "title": "Regexp is the representation of a compiled regular expression.", "x-go-package": "regexp" }, + "RelativeTimeRange": { + "description": "RelativeTimeRange is the per query start and end time\nfor requests.", + "type": "object", + "properties": { + "from": { + "$ref": "#/definitions/Duration" + }, + "to": { + "$ref": "#/definitions/Duration" + } + }, + "x-go-package": "github.com/grafana/grafana/pkg/services/ngalert/eval" + }, "Route": { "type": "object", "title": "A Route is a node that contains definitions of how to handle alerts.", @@ -2081,6 +2181,42 @@ "type": "object", "x-go-package": "github.com/grafana/alerting-api/pkg/models" }, + "UpsertAlertDefinitionCommand": { + "type": "object", + "properties": { + "condition": { + "type": "string", + "x-go-name": "Condition" + }, + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/AlertQuery" + }, + "x-go-name": "Data" + }, + "intervalSeconds": { + "type": "integer", + "format": "int64", + "x-go-name": "IntervalSeconds" + }, + "org_id": { + "type": "integer", + "format": "int64", + "x-go-name": "OrgID" + }, + "title": { + "type": "string", + "x-go-name": "Title" + }, + "uid": { + "description": "UID exists is set only for existing definitins", + "type": "string", + "x-go-name": "UID" + } + }, + "x-go-package": "github.com/grafana/alerting-api/pkg/api" + }, "Userinfo": { "description": "The Userinfo type is an immutable encapsulation of username and\npassword details for a URL. An existing Userinfo value is guaranteed\nto have a username set (potentially empty, as allowed by RFC 2396),\nand optionally a password.", "type": "object", diff --git a/vendor/github.com/fatih/color/LICENSE.md b/vendor/github.com/fatih/color/LICENSE.md new file mode 100644 index 00000000000..25fdaf639df --- /dev/null +++ b/vendor/github.com/fatih/color/LICENSE.md @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2013 Fatih Arslan + +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/fatih/color/README.md b/vendor/github.com/fatih/color/README.md new file mode 100644 index 00000000000..d62e4024aa5 --- /dev/null +++ b/vendor/github.com/fatih/color/README.md @@ -0,0 +1,173 @@ +# color [![](https://github.com/fatih/color/workflows/build/badge.svg)](https://github.com/fatih/color/actions) [![PkgGoDev](https://pkg.go.dev/badge/github.com/fatih/color)](https://pkg.go.dev/github.com/fatih/color) + +Color lets you use colorized outputs in terms of [ANSI Escape +Codes](http://en.wikipedia.org/wiki/ANSI_escape_code#Colors) in Go (Golang). It +has support for Windows too! The API can be used in several ways, pick one that +suits you. + +![Color](https://user-images.githubusercontent.com/438920/96832689-03b3e000-13f4-11eb-9803-46f4c4de3406.jpg) + + +## Install + +```bash +go get github.com/fatih/color +``` + +## Examples + +### Standard colors + +```go +// Print with default helper functions +color.Cyan("Prints text in cyan.") + +// A newline will be appended automatically +color.Blue("Prints %s in blue.", "text") + +// These are using the default foreground colors +color.Red("We have red") +color.Magenta("And many others ..") + +``` + +### Mix and reuse colors + +```go +// Create a new color object +c := color.New(color.FgCyan).Add(color.Underline) +c.Println("Prints cyan text with an underline.") + +// Or just add them to New() +d := color.New(color.FgCyan, color.Bold) +d.Printf("This prints bold cyan %s\n", "too!.") + +// Mix up foreground and background colors, create new mixes! +red := color.New(color.FgRed) + +boldRed := red.Add(color.Bold) +boldRed.Println("This will print text in bold red.") + +whiteBackground := red.Add(color.BgWhite) +whiteBackground.Println("Red text with white background.") +``` + +### Use your own output (io.Writer) + +```go +// Use your own io.Writer output +color.New(color.FgBlue).Fprintln(myWriter, "blue color!") + +blue := color.New(color.FgBlue) +blue.Fprint(writer, "This will print text in blue.") +``` + +### Custom print functions (PrintFunc) + +```go +// Create a custom print function for convenience +red := color.New(color.FgRed).PrintfFunc() +red("Warning") +red("Error: %s", err) + +// Mix up multiple attributes +notice := color.New(color.Bold, color.FgGreen).PrintlnFunc() +notice("Don't forget this...") +``` + +### Custom fprint functions (FprintFunc) + +```go +blue := color.New(FgBlue).FprintfFunc() +blue(myWriter, "important notice: %s", stars) + +// Mix up with multiple attributes +success := color.New(color.Bold, color.FgGreen).FprintlnFunc() +success(myWriter, "Don't forget this...") +``` + +### Insert into noncolor strings (SprintFunc) + +```go +// Create SprintXxx functions to mix strings with other non-colorized strings: +yellow := color.New(color.FgYellow).SprintFunc() +red := color.New(color.FgRed).SprintFunc() +fmt.Printf("This is a %s and this is %s.\n", yellow("warning"), red("error")) + +info := color.New(color.FgWhite, color.BgGreen).SprintFunc() +fmt.Printf("This %s rocks!\n", info("package")) + +// Use helper functions +fmt.Println("This", color.RedString("warning"), "should be not neglected.") +fmt.Printf("%v %v\n", color.GreenString("Info:"), "an important message.") + +// Windows supported too! Just don't forget to change the output to color.Output +fmt.Fprintf(color.Output, "Windows support: %s", color.GreenString("PASS")) +``` + +### Plug into existing code + +```go +// Use handy standard colors +color.Set(color.FgYellow) + +fmt.Println("Existing text will now be in yellow") +fmt.Printf("This one %s\n", "too") + +color.Unset() // Don't forget to unset + +// You can mix up parameters +color.Set(color.FgMagenta, color.Bold) +defer color.Unset() // Use it in your function + +fmt.Println("All text will now be bold magenta.") +``` + +### Disable/Enable color + +There might be a case where you want to explicitly disable/enable color output. the +`go-isatty` package will automatically disable color output for non-tty output streams +(for example if the output were piped directly to `less`) + +`Color` has support to disable/enable colors both globally and for single color +definitions. For example suppose you have a CLI app and a `--no-color` bool flag. You +can easily disable the color output with: + +```go + +var flagNoColor = flag.Bool("no-color", false, "Disable color output") + +if *flagNoColor { + color.NoColor = true // disables colorized output +} +``` + +It also has support for single color definitions (local). You can +disable/enable color output on the fly: + +```go +c := color.New(color.FgCyan) +c.Println("Prints cyan text") + +c.DisableColor() +c.Println("This is printed without any color") + +c.EnableColor() +c.Println("This prints again cyan...") +``` + +## Todo + +* Save/Return previous values +* Evaluate fmt.Formatter interface + + +## Credits + + * [Fatih Arslan](https://github.com/fatih) + * Windows support via @mattn: [colorable](https://github.com/mattn/go-colorable) + +## License + +The MIT License (MIT) - see [`LICENSE.md`](https://github.com/fatih/color/blob/master/LICENSE.md) for more details + diff --git a/vendor/github.com/fatih/color/color.go b/vendor/github.com/fatih/color/color.go new file mode 100644 index 00000000000..91c8e9f0620 --- /dev/null +++ b/vendor/github.com/fatih/color/color.go @@ -0,0 +1,603 @@ +package color + +import ( + "fmt" + "io" + "os" + "strconv" + "strings" + "sync" + + "github.com/mattn/go-colorable" + "github.com/mattn/go-isatty" +) + +var ( + // NoColor defines if the output is colorized or not. It's dynamically set to + // false or true based on the stdout's file descriptor referring to a terminal + // or not. This is a global option and affects all colors. For more control + // over each color block use the methods DisableColor() individually. + NoColor = os.Getenv("TERM") == "dumb" || + (!isatty.IsTerminal(os.Stdout.Fd()) && !isatty.IsCygwinTerminal(os.Stdout.Fd())) + + // Output defines the standard output of the print functions. By default + // os.Stdout is used. + Output = colorable.NewColorableStdout() + + // Error defines a color supporting writer for os.Stderr. + Error = colorable.NewColorableStderr() + + // colorsCache is used to reduce the count of created Color objects and + // allows to reuse already created objects with required Attribute. + colorsCache = make(map[Attribute]*Color) + colorsCacheMu sync.Mutex // protects colorsCache +) + +// Color defines a custom color object which is defined by SGR parameters. +type Color struct { + params []Attribute + noColor *bool +} + +// Attribute defines a single SGR Code +type Attribute int + +const escape = "\x1b" + +// Base attributes +const ( + Reset Attribute = iota + Bold + Faint + Italic + Underline + BlinkSlow + BlinkRapid + ReverseVideo + Concealed + CrossedOut +) + +// Foreground text colors +const ( + FgBlack Attribute = iota + 30 + FgRed + FgGreen + FgYellow + FgBlue + FgMagenta + FgCyan + FgWhite +) + +// Foreground Hi-Intensity text colors +const ( + FgHiBlack Attribute = iota + 90 + FgHiRed + FgHiGreen + FgHiYellow + FgHiBlue + FgHiMagenta + FgHiCyan + FgHiWhite +) + +// Background text colors +const ( + BgBlack Attribute = iota + 40 + BgRed + BgGreen + BgYellow + BgBlue + BgMagenta + BgCyan + BgWhite +) + +// Background Hi-Intensity text colors +const ( + BgHiBlack Attribute = iota + 100 + BgHiRed + BgHiGreen + BgHiYellow + BgHiBlue + BgHiMagenta + BgHiCyan + BgHiWhite +) + +// New returns a newly created color object. +func New(value ...Attribute) *Color { + c := &Color{params: make([]Attribute, 0)} + c.Add(value...) + return c +} + +// Set sets the given parameters immediately. It will change the color of +// output with the given SGR parameters until color.Unset() is called. +func Set(p ...Attribute) *Color { + c := New(p...) + c.Set() + return c +} + +// Unset resets all escape attributes and clears the output. Usually should +// be called after Set(). +func Unset() { + if NoColor { + return + } + + fmt.Fprintf(Output, "%s[%dm", escape, Reset) +} + +// Set sets the SGR sequence. +func (c *Color) Set() *Color { + if c.isNoColorSet() { + return c + } + + fmt.Fprintf(Output, c.format()) + return c +} + +func (c *Color) unset() { + if c.isNoColorSet() { + return + } + + Unset() +} + +func (c *Color) setWriter(w io.Writer) *Color { + if c.isNoColorSet() { + return c + } + + fmt.Fprintf(w, c.format()) + return c +} + +func (c *Color) unsetWriter(w io.Writer) { + if c.isNoColorSet() { + return + } + + if NoColor { + return + } + + fmt.Fprintf(w, "%s[%dm", escape, Reset) +} + +// Add is used to chain SGR parameters. Use as many as parameters to combine +// and create custom color objects. Example: Add(color.FgRed, color.Underline). +func (c *Color) Add(value ...Attribute) *Color { + c.params = append(c.params, value...) + return c +} + +func (c *Color) prepend(value Attribute) { + c.params = append(c.params, 0) + copy(c.params[1:], c.params[0:]) + c.params[0] = value +} + +// Fprint formats using the default formats for its operands and writes to w. +// Spaces are added between operands when neither is a string. +// It returns the number of bytes written and any write error encountered. +// On Windows, users should wrap w with colorable.NewColorable() if w is of +// type *os.File. +func (c *Color) Fprint(w io.Writer, a ...interface{}) (n int, err error) { + c.setWriter(w) + defer c.unsetWriter(w) + + return fmt.Fprint(w, a...) +} + +// Print formats using the default formats for its operands and writes to +// standard output. Spaces are added between operands when neither is a +// string. It returns the number of bytes written and any write error +// encountered. This is the standard fmt.Print() method wrapped with the given +// color. +func (c *Color) Print(a ...interface{}) (n int, err error) { + c.Set() + defer c.unset() + + return fmt.Fprint(Output, a...) +} + +// Fprintf formats according to a format specifier and writes to w. +// It returns the number of bytes written and any write error encountered. +// On Windows, users should wrap w with colorable.NewColorable() if w is of +// type *os.File. +func (c *Color) Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error) { + c.setWriter(w) + defer c.unsetWriter(w) + + return fmt.Fprintf(w, format, a...) +} + +// Printf formats according to a format specifier and writes to standard output. +// It returns the number of bytes written and any write error encountered. +// This is the standard fmt.Printf() method wrapped with the given color. +func (c *Color) Printf(format string, a ...interface{}) (n int, err error) { + c.Set() + defer c.unset() + + return fmt.Fprintf(Output, format, a...) +} + +// Fprintln formats using the default formats for its operands and writes to w. +// Spaces are always added between operands and a newline is appended. +// On Windows, users should wrap w with colorable.NewColorable() if w is of +// type *os.File. +func (c *Color) Fprintln(w io.Writer, a ...interface{}) (n int, err error) { + c.setWriter(w) + defer c.unsetWriter(w) + + return fmt.Fprintln(w, a...) +} + +// Println formats using the default formats for its operands and writes to +// standard output. Spaces are always added between operands and a newline is +// appended. It returns the number of bytes written and any write error +// encountered. This is the standard fmt.Print() method wrapped with the given +// color. +func (c *Color) Println(a ...interface{}) (n int, err error) { + c.Set() + defer c.unset() + + return fmt.Fprintln(Output, a...) +} + +// Sprint is just like Print, but returns a string instead of printing it. +func (c *Color) Sprint(a ...interface{}) string { + return c.wrap(fmt.Sprint(a...)) +} + +// Sprintln is just like Println, but returns a string instead of printing it. +func (c *Color) Sprintln(a ...interface{}) string { + return c.wrap(fmt.Sprintln(a...)) +} + +// Sprintf is just like Printf, but returns a string instead of printing it. +func (c *Color) Sprintf(format string, a ...interface{}) string { + return c.wrap(fmt.Sprintf(format, a...)) +} + +// FprintFunc returns a new function that prints the passed arguments as +// colorized with color.Fprint(). +func (c *Color) FprintFunc() func(w io.Writer, a ...interface{}) { + return func(w io.Writer, a ...interface{}) { + c.Fprint(w, a...) + } +} + +// PrintFunc returns a new function that prints the passed arguments as +// colorized with color.Print(). +func (c *Color) PrintFunc() func(a ...interface{}) { + return func(a ...interface{}) { + c.Print(a...) + } +} + +// FprintfFunc returns a new function that prints the passed arguments as +// colorized with color.Fprintf(). +func (c *Color) FprintfFunc() func(w io.Writer, format string, a ...interface{}) { + return func(w io.Writer, format string, a ...interface{}) { + c.Fprintf(w, format, a...) + } +} + +// PrintfFunc returns a new function that prints the passed arguments as +// colorized with color.Printf(). +func (c *Color) PrintfFunc() func(format string, a ...interface{}) { + return func(format string, a ...interface{}) { + c.Printf(format, a...) + } +} + +// FprintlnFunc returns a new function that prints the passed arguments as +// colorized with color.Fprintln(). +func (c *Color) FprintlnFunc() func(w io.Writer, a ...interface{}) { + return func(w io.Writer, a ...interface{}) { + c.Fprintln(w, a...) + } +} + +// PrintlnFunc returns a new function that prints the passed arguments as +// colorized with color.Println(). +func (c *Color) PrintlnFunc() func(a ...interface{}) { + return func(a ...interface{}) { + c.Println(a...) + } +} + +// SprintFunc returns a new function that returns colorized strings for the +// given arguments with fmt.Sprint(). Useful to put into or mix into other +// string. Windows users should use this in conjunction with color.Output, example: +// +// put := New(FgYellow).SprintFunc() +// fmt.Fprintf(color.Output, "This is a %s", put("warning")) +func (c *Color) SprintFunc() func(a ...interface{}) string { + return func(a ...interface{}) string { + return c.wrap(fmt.Sprint(a...)) + } +} + +// SprintfFunc returns a new function that returns colorized strings for the +// given arguments with fmt.Sprintf(). Useful to put into or mix into other +// string. Windows users should use this in conjunction with color.Output. +func (c *Color) SprintfFunc() func(format string, a ...interface{}) string { + return func(format string, a ...interface{}) string { + return c.wrap(fmt.Sprintf(format, a...)) + } +} + +// SprintlnFunc returns a new function that returns colorized strings for the +// given arguments with fmt.Sprintln(). Useful to put into or mix into other +// string. Windows users should use this in conjunction with color.Output. +func (c *Color) SprintlnFunc() func(a ...interface{}) string { + return func(a ...interface{}) string { + return c.wrap(fmt.Sprintln(a...)) + } +} + +// sequence returns a formatted SGR sequence to be plugged into a "\x1b[...m" +// an example output might be: "1;36" -> bold cyan +func (c *Color) sequence() string { + format := make([]string, len(c.params)) + for i, v := range c.params { + format[i] = strconv.Itoa(int(v)) + } + + return strings.Join(format, ";") +} + +// wrap wraps the s string with the colors attributes. The string is ready to +// be printed. +func (c *Color) wrap(s string) string { + if c.isNoColorSet() { + return s + } + + return c.format() + s + c.unformat() +} + +func (c *Color) format() string { + return fmt.Sprintf("%s[%sm", escape, c.sequence()) +} + +func (c *Color) unformat() string { + return fmt.Sprintf("%s[%dm", escape, Reset) +} + +// DisableColor disables the color output. Useful to not change any existing +// code and still being able to output. Can be used for flags like +// "--no-color". To enable back use EnableColor() method. +func (c *Color) DisableColor() { + c.noColor = boolPtr(true) +} + +// EnableColor enables the color output. Use it in conjunction with +// DisableColor(). Otherwise this method has no side effects. +func (c *Color) EnableColor() { + c.noColor = boolPtr(false) +} + +func (c *Color) isNoColorSet() bool { + // check first if we have user setted action + if c.noColor != nil { + return *c.noColor + } + + // if not return the global option, which is disabled by default + return NoColor +} + +// Equals returns a boolean value indicating whether two colors are equal. +func (c *Color) Equals(c2 *Color) bool { + if len(c.params) != len(c2.params) { + return false + } + + for _, attr := range c.params { + if !c2.attrExists(attr) { + return false + } + } + + return true +} + +func (c *Color) attrExists(a Attribute) bool { + for _, attr := range c.params { + if attr == a { + return true + } + } + + return false +} + +func boolPtr(v bool) *bool { + return &v +} + +func getCachedColor(p Attribute) *Color { + colorsCacheMu.Lock() + defer colorsCacheMu.Unlock() + + c, ok := colorsCache[p] + if !ok { + c = New(p) + colorsCache[p] = c + } + + return c +} + +func colorPrint(format string, p Attribute, a ...interface{}) { + c := getCachedColor(p) + + if !strings.HasSuffix(format, "\n") { + format += "\n" + } + + if len(a) == 0 { + c.Print(format) + } else { + c.Printf(format, a...) + } +} + +func colorString(format string, p Attribute, a ...interface{}) string { + c := getCachedColor(p) + + if len(a) == 0 { + return c.SprintFunc()(format) + } + + return c.SprintfFunc()(format, a...) +} + +// Black is a convenient helper function to print with black foreground. A +// newline is appended to format by default. +func Black(format string, a ...interface{}) { colorPrint(format, FgBlack, a...) } + +// Red is a convenient helper function to print with red foreground. A +// newline is appended to format by default. +func Red(format string, a ...interface{}) { colorPrint(format, FgRed, a...) } + +// Green is a convenient helper function to print with green foreground. A +// newline is appended to format by default. +func Green(format string, a ...interface{}) { colorPrint(format, FgGreen, a...) } + +// Yellow is a convenient helper function to print with yellow foreground. +// A newline is appended to format by default. +func Yellow(format string, a ...interface{}) { colorPrint(format, FgYellow, a...) } + +// Blue is a convenient helper function to print with blue foreground. A +// newline is appended to format by default. +func Blue(format string, a ...interface{}) { colorPrint(format, FgBlue, a...) } + +// Magenta is a convenient helper function to print with magenta foreground. +// A newline is appended to format by default. +func Magenta(format string, a ...interface{}) { colorPrint(format, FgMagenta, a...) } + +// Cyan is a convenient helper function to print with cyan foreground. A +// newline is appended to format by default. +func Cyan(format string, a ...interface{}) { colorPrint(format, FgCyan, a...) } + +// White is a convenient helper function to print with white foreground. A +// newline is appended to format by default. +func White(format string, a ...interface{}) { colorPrint(format, FgWhite, a...) } + +// BlackString is a convenient helper function to return a string with black +// foreground. +func BlackString(format string, a ...interface{}) string { return colorString(format, FgBlack, a...) } + +// RedString is a convenient helper function to return a string with red +// foreground. +func RedString(format string, a ...interface{}) string { return colorString(format, FgRed, a...) } + +// GreenString is a convenient helper function to return a string with green +// foreground. +func GreenString(format string, a ...interface{}) string { return colorString(format, FgGreen, a...) } + +// YellowString is a convenient helper function to return a string with yellow +// foreground. +func YellowString(format string, a ...interface{}) string { return colorString(format, FgYellow, a...) } + +// BlueString is a convenient helper function to return a string with blue +// foreground. +func BlueString(format string, a ...interface{}) string { return colorString(format, FgBlue, a...) } + +// MagentaString is a convenient helper function to return a string with magenta +// foreground. +func MagentaString(format string, a ...interface{}) string { + return colorString(format, FgMagenta, a...) +} + +// CyanString is a convenient helper function to return a string with cyan +// foreground. +func CyanString(format string, a ...interface{}) string { return colorString(format, FgCyan, a...) } + +// WhiteString is a convenient helper function to return a string with white +// foreground. +func WhiteString(format string, a ...interface{}) string { return colorString(format, FgWhite, a...) } + +// HiBlack is a convenient helper function to print with hi-intensity black foreground. A +// newline is appended to format by default. +func HiBlack(format string, a ...interface{}) { colorPrint(format, FgHiBlack, a...) } + +// HiRed is a convenient helper function to print with hi-intensity red foreground. A +// newline is appended to format by default. +func HiRed(format string, a ...interface{}) { colorPrint(format, FgHiRed, a...) } + +// HiGreen is a convenient helper function to print with hi-intensity green foreground. A +// newline is appended to format by default. +func HiGreen(format string, a ...interface{}) { colorPrint(format, FgHiGreen, a...) } + +// HiYellow is a convenient helper function to print with hi-intensity yellow foreground. +// A newline is appended to format by default. +func HiYellow(format string, a ...interface{}) { colorPrint(format, FgHiYellow, a...) } + +// HiBlue is a convenient helper function to print with hi-intensity blue foreground. A +// newline is appended to format by default. +func HiBlue(format string, a ...interface{}) { colorPrint(format, FgHiBlue, a...) } + +// HiMagenta is a convenient helper function to print with hi-intensity magenta foreground. +// A newline is appended to format by default. +func HiMagenta(format string, a ...interface{}) { colorPrint(format, FgHiMagenta, a...) } + +// HiCyan is a convenient helper function to print with hi-intensity cyan foreground. A +// newline is appended to format by default. +func HiCyan(format string, a ...interface{}) { colorPrint(format, FgHiCyan, a...) } + +// HiWhite is a convenient helper function to print with hi-intensity white foreground. A +// newline is appended to format by default. +func HiWhite(format string, a ...interface{}) { colorPrint(format, FgHiWhite, a...) } + +// HiBlackString is a convenient helper function to return a string with hi-intensity black +// foreground. +func HiBlackString(format string, a ...interface{}) string { + return colorString(format, FgHiBlack, a...) +} + +// HiRedString is a convenient helper function to return a string with hi-intensity red +// foreground. +func HiRedString(format string, a ...interface{}) string { return colorString(format, FgHiRed, a...) } + +// HiGreenString is a convenient helper function to return a string with hi-intensity green +// foreground. +func HiGreenString(format string, a ...interface{}) string { + return colorString(format, FgHiGreen, a...) +} + +// HiYellowString is a convenient helper function to return a string with hi-intensity yellow +// foreground. +func HiYellowString(format string, a ...interface{}) string { + return colorString(format, FgHiYellow, a...) +} + +// HiBlueString is a convenient helper function to return a string with hi-intensity blue +// foreground. +func HiBlueString(format string, a ...interface{}) string { return colorString(format, FgHiBlue, a...) } + +// HiMagentaString is a convenient helper function to return a string with hi-intensity magenta +// foreground. +func HiMagentaString(format string, a ...interface{}) string { + return colorString(format, FgHiMagenta, a...) +} + +// HiCyanString is a convenient helper function to return a string with hi-intensity cyan +// foreground. +func HiCyanString(format string, a ...interface{}) string { return colorString(format, FgHiCyan, a...) } + +// HiWhiteString is a convenient helper function to return a string with hi-intensity white +// foreground. +func HiWhiteString(format string, a ...interface{}) string { + return colorString(format, FgHiWhite, a...) +} diff --git a/vendor/github.com/fatih/color/doc.go b/vendor/github.com/fatih/color/doc.go new file mode 100644 index 00000000000..cf1e96500f4 --- /dev/null +++ b/vendor/github.com/fatih/color/doc.go @@ -0,0 +1,133 @@ +/* +Package color is an ANSI color package to output colorized or SGR defined +output to the standard output. The API can be used in several way, pick one +that suits you. + +Use simple and default helper functions with predefined foreground colors: + + color.Cyan("Prints text in cyan.") + + // a newline will be appended automatically + color.Blue("Prints %s in blue.", "text") + + // More default foreground colors.. + color.Red("We have red") + color.Yellow("Yellow color too!") + color.Magenta("And many others ..") + + // Hi-intensity colors + color.HiGreen("Bright green color.") + color.HiBlack("Bright black means gray..") + color.HiWhite("Shiny white color!") + +However there are times where custom color mixes are required. Below are some +examples to create custom color objects and use the print functions of each +separate color object. + + // Create a new color object + c := color.New(color.FgCyan).Add(color.Underline) + c.Println("Prints cyan text with an underline.") + + // Or just add them to New() + d := color.New(color.FgCyan, color.Bold) + d.Printf("This prints bold cyan %s\n", "too!.") + + + // Mix up foreground and background colors, create new mixes! + red := color.New(color.FgRed) + + boldRed := red.Add(color.Bold) + boldRed.Println("This will print text in bold red.") + + whiteBackground := red.Add(color.BgWhite) + whiteBackground.Println("Red text with White background.") + + // Use your own io.Writer output + color.New(color.FgBlue).Fprintln(myWriter, "blue color!") + + blue := color.New(color.FgBlue) + blue.Fprint(myWriter, "This will print text in blue.") + +You can create PrintXxx functions to simplify even more: + + // Create a custom print function for convenient + red := color.New(color.FgRed).PrintfFunc() + red("warning") + red("error: %s", err) + + // Mix up multiple attributes + notice := color.New(color.Bold, color.FgGreen).PrintlnFunc() + notice("don't forget this...") + +You can also FprintXxx functions to pass your own io.Writer: + + blue := color.New(FgBlue).FprintfFunc() + blue(myWriter, "important notice: %s", stars) + + // Mix up with multiple attributes + success := color.New(color.Bold, color.FgGreen).FprintlnFunc() + success(myWriter, don't forget this...") + + +Or create SprintXxx functions to mix strings with other non-colorized strings: + + yellow := New(FgYellow).SprintFunc() + red := New(FgRed).SprintFunc() + + fmt.Printf("this is a %s and this is %s.\n", yellow("warning"), red("error")) + + info := New(FgWhite, BgGreen).SprintFunc() + fmt.Printf("this %s rocks!\n", info("package")) + +Windows support is enabled by default. All Print functions work as intended. +However only for color.SprintXXX functions, user should use fmt.FprintXXX and +set the output to color.Output: + + fmt.Fprintf(color.Output, "Windows support: %s", color.GreenString("PASS")) + + info := New(FgWhite, BgGreen).SprintFunc() + fmt.Fprintf(color.Output, "this %s rocks!\n", info("package")) + +Using with existing code is possible. Just use the Set() method to set the +standard output to the given parameters. That way a rewrite of an existing +code is not required. + + // Use handy standard colors. + color.Set(color.FgYellow) + + fmt.Println("Existing text will be now in Yellow") + fmt.Printf("This one %s\n", "too") + + color.Unset() // don't forget to unset + + // You can mix up parameters + color.Set(color.FgMagenta, color.Bold) + defer color.Unset() // use it in your function + + fmt.Println("All text will be now bold magenta.") + +There might be a case where you want to disable color output (for example to +pipe the standard output of your app to somewhere else). `Color` has support to +disable colors both globally and for single color definition. For example +suppose you have a CLI app and a `--no-color` bool flag. You can easily disable +the color output with: + + var flagNoColor = flag.Bool("no-color", false, "Disable color output") + + if *flagNoColor { + color.NoColor = true // disables colorized output + } + +It also has support for single color definitions (local). You can +disable/enable color output on the fly: + + c := color.New(color.FgCyan) + c.Println("Prints cyan text") + + c.DisableColor() + c.Println("This is printed without any color") + + c.EnableColor() + c.Println("This prints again cyan...") +*/ +package color diff --git a/vendor/github.com/fatih/color/go.mod b/vendor/github.com/fatih/color/go.mod new file mode 100644 index 00000000000..78872815ea6 --- /dev/null +++ b/vendor/github.com/fatih/color/go.mod @@ -0,0 +1,8 @@ +module github.com/fatih/color + +go 1.13 + +require ( + github.com/mattn/go-colorable v0.1.8 + github.com/mattn/go-isatty v0.0.12 +) diff --git a/vendor/github.com/fatih/color/go.sum b/vendor/github.com/fatih/color/go.sum new file mode 100644 index 00000000000..54f7c46e810 --- /dev/null +++ b/vendor/github.com/fatih/color/go.sum @@ -0,0 +1,7 @@ +github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8= +github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae h1:/WDfKMnPU+m5M4xB+6x4kaepxRw6jWvR5iDRdvjHgy8= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= diff --git a/vendor/github.com/golang/protobuf/protoc-gen-go/descriptor/descriptor.pb.go b/vendor/github.com/golang/protobuf/protoc-gen-go/descriptor/descriptor.pb.go new file mode 100644 index 00000000000..63dc0578514 --- /dev/null +++ b/vendor/github.com/golang/protobuf/protoc-gen-go/descriptor/descriptor.pb.go @@ -0,0 +1,200 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// source: github.com/golang/protobuf/protoc-gen-go/descriptor/descriptor.proto + +package descriptor + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + descriptorpb "google.golang.org/protobuf/types/descriptorpb" + reflect "reflect" +) + +// Symbols defined in public import of google/protobuf/descriptor.proto. + +type FieldDescriptorProto_Type = descriptorpb.FieldDescriptorProto_Type + +const FieldDescriptorProto_TYPE_DOUBLE = descriptorpb.FieldDescriptorProto_TYPE_DOUBLE +const FieldDescriptorProto_TYPE_FLOAT = descriptorpb.FieldDescriptorProto_TYPE_FLOAT +const FieldDescriptorProto_TYPE_INT64 = descriptorpb.FieldDescriptorProto_TYPE_INT64 +const FieldDescriptorProto_TYPE_UINT64 = descriptorpb.FieldDescriptorProto_TYPE_UINT64 +const FieldDescriptorProto_TYPE_INT32 = descriptorpb.FieldDescriptorProto_TYPE_INT32 +const FieldDescriptorProto_TYPE_FIXED64 = descriptorpb.FieldDescriptorProto_TYPE_FIXED64 +const FieldDescriptorProto_TYPE_FIXED32 = descriptorpb.FieldDescriptorProto_TYPE_FIXED32 +const FieldDescriptorProto_TYPE_BOOL = descriptorpb.FieldDescriptorProto_TYPE_BOOL +const FieldDescriptorProto_TYPE_STRING = descriptorpb.FieldDescriptorProto_TYPE_STRING +const FieldDescriptorProto_TYPE_GROUP = descriptorpb.FieldDescriptorProto_TYPE_GROUP +const FieldDescriptorProto_TYPE_MESSAGE = descriptorpb.FieldDescriptorProto_TYPE_MESSAGE +const FieldDescriptorProto_TYPE_BYTES = descriptorpb.FieldDescriptorProto_TYPE_BYTES +const FieldDescriptorProto_TYPE_UINT32 = descriptorpb.FieldDescriptorProto_TYPE_UINT32 +const FieldDescriptorProto_TYPE_ENUM = descriptorpb.FieldDescriptorProto_TYPE_ENUM +const FieldDescriptorProto_TYPE_SFIXED32 = descriptorpb.FieldDescriptorProto_TYPE_SFIXED32 +const FieldDescriptorProto_TYPE_SFIXED64 = descriptorpb.FieldDescriptorProto_TYPE_SFIXED64 +const FieldDescriptorProto_TYPE_SINT32 = descriptorpb.FieldDescriptorProto_TYPE_SINT32 +const FieldDescriptorProto_TYPE_SINT64 = descriptorpb.FieldDescriptorProto_TYPE_SINT64 + +var FieldDescriptorProto_Type_name = descriptorpb.FieldDescriptorProto_Type_name +var FieldDescriptorProto_Type_value = descriptorpb.FieldDescriptorProto_Type_value + +type FieldDescriptorProto_Label = descriptorpb.FieldDescriptorProto_Label + +const FieldDescriptorProto_LABEL_OPTIONAL = descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL +const FieldDescriptorProto_LABEL_REQUIRED = descriptorpb.FieldDescriptorProto_LABEL_REQUIRED +const FieldDescriptorProto_LABEL_REPEATED = descriptorpb.FieldDescriptorProto_LABEL_REPEATED + +var FieldDescriptorProto_Label_name = descriptorpb.FieldDescriptorProto_Label_name +var FieldDescriptorProto_Label_value = descriptorpb.FieldDescriptorProto_Label_value + +type FileOptions_OptimizeMode = descriptorpb.FileOptions_OptimizeMode + +const FileOptions_SPEED = descriptorpb.FileOptions_SPEED +const FileOptions_CODE_SIZE = descriptorpb.FileOptions_CODE_SIZE +const FileOptions_LITE_RUNTIME = descriptorpb.FileOptions_LITE_RUNTIME + +var FileOptions_OptimizeMode_name = descriptorpb.FileOptions_OptimizeMode_name +var FileOptions_OptimizeMode_value = descriptorpb.FileOptions_OptimizeMode_value + +type FieldOptions_CType = descriptorpb.FieldOptions_CType + +const FieldOptions_STRING = descriptorpb.FieldOptions_STRING +const FieldOptions_CORD = descriptorpb.FieldOptions_CORD +const FieldOptions_STRING_PIECE = descriptorpb.FieldOptions_STRING_PIECE + +var FieldOptions_CType_name = descriptorpb.FieldOptions_CType_name +var FieldOptions_CType_value = descriptorpb.FieldOptions_CType_value + +type FieldOptions_JSType = descriptorpb.FieldOptions_JSType + +const FieldOptions_JS_NORMAL = descriptorpb.FieldOptions_JS_NORMAL +const FieldOptions_JS_STRING = descriptorpb.FieldOptions_JS_STRING +const FieldOptions_JS_NUMBER = descriptorpb.FieldOptions_JS_NUMBER + +var FieldOptions_JSType_name = descriptorpb.FieldOptions_JSType_name +var FieldOptions_JSType_value = descriptorpb.FieldOptions_JSType_value + +type MethodOptions_IdempotencyLevel = descriptorpb.MethodOptions_IdempotencyLevel + +const MethodOptions_IDEMPOTENCY_UNKNOWN = descriptorpb.MethodOptions_IDEMPOTENCY_UNKNOWN +const MethodOptions_NO_SIDE_EFFECTS = descriptorpb.MethodOptions_NO_SIDE_EFFECTS +const MethodOptions_IDEMPOTENT = descriptorpb.MethodOptions_IDEMPOTENT + +var MethodOptions_IdempotencyLevel_name = descriptorpb.MethodOptions_IdempotencyLevel_name +var MethodOptions_IdempotencyLevel_value = descriptorpb.MethodOptions_IdempotencyLevel_value + +type FileDescriptorSet = descriptorpb.FileDescriptorSet +type FileDescriptorProto = descriptorpb.FileDescriptorProto +type DescriptorProto = descriptorpb.DescriptorProto +type ExtensionRangeOptions = descriptorpb.ExtensionRangeOptions +type FieldDescriptorProto = descriptorpb.FieldDescriptorProto +type OneofDescriptorProto = descriptorpb.OneofDescriptorProto +type EnumDescriptorProto = descriptorpb.EnumDescriptorProto +type EnumValueDescriptorProto = descriptorpb.EnumValueDescriptorProto +type ServiceDescriptorProto = descriptorpb.ServiceDescriptorProto +type MethodDescriptorProto = descriptorpb.MethodDescriptorProto + +const Default_MethodDescriptorProto_ClientStreaming = descriptorpb.Default_MethodDescriptorProto_ClientStreaming +const Default_MethodDescriptorProto_ServerStreaming = descriptorpb.Default_MethodDescriptorProto_ServerStreaming + +type FileOptions = descriptorpb.FileOptions + +const Default_FileOptions_JavaMultipleFiles = descriptorpb.Default_FileOptions_JavaMultipleFiles +const Default_FileOptions_JavaStringCheckUtf8 = descriptorpb.Default_FileOptions_JavaStringCheckUtf8 +const Default_FileOptions_OptimizeFor = descriptorpb.Default_FileOptions_OptimizeFor +const Default_FileOptions_CcGenericServices = descriptorpb.Default_FileOptions_CcGenericServices +const Default_FileOptions_JavaGenericServices = descriptorpb.Default_FileOptions_JavaGenericServices +const Default_FileOptions_PyGenericServices = descriptorpb.Default_FileOptions_PyGenericServices +const Default_FileOptions_PhpGenericServices = descriptorpb.Default_FileOptions_PhpGenericServices +const Default_FileOptions_Deprecated = descriptorpb.Default_FileOptions_Deprecated +const Default_FileOptions_CcEnableArenas = descriptorpb.Default_FileOptions_CcEnableArenas + +type MessageOptions = descriptorpb.MessageOptions + +const Default_MessageOptions_MessageSetWireFormat = descriptorpb.Default_MessageOptions_MessageSetWireFormat +const Default_MessageOptions_NoStandardDescriptorAccessor = descriptorpb.Default_MessageOptions_NoStandardDescriptorAccessor +const Default_MessageOptions_Deprecated = descriptorpb.Default_MessageOptions_Deprecated + +type FieldOptions = descriptorpb.FieldOptions + +const Default_FieldOptions_Ctype = descriptorpb.Default_FieldOptions_Ctype +const Default_FieldOptions_Jstype = descriptorpb.Default_FieldOptions_Jstype +const Default_FieldOptions_Lazy = descriptorpb.Default_FieldOptions_Lazy +const Default_FieldOptions_Deprecated = descriptorpb.Default_FieldOptions_Deprecated +const Default_FieldOptions_Weak = descriptorpb.Default_FieldOptions_Weak + +type OneofOptions = descriptorpb.OneofOptions +type EnumOptions = descriptorpb.EnumOptions + +const Default_EnumOptions_Deprecated = descriptorpb.Default_EnumOptions_Deprecated + +type EnumValueOptions = descriptorpb.EnumValueOptions + +const Default_EnumValueOptions_Deprecated = descriptorpb.Default_EnumValueOptions_Deprecated + +type ServiceOptions = descriptorpb.ServiceOptions + +const Default_ServiceOptions_Deprecated = descriptorpb.Default_ServiceOptions_Deprecated + +type MethodOptions = descriptorpb.MethodOptions + +const Default_MethodOptions_Deprecated = descriptorpb.Default_MethodOptions_Deprecated +const Default_MethodOptions_IdempotencyLevel = descriptorpb.Default_MethodOptions_IdempotencyLevel + +type UninterpretedOption = descriptorpb.UninterpretedOption +type SourceCodeInfo = descriptorpb.SourceCodeInfo +type GeneratedCodeInfo = descriptorpb.GeneratedCodeInfo +type DescriptorProto_ExtensionRange = descriptorpb.DescriptorProto_ExtensionRange +type DescriptorProto_ReservedRange = descriptorpb.DescriptorProto_ReservedRange +type EnumDescriptorProto_EnumReservedRange = descriptorpb.EnumDescriptorProto_EnumReservedRange +type UninterpretedOption_NamePart = descriptorpb.UninterpretedOption_NamePart +type SourceCodeInfo_Location = descriptorpb.SourceCodeInfo_Location +type GeneratedCodeInfo_Annotation = descriptorpb.GeneratedCodeInfo_Annotation + +var File_github_com_golang_protobuf_protoc_gen_go_descriptor_descriptor_proto protoreflect.FileDescriptor + +var file_github_com_golang_protobuf_protoc_gen_go_descriptor_descriptor_proto_rawDesc = []byte{ + 0x0a, 0x44, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x6f, 0x6c, + 0x61, 0x6e, 0x67, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x63, 0x2d, 0x67, 0x65, 0x6e, 0x2d, 0x67, 0x6f, 0x2f, 0x64, 0x65, 0x73, 0x63, 0x72, + 0x69, 0x70, 0x74, 0x6f, 0x72, 0x2f, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x20, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, + 0x6f, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x42, 0x40, 0x5a, 0x3e, 0x67, 0x69, 0x74, 0x68, + 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x6f, 0x6c, 0x61, 0x6e, 0x67, 0x2f, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x2d, 0x67, 0x65, + 0x6e, 0x2d, 0x67, 0x6f, 0x2f, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x3b, + 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x50, 0x00, 0x62, 0x06, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x32, +} + +var file_github_com_golang_protobuf_protoc_gen_go_descriptor_descriptor_proto_goTypes = []interface{}{} +var file_github_com_golang_protobuf_protoc_gen_go_descriptor_descriptor_proto_depIdxs = []int32{ + 0, // [0:0] is the sub-list for method output_type + 0, // [0:0] is the sub-list for method input_type + 0, // [0:0] is the sub-list for extension type_name + 0, // [0:0] is the sub-list for extension extendee + 0, // [0:0] is the sub-list for field type_name +} + +func init() { file_github_com_golang_protobuf_protoc_gen_go_descriptor_descriptor_proto_init() } +func file_github_com_golang_protobuf_protoc_gen_go_descriptor_descriptor_proto_init() { + if File_github_com_golang_protobuf_protoc_gen_go_descriptor_descriptor_proto != nil { + return + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_github_com_golang_protobuf_protoc_gen_go_descriptor_descriptor_proto_rawDesc, + NumEnums: 0, + NumMessages: 0, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_github_com_golang_protobuf_protoc_gen_go_descriptor_descriptor_proto_goTypes, + DependencyIndexes: file_github_com_golang_protobuf_protoc_gen_go_descriptor_descriptor_proto_depIdxs, + }.Build() + File_github_com_golang_protobuf_protoc_gen_go_descriptor_descriptor_proto = out.File + file_github_com_golang_protobuf_protoc_gen_go_descriptor_descriptor_proto_rawDesc = nil + file_github_com_golang_protobuf_protoc_gen_go_descriptor_descriptor_proto_goTypes = nil + file_github_com_golang_protobuf_protoc_gen_go_descriptor_descriptor_proto_depIdxs = nil +} diff --git a/vendor/github.com/golang/protobuf/ptypes/empty/empty.pb.go b/vendor/github.com/golang/protobuf/ptypes/empty/empty.pb.go new file mode 100644 index 00000000000..16686a65523 --- /dev/null +++ b/vendor/github.com/golang/protobuf/ptypes/empty/empty.pb.go @@ -0,0 +1,62 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// source: github.com/golang/protobuf/ptypes/empty/empty.proto + +package empty + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + emptypb "google.golang.org/protobuf/types/known/emptypb" + reflect "reflect" +) + +// Symbols defined in public import of google/protobuf/empty.proto. + +type Empty = emptypb.Empty + +var File_github_com_golang_protobuf_ptypes_empty_empty_proto protoreflect.FileDescriptor + +var file_github_com_golang_protobuf_ptypes_empty_empty_proto_rawDesc = []byte{ + 0x0a, 0x33, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x6f, 0x6c, + 0x61, 0x6e, 0x67, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x70, 0x74, 0x79, + 0x70, 0x65, 0x73, 0x2f, 0x65, 0x6d, 0x70, 0x74, 0x79, 0x2f, 0x65, 0x6d, 0x70, 0x74, 0x79, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1b, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x65, 0x6d, 0x70, 0x74, 0x79, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x42, 0x2f, 0x5a, 0x2d, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, + 0x2f, 0x67, 0x6f, 0x6c, 0x61, 0x6e, 0x67, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, + 0x2f, 0x70, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2f, 0x65, 0x6d, 0x70, 0x74, 0x79, 0x3b, 0x65, 0x6d, + 0x70, 0x74, 0x79, 0x50, 0x00, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var file_github_com_golang_protobuf_ptypes_empty_empty_proto_goTypes = []interface{}{} +var file_github_com_golang_protobuf_ptypes_empty_empty_proto_depIdxs = []int32{ + 0, // [0:0] is the sub-list for method output_type + 0, // [0:0] is the sub-list for method input_type + 0, // [0:0] is the sub-list for extension type_name + 0, // [0:0] is the sub-list for extension extendee + 0, // [0:0] is the sub-list for field type_name +} + +func init() { file_github_com_golang_protobuf_ptypes_empty_empty_proto_init() } +func file_github_com_golang_protobuf_ptypes_empty_empty_proto_init() { + if File_github_com_golang_protobuf_ptypes_empty_empty_proto != nil { + return + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_github_com_golang_protobuf_ptypes_empty_empty_proto_rawDesc, + NumEnums: 0, + NumMessages: 0, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_github_com_golang_protobuf_ptypes_empty_empty_proto_goTypes, + DependencyIndexes: file_github_com_golang_protobuf_ptypes_empty_empty_proto_depIdxs, + }.Build() + File_github_com_golang_protobuf_ptypes_empty_empty_proto = out.File + file_github_com_golang_protobuf_ptypes_empty_empty_proto_rawDesc = nil + file_github_com_golang_protobuf_ptypes_empty_empty_proto_goTypes = nil + file_github_com_golang_protobuf_ptypes_empty_empty_proto_depIdxs = nil +} diff --git a/vendor/github.com/grafana/grafana-plugin-sdk-go/backend/common.go b/vendor/github.com/grafana/grafana-plugin-sdk-go/backend/common.go new file mode 100644 index 00000000000..a9621653971 --- /dev/null +++ b/vendor/github.com/grafana/grafana-plugin-sdk-go/backend/common.go @@ -0,0 +1,106 @@ +package backend + +import ( + "encoding/json" + "time" +) + +// User represents a Grafana user. +type User struct { + Login string + Name string + Email string + Role string +} + +// AppInstanceSettings represents settings for an app instance. +// +// In Grafana an app instance is an app plugin of certain +// type that have been configured and enabled in a Grafana organization. +type AppInstanceSettings struct { + // JSONData repeats the properties at this level of the object (excluding DataSourceConfig), and also includes any + // custom properties associated with the plugin config instance. + JSONData json.RawMessage + + // DecryptedSecureJSONData contains key,value pairs where the encrypted configuration plugin instance in Grafana + // server have been decrypted before passing them to the plugin. + DecryptedSecureJSONData map[string]string + + // Updated is the last time this plugin instance's configuration was updated. + Updated time.Time +} + +// DataSourceInstanceSettings represents settings for a data source instance. +// +// In Grafana a data source instance is a data source plugin of certain +// type that have been configured and created in a Grafana organization. +type DataSourceInstanceSettings struct { + // ID is the Grafana assigned numeric identifier of the the data source instance. + ID int64 + + // UID is the Grafana assigned string identifier of the the data source instance. + UID string + + // Name is the configured name of the data source instance. + Name string + + // URL is the configured URL of a data source instance (e.g. the URL of an API endpoint). + URL string + + // User is a configured user for a data source instance. This is not a Grafana user, rather an arbitrary string. + User string + + // Database is the configured database for a data source instance. (e.g. the default Database a SQL data source would connect to). + Database string + + // BasicAuthEnabled indicates if this data source instance should use basic authentication. + BasicAuthEnabled bool + + // BasicAuthUser is the configured user for basic authentication. (e.g. when a data source uses basic + // authentication to connect to whatever API it fetches data from). + BasicAuthUser string + + // JSONData contains the raw DataSourceConfig as JSON as stored by Grafana server. It repeats the properties in + // this object and includes custom properties. + JSONData json.RawMessage + + // DecryptedSecureJSONData contains key,value pairs where the encrypted configuration in Grafana server have been + // decrypted before passing them to the plugin. + DecryptedSecureJSONData map[string]string + + // Updated is the last time the configuration for the data source instance was updated. + Updated time.Time +} + +// PluginContext holds contextual information about a plugin request, such as +// Grafana organization, user and plugin instance settings. +type PluginContext struct { + // OrgID is The Grafana organization identifier the request originating from. + OrgID int64 + + // PluginID is the unique identifier of the plugin that the request is for. + PluginID string + + // User is the Grafana user making the request. + // + // Will not be provided if Grafana backend initiated the request, + // for example when request is coming from Grafana Alerting. + User *User + + // AppInstanceSettings is the configured app instance settings. + // + // In Grafana an app instance is an app plugin of certain + // type that have been configured and enabled in a Grafana organization. + // + // Will only be set if request targeting an app instance. + AppInstanceSettings *AppInstanceSettings + + // DataSourceConfig is the configured data source instance + // settings. + // + // In Grafana a data source instance is a data source plugin of certain + // type that have been configured and created in a Grafana organization. + // + // Will only be set if request targeting a data source instance. + DataSourceInstanceSettings *DataSourceInstanceSettings +} diff --git a/vendor/github.com/grafana/grafana-plugin-sdk-go/backend/convert_from_protobuf.go b/vendor/github.com/grafana/grafana-plugin-sdk-go/backend/convert_from_protobuf.go new file mode 100644 index 00000000000..2ded4d15f46 --- /dev/null +++ b/vendor/github.com/grafana/grafana-plugin-sdk-go/backend/convert_from_protobuf.go @@ -0,0 +1,204 @@ +package backend + +import ( + "errors" + "time" + + "github.com/grafana/grafana-plugin-sdk-go/data" + "github.com/grafana/grafana-plugin-sdk-go/genproto/pluginv2" +) + +// ConvertFromProtobuf has a collection of methods for converting from the autogenerated +// protobuf go code to our SDK objects. This object exists to attach a collection +// of conversion methods to. +// +// This is used internally by the SDK and inside Grafana-server, plugin authors should not +// need this functionality. +type ConvertFromProtobuf struct{} + +// FromProto returns a new ConvertFromProtobuf. +func FromProto() ConvertFromProtobuf { + return ConvertFromProtobuf{} +} + +// User converts protobuf version of a User to the SDK version. +func (f ConvertFromProtobuf) User(user *pluginv2.User) *User { + if user == nil { + return nil + } + + return &User{ + Login: user.Login, + Name: user.Name, + Email: user.Email, + Role: user.Role, + } +} + +// AppInstanceSettings converts protobuf version of an AppInstanceSettings to the SDK version. +func (f ConvertFromProtobuf) AppInstanceSettings(proto *pluginv2.AppInstanceSettings) *AppInstanceSettings { + if proto == nil { + return nil + } + + return &AppInstanceSettings{ + JSONData: proto.JsonData, + DecryptedSecureJSONData: proto.DecryptedSecureJsonData, + Updated: time.Unix(0, proto.LastUpdatedMS*int64(time.Millisecond)), + } +} + +// DataSourceInstanceSettings converts protobuf version of a DataSourceInstanceSettings to the SDK version. +func (f ConvertFromProtobuf) DataSourceInstanceSettings(proto *pluginv2.DataSourceInstanceSettings) *DataSourceInstanceSettings { + if proto == nil { + return nil + } + + return &DataSourceInstanceSettings{ + ID: proto.Id, + UID: proto.Uid, + Name: proto.Name, + URL: proto.Url, + User: proto.User, + Database: proto.Database, + BasicAuthEnabled: proto.BasicAuthEnabled, + BasicAuthUser: proto.BasicAuthUser, + JSONData: proto.JsonData, + DecryptedSecureJSONData: proto.DecryptedSecureJsonData, + Updated: time.Unix(0, proto.LastUpdatedMS*int64(time.Millisecond)), + } +} + +// PluginContext converts protobuf version of a PluginContext to the SDK version. +func (f ConvertFromProtobuf) PluginContext(proto *pluginv2.PluginContext) PluginContext { + return PluginContext{ + OrgID: proto.OrgId, + PluginID: proto.PluginId, + User: f.User(proto.User), + AppInstanceSettings: f.AppInstanceSettings(proto.AppInstanceSettings), + DataSourceInstanceSettings: f.DataSourceInstanceSettings(proto.DataSourceInstanceSettings), + } +} + +// TimeRange converts protobuf version of a TimeRange to the SDK version. +func (f ConvertFromProtobuf) TimeRange(proto *pluginv2.TimeRange) TimeRange { + return TimeRange{ + From: time.Unix(0, proto.FromEpochMS*int64(time.Millisecond)), + To: time.Unix(0, proto.ToEpochMS*int64(time.Millisecond)), + } +} + +// DataQuery converts protobuf version of a DataQuery to the SDK version. +func (f ConvertFromProtobuf) DataQuery(proto *pluginv2.DataQuery) *DataQuery { + return &DataQuery{ + RefID: proto.RefId, + QueryType: proto.QueryType, + MaxDataPoints: proto.MaxDataPoints, + TimeRange: f.TimeRange(proto.TimeRange), + Interval: time.Duration(proto.IntervalMS) * time.Millisecond, + JSON: proto.Json, + } +} + +// QueryDataRequest converts protobuf version of a QueryDataRequest to the SDK version. +func (f ConvertFromProtobuf) QueryDataRequest(protoReq *pluginv2.QueryDataRequest) *QueryDataRequest { + queries := make([]DataQuery, len(protoReq.Queries)) + for i, q := range protoReq.Queries { + queries[i] = *f.DataQuery(q) + } + + return &QueryDataRequest{ + PluginContext: f.PluginContext(protoReq.PluginContext), + Headers: protoReq.Headers, + Queries: queries, + } +} + +// QueryDataResponse converts protobuf version of a QueryDataResponse to the SDK version. +func (f ConvertFromProtobuf) QueryDataResponse(protoRes *pluginv2.QueryDataResponse) (*QueryDataResponse, error) { + qdr := &QueryDataResponse{ + Responses: make(Responses, len(protoRes.Responses)), + } + for refID, res := range protoRes.Responses { + frames, err := data.UnmarshalArrowFrames(res.Frames) + if err != nil { + return nil, err + } + dr := DataResponse{ + Frames: frames, + } + if res.Error != "" { + dr.Error = errors.New(res.Error) + } + qdr.Responses[refID] = dr + } + return qdr, nil +} + +// CallResourceRequest converts protobuf version of a CallResourceRequest to the SDK version. +func (f ConvertFromProtobuf) CallResourceRequest(protoReq *pluginv2.CallResourceRequest) *CallResourceRequest { + headers := map[string][]string{} + for k, values := range protoReq.Headers { + headers[k] = values.Values + } + + return &CallResourceRequest{ + PluginContext: f.PluginContext(protoReq.PluginContext), + Path: protoReq.Path, + Method: protoReq.Method, + URL: protoReq.Url, + Headers: headers, + Body: protoReq.Body, + } +} + +// CallResourceResponse converts protobuf version of a CallResourceResponse to the SDK version. +func (f ConvertFromProtobuf) CallResourceResponse(protoResp *pluginv2.CallResourceResponse) *CallResourceResponse { + headers := map[string][]string{} + for k, values := range protoResp.Headers { + headers[k] = values.Values + } + + return &CallResourceResponse{ + Status: int(protoResp.Code), + Body: protoResp.Body, + Headers: headers, + } +} + +// CheckHealthRequest converts protobuf version of a CheckHealthRequest to the SDK version. +func (f ConvertFromProtobuf) CheckHealthRequest(protoReq *pluginv2.CheckHealthRequest) *CheckHealthRequest { + return &CheckHealthRequest{ + PluginContext: f.PluginContext(protoReq.PluginContext), + } +} + +// CheckHealthResponse converts protobuf version of a HealthCheckResponse to the SDK version. +func (f ConvertFromProtobuf) CheckHealthResponse(protoResp *pluginv2.CheckHealthResponse) *CheckHealthResult { + status := HealthStatusUnknown + switch protoResp.Status { + case pluginv2.CheckHealthResponse_ERROR: + status = HealthStatusError + case pluginv2.CheckHealthResponse_OK: + status = HealthStatusOk + } + + return &CheckHealthResult{ + Status: status, + Message: protoResp.Message, + JSONDetails: protoResp.JsonDetails, + } +} + +// CollectMetricsResponse converts protobuf version of a CollectMetricsResponse to the SDK version. +func (f ConvertFromProtobuf) CollectMetricsResponse(protoResp *pluginv2.CollectMetricsResponse) *CollectMetricsResult { + var prometheusMetrics []byte + + if protoResp.Metrics != nil { + prometheusMetrics = protoResp.Metrics.Prometheus + } + + return &CollectMetricsResult{ + PrometheusMetrics: prometheusMetrics, + } +} diff --git a/vendor/github.com/grafana/grafana-plugin-sdk-go/backend/convert_to_protobuf.go b/vendor/github.com/grafana/grafana-plugin-sdk-go/backend/convert_to_protobuf.go new file mode 100644 index 00000000000..e5631c53c76 --- /dev/null +++ b/vendor/github.com/grafana/grafana-plugin-sdk-go/backend/convert_to_protobuf.go @@ -0,0 +1,197 @@ +package backend + +import ( + "time" + + "github.com/grafana/grafana-plugin-sdk-go/genproto/pluginv2" +) + +// ConvertToProtobuf has a collection of methods for converting the autogenerated +// protobuf go code to our SDK objects. This object exists to attach a collection +// of conversion methods to. +// +// This is used internally by the SDK and inside Grafana-server, plugin authors should not +// need this functionality. +type ConvertToProtobuf struct{} + +// ToProto returns a new ConvertToProtobuf. +func ToProto() ConvertToProtobuf { + return ConvertToProtobuf{} +} + +// User converts the SDK version of a User to the protobuf version. +func (t ConvertToProtobuf) User(user *User) *pluginv2.User { + if user == nil { + return nil + } + + return &pluginv2.User{ + Login: user.Login, + Name: user.Name, + Email: user.Email, + Role: user.Role, + } +} + +// AppInstanceSettings converts the SDK version of an AppInstanceSettings to the protobuf version. +func (t ConvertToProtobuf) AppInstanceSettings(s *AppInstanceSettings) *pluginv2.AppInstanceSettings { + if s == nil { + return nil + } + + return &pluginv2.AppInstanceSettings{ + JsonData: s.JSONData, + DecryptedSecureJsonData: s.DecryptedSecureJSONData, + LastUpdatedMS: s.Updated.UnixNano() / int64(time.Millisecond), + } +} + +// DataSourceInstanceSettings converts the SDK version of a DataSourceInstanceSettings to the protobuf version. +func (t ConvertToProtobuf) DataSourceInstanceSettings(s *DataSourceInstanceSettings) *pluginv2.DataSourceInstanceSettings { + if s == nil { + return nil + } + + return &pluginv2.DataSourceInstanceSettings{ + Id: s.ID, + Uid: s.UID, + Name: s.Name, + Url: s.URL, + User: s.User, + Database: s.Database, + BasicAuthEnabled: s.BasicAuthEnabled, + BasicAuthUser: s.BasicAuthUser, + JsonData: s.JSONData, + DecryptedSecureJsonData: s.DecryptedSecureJSONData, + LastUpdatedMS: s.Updated.UnixNano() / int64(time.Millisecond), + } +} + +// PluginContext converts the SDK version of a PluginContext to the protobuf version. +func (t ConvertToProtobuf) PluginContext(pluginCtx PluginContext) *pluginv2.PluginContext { + return &pluginv2.PluginContext{ + OrgId: pluginCtx.OrgID, + PluginId: pluginCtx.PluginID, + User: t.User(pluginCtx.User), + AppInstanceSettings: t.AppInstanceSettings(pluginCtx.AppInstanceSettings), + DataSourceInstanceSettings: t.DataSourceInstanceSettings(pluginCtx.DataSourceInstanceSettings), + } +} + +// TimeRange converts the SDK version of a TimeRange to the protobuf version. +func (t ConvertToProtobuf) TimeRange(tr TimeRange) *pluginv2.TimeRange { + return &pluginv2.TimeRange{ + FromEpochMS: tr.From.UnixNano() / int64(time.Millisecond), + ToEpochMS: tr.To.UnixNano() / int64(time.Millisecond), + } +} + +// HealthStatus converts the SDK version of a HealthStatus to the protobuf version. +func (t ConvertToProtobuf) HealthStatus(status HealthStatus) pluginv2.CheckHealthResponse_HealthStatus { + switch status { + case HealthStatusUnknown: + return pluginv2.CheckHealthResponse_UNKNOWN + case HealthStatusOk: + return pluginv2.CheckHealthResponse_OK + case HealthStatusError: + return pluginv2.CheckHealthResponse_ERROR + } + panic("unsupported protobuf health status type in sdk") +} + +// CheckHealthResponse converts the SDK version of a CheckHealthResponse to the protobuf version. +func (t ConvertToProtobuf) CheckHealthResponse(res *CheckHealthResult) *pluginv2.CheckHealthResponse { + return &pluginv2.CheckHealthResponse{ + Status: t.HealthStatus(res.Status), + Message: res.Message, + JsonDetails: res.JSONDetails, + } +} + +// DataQuery converts the SDK version of a DataQuery to the protobuf version. +func (t ConvertToProtobuf) DataQuery(q DataQuery) *pluginv2.DataQuery { + return &pluginv2.DataQuery{ + RefId: q.RefID, + QueryType: q.QueryType, + MaxDataPoints: q.MaxDataPoints, + IntervalMS: q.Interval.Milliseconds(), + TimeRange: t.TimeRange(q.TimeRange), + Json: q.JSON, + } +} + +// QueryDataRequest converts the SDK version of a QueryDataRequest to the protobuf version. +func (t ConvertToProtobuf) QueryDataRequest(req *QueryDataRequest) *pluginv2.QueryDataRequest { + queries := make([]*pluginv2.DataQuery, len(req.Queries)) + for i, q := range req.Queries { + queries[i] = t.DataQuery(q) + } + return &pluginv2.QueryDataRequest{ + PluginContext: t.PluginContext(req.PluginContext), + Headers: req.Headers, + Queries: queries, + } +} + +// QueryDataResponse converts the SDK version of a QueryDataResponse to the protobuf version. +// It will set the RefID on the frames to the RefID key in Responses if a Frame's +// RefId property is an empty string. +func (t ConvertToProtobuf) QueryDataResponse(res *QueryDataResponse) (*pluginv2.QueryDataResponse, error) { + pQDR := &pluginv2.QueryDataResponse{ + Responses: make(map[string]*pluginv2.DataResponse, len(res.Responses)), + } + for refID, dr := range res.Responses { + for _, f := range dr.Frames { + if f.RefID == "" { + f.RefID = refID + } + } + encodedFrames, err := dr.Frames.MarshalArrow() + if err != nil { + return nil, err + } + pDR := pluginv2.DataResponse{ + Frames: encodedFrames, + } + if dr.Error != nil { + pDR.Error = dr.Error.Error() + } + pQDR.Responses[refID] = &pDR + } + + return pQDR, nil +} + +// CallResourceResponse converts the SDK version of a CallResourceResponse to the protobuf version. +func (t ConvertToProtobuf) CallResourceResponse(resp *CallResourceResponse) *pluginv2.CallResourceResponse { + headers := map[string]*pluginv2.StringList{} + + for key, values := range resp.Headers { + headers[key] = &pluginv2.StringList{Values: values} + } + + return &pluginv2.CallResourceResponse{ + Headers: headers, + Code: int32(resp.Status), + Body: resp.Body, + } +} + +// CallResourceRequest converts the SDK version of a CallResourceRequest to the protobuf version. +func (t ConvertToProtobuf) CallResourceRequest(req *CallResourceRequest) *pluginv2.CallResourceRequest { + protoReq := &pluginv2.CallResourceRequest{ + PluginContext: t.PluginContext(req.PluginContext), + Path: req.Path, + Method: req.Method, + Url: req.URL, + Body: req.Body, + } + if req.Headers == nil { + return protoReq + } + protoReq.Headers = make(map[string]*pluginv2.StringList, len(protoReq.Headers)) + for k, values := range req.Headers { + protoReq.Headers[k] = &pluginv2.StringList{Values: values} + } + return protoReq +} diff --git a/vendor/github.com/grafana/grafana-plugin-sdk-go/backend/data.go b/vendor/github.com/grafana/grafana-plugin-sdk-go/backend/data.go new file mode 100644 index 00000000000..02416424f2f --- /dev/null +++ b/vendor/github.com/grafana/grafana-plugin-sdk-go/backend/data.go @@ -0,0 +1,108 @@ +package backend + +import ( + "context" + "encoding/json" + "time" + + "github.com/grafana/grafana-plugin-sdk-go/data" +) + +// QueryDataHandler handles data queries. +type QueryDataHandler interface { + // QueryData handles multiple queries and returns multiple responses. + // req contains the queries []DataQuery (where each query contains RefID as a unique identifier). + // The QueryDataResponse contains a map of RefID to the response for each query, and each response + // contains Frames ([]*Frame). + // + // The Frames' RefID property, when it is an empty string, will be automatically set to + // the RefID in QueryDataResponse.Responses map. This is done before the QueryDataResponse is + // sent to Grafana. Therefore one does not need to be set that property on frames when using this method. + QueryData(ctx context.Context, req *QueryDataRequest) (*QueryDataResponse, error) +} + +// QueryDataHandlerFunc is an adapter to allow the use of +// ordinary functions as backend.QueryDataHandler. If f is a function +// with the appropriate signature, QueryDataHandlerFunc(f) is a +// Handler that calls f. +type QueryDataHandlerFunc func(ctx context.Context, req *QueryDataRequest) (*QueryDataResponse, error) + +// QueryData calls fn(ctx, req). +func (fn QueryDataHandlerFunc) QueryData(ctx context.Context, req *QueryDataRequest) (*QueryDataResponse, error) { + return fn(ctx, req) +} + +// QueryDataRequest contains a single request which contains multiple queries. +// It is the input type for a QueryData call. +type QueryDataRequest struct { + PluginContext PluginContext + Headers map[string]string + Queries []DataQuery +} + +// DataQuery represents a single query as sent from the frontend. +// A slice of DataQuery makes up the Queries property of a QueryDataRequest. +type DataQuery struct { + // RefID is the unique identifier of the query, set by the frontend call. + RefID string + + // QueryType is an optional identifier for the type of query. + // It can be used to distinguish different types of queries. + QueryType string + + // MaxDataPoints is the maximum number of datapoints that should be returned from a time series query. + MaxDataPoints int64 + + // Interval is the suggested duration between time points in a time series query. + Interval time.Duration + + // TimeRange is the Start and End of the query as sent by the frontend. + TimeRange TimeRange + + // JSON is the raw JSON query and includes the above properties as well as custom properties. + JSON json.RawMessage +} + +// QueryDataResponse contains the results from a QueryDataRequest. +// It is the return type of a QueryData call. +type QueryDataResponse struct { + // Responses is a map of RefIDs (Unique Query ID) to *DataResponse. + Responses Responses +} + +// NewQueryDataResponse returns a QueryDataResponse with the Responses property initialized. +func NewQueryDataResponse() *QueryDataResponse { + return &QueryDataResponse{ + Responses: make(Responses), + } +} + +// Responses is a map of RefIDs (Unique Query ID) to DataResponses. +// The QueryData method the QueryDataHandler method will set the RefId +// property on the DataRespones' frames based on these RefIDs. +type Responses map[string]DataResponse + +// DataResponse contains the results from a DataQuery. +// A map of RefIDs (unique query identifers) to this type makes up the Responses property of a QueryDataResponse. +// The Error property is used to allow for partial success responses from the containing QueryDataResponse. +type DataResponse struct { + // The data returned from the Query. Each Frame repeats the RefID. + Frames data.Frames + + // Error is a property to be set if the the corresponding DataQuery has an error. + Error error +} + +// TimeRange represents a time range for a query and is a property of DataQuery. +type TimeRange struct { + // From is the start time of the query. + From time.Time + + // To is the end time of the query. + To time.Time +} + +// Duration returns a time.Duration representing the amount of time between From and To. +func (tr TimeRange) Duration() time.Duration { + return tr.To.Sub(tr.From) +} diff --git a/vendor/github.com/grafana/grafana-plugin-sdk-go/backend/data_adapter.go b/vendor/github.com/grafana/grafana-plugin-sdk-go/backend/data_adapter.go new file mode 100644 index 00000000000..e1383a3b1e1 --- /dev/null +++ b/vendor/github.com/grafana/grafana-plugin-sdk-go/backend/data_adapter.go @@ -0,0 +1,27 @@ +package backend + +import ( + "context" + + "github.com/grafana/grafana-plugin-sdk-go/genproto/pluginv2" +) + +// dataSDKAdapter adapter between low level plugin protocol and SDK interfaces. +type dataSDKAdapter struct { + queryDataHandler QueryDataHandler +} + +func newDataSDKAdapter(handler QueryDataHandler) *dataSDKAdapter { + return &dataSDKAdapter{ + queryDataHandler: handler, + } +} + +func (a *dataSDKAdapter) QueryData(ctx context.Context, req *pluginv2.QueryDataRequest) (*pluginv2.QueryDataResponse, error) { + resp, err := a.queryDataHandler.QueryData(ctx, FromProto().QueryDataRequest(req)) + if err != nil { + return nil, err + } + + return ToProto().QueryDataResponse(resp) +} diff --git a/vendor/github.com/grafana/grafana-plugin-sdk-go/backend/diagnostics.go b/vendor/github.com/grafana/grafana-plugin-sdk-go/backend/diagnostics.go new file mode 100644 index 00000000000..289a25ed6f3 --- /dev/null +++ b/vendor/github.com/grafana/grafana-plugin-sdk-go/backend/diagnostics.go @@ -0,0 +1,85 @@ +package backend + +import ( + "context" + "strconv" +) + +// CheckHealthHandler enables users to send health check +// requests to a backend plugin +type CheckHealthHandler interface { + CheckHealth(ctx context.Context, req *CheckHealthRequest) (*CheckHealthResult, error) +} + +// CheckHealthHandlerFunc is an adapter to allow the use of +// ordinary functions as backend.CheckHealthHandler. If f is a function +// with the appropriate signature, CheckHealthHandlerFunc(f) is a +// Handler that calls f. +type CheckHealthHandlerFunc func(ctx context.Context, req *CheckHealthRequest) (*CheckHealthResult, error) + +// CheckHealth calls fn(ctx, req). +func (fn CheckHealthHandlerFunc) CheckHealth(ctx context.Context, req *CheckHealthRequest) (*CheckHealthResult, error) { + return fn(ctx, req) +} + +// HealthStatus is the status of the plugin. +type HealthStatus int + +const ( + // HealthStatusUnknown means the status of the plugin is unknown. + HealthStatusUnknown HealthStatus = iota + + // HealthStatusOk means the status of the plugin is good. + HealthStatusOk + + // HealthStatusError means the plugin is in an error state. + HealthStatusError +) + +var healthStatusNames = map[int]string{ + 0: "UNKNOWN", + 1: "OK", + 2: "ERROR", +} + +// String textual represntation of the status. +func (hs HealthStatus) String() string { + s, exists := healthStatusNames[int(hs)] + if exists { + return s + } + return strconv.Itoa(int(hs)) +} + +// CheckHealthRequest contains the healthcheck request +type CheckHealthRequest struct { + PluginContext PluginContext +} + +// CheckHealthResult contains the healthcheck response +type CheckHealthResult struct { + Status HealthStatus + Message string + JSONDetails []byte +} + +// CollectMetricsHandler handles metric collection. +type CollectMetricsHandler interface { + CollectMetrics(ctx context.Context) (*CollectMetricsResult, error) +} + +// CollectMetricsHandlerFunc is an adapter to allow the use of +// ordinary functions as backend.CollectMetricsHandler. If f is a function +// with the appropriate signature, CollectMetricsHandlerFunc(f) is a +// Handler that calls f. +type CollectMetricsHandlerFunc func(ctx context.Context) (*CollectMetricsResult, error) + +// CollectMetrics calls fn(ctx, req). +func (fn CollectMetricsHandlerFunc) CollectMetrics(ctx context.Context) (*CollectMetricsResult, error) { + return fn(ctx) +} + +// CollectMetricsResult collect metrics result. +type CollectMetricsResult struct { + PrometheusMetrics []byte +} diff --git a/vendor/github.com/grafana/grafana-plugin-sdk-go/backend/diagnostics_adapter.go b/vendor/github.com/grafana/grafana-plugin-sdk-go/backend/diagnostics_adapter.go new file mode 100644 index 00000000000..b5864a89416 --- /dev/null +++ b/vendor/github.com/grafana/grafana-plugin-sdk-go/backend/diagnostics_adapter.go @@ -0,0 +1,58 @@ +package backend + +import ( + "bytes" + "context" + + "github.com/grafana/grafana-plugin-sdk-go/genproto/pluginv2" + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/common/expfmt" +) + +// diagnosticsSDKAdapter adapter between low level plugin protocol and SDK interfaces. +type diagnosticsSDKAdapter struct { + metricGatherer prometheus.Gatherer + checkHealthHandler CheckHealthHandler +} + +func newDiagnosticsSDKAdapter(metricGatherer prometheus.Gatherer, checkHealthHandler CheckHealthHandler) *diagnosticsSDKAdapter { + return &diagnosticsSDKAdapter{ + metricGatherer: metricGatherer, + checkHealthHandler: checkHealthHandler, + } +} + +func (a *diagnosticsSDKAdapter) CollectMetrics(ctx context.Context, protoReq *pluginv2.CollectMetricsRequest) (*pluginv2.CollectMetricsResponse, error) { + mfs, err := a.metricGatherer.Gather() + if err != nil { + return nil, err + } + + var buf bytes.Buffer + for _, mf := range mfs { + _, err := expfmt.MetricFamilyToText(&buf, mf) + if err != nil { + return nil, err + } + } + + return &pluginv2.CollectMetricsResponse{ + Metrics: &pluginv2.CollectMetricsResponse_Payload{ + Prometheus: buf.Bytes(), + }, + }, nil +} + +func (a *diagnosticsSDKAdapter) CheckHealth(ctx context.Context, protoReq *pluginv2.CheckHealthRequest) (*pluginv2.CheckHealthResponse, error) { + if a.checkHealthHandler != nil { + res, err := a.checkHealthHandler.CheckHealth(ctx, FromProto().CheckHealthRequest(protoReq)) + if err != nil { + return nil, err + } + return ToProto().CheckHealthResponse(res), nil + } + + return &pluginv2.CheckHealthResponse{ + Status: pluginv2.CheckHealthResponse_OK, + }, nil +} diff --git a/vendor/github.com/grafana/grafana-plugin-sdk-go/backend/doc.go b/vendor/github.com/grafana/grafana-plugin-sdk-go/backend/doc.go new file mode 100644 index 00000000000..6d44b453830 --- /dev/null +++ b/vendor/github.com/grafana/grafana-plugin-sdk-go/backend/doc.go @@ -0,0 +1,2 @@ +// Package backend provides SDK handler interfaces and contracts for implementing and serving backend plugins. +package backend diff --git a/vendor/github.com/grafana/grafana-plugin-sdk-go/backend/framer.go b/vendor/github.com/grafana/grafana-plugin-sdk-go/backend/framer.go new file mode 100644 index 00000000000..e8671387c09 --- /dev/null +++ b/vendor/github.com/grafana/grafana-plugin-sdk-go/backend/framer.go @@ -0,0 +1,24 @@ +package backend + +import "github.com/grafana/grafana-plugin-sdk-go/data" + +// FrameResponse creates a DataResponse that contains the Framer's data.Frames. +func FrameResponse(f data.Framer) *DataResponse { + frames, err := f.Frames() + return &DataResponse{ + Frames: frames, + Error: err, + } +} + +// FrameResponseWithError creates a DataResponse with the error's contents (if not nil), and the Framer's data.Frames. +// This function is particularly useful if you have a function that returns `(StructX, error)`, where StructX implements Framer, which is a very common pattern. +func FrameResponseWithError(f data.Framer, err error) *DataResponse { + if err != nil { + return &DataResponse{ + Error: err, + } + } + + return FrameResponse(f) +} diff --git a/vendor/github.com/grafana/grafana-plugin-sdk-go/backend/grpcplugin/doc.go b/vendor/github.com/grafana/grafana-plugin-sdk-go/backend/grpcplugin/doc.go new file mode 100644 index 00000000000..449bdbffe38 --- /dev/null +++ b/vendor/github.com/grafana/grafana-plugin-sdk-go/backend/grpcplugin/doc.go @@ -0,0 +1,5 @@ +// Package grpcplugin provides support for serving plugin over gRPC. +// +// This package is internal and meant to be used internally by the +// SDK and Grafana. +package grpcplugin diff --git a/vendor/github.com/grafana/grafana-plugin-sdk-go/backend/grpcplugin/grpc_data.go b/vendor/github.com/grafana/grafana-plugin-sdk-go/backend/grpcplugin/grpc_data.go new file mode 100644 index 00000000000..2383d7706ac --- /dev/null +++ b/vendor/github.com/grafana/grafana-plugin-sdk-go/backend/grpcplugin/grpc_data.go @@ -0,0 +1,60 @@ +package grpcplugin + +import ( + "context" + + "github.com/grafana/grafana-plugin-sdk-go/genproto/pluginv2" + plugin "github.com/hashicorp/go-plugin" + "google.golang.org/grpc" +) + +// DataServer represents a data server. +type DataServer interface { + pluginv2.DataServer +} + +// DataClient represents a data client. +type DataClient interface { + pluginv2.DataClient +} + +// DataGRPCPlugin implements the GRPCPlugin interface from github.com/hashicorp/go-plugin. +type DataGRPCPlugin struct { + plugin.NetRPCUnsupportedPlugin + plugin.GRPCPlugin + DataServer DataServer +} + +// GRPCServer registers p as a data gRPC server. +func (p *DataGRPCPlugin) GRPCServer(broker *plugin.GRPCBroker, s *grpc.Server) error { + pluginv2.RegisterDataServer(s, &dataGRPCServer{ + server: p.DataServer, + }) + return nil +} + +// GRPCClient returns c as a data gRPC client. +func (p *DataGRPCPlugin) GRPCClient(ctx context.Context, broker *plugin.GRPCBroker, c *grpc.ClientConn) (interface{}, error) { + return &dataGRPCClient{client: pluginv2.NewDataClient(c)}, nil +} + +type dataGRPCServer struct { + server DataServer +} + +// QueryData queries s for data. +func (s *dataGRPCServer) QueryData(ctx context.Context, req *pluginv2.QueryDataRequest) (*pluginv2.QueryDataResponse, error) { + return s.server.QueryData(ctx, req) +} + +type dataGRPCClient struct { + client pluginv2.DataClient +} + +// QueryData queries m for data. +func (m *dataGRPCClient) QueryData(ctx context.Context, req *pluginv2.QueryDataRequest, opts ...grpc.CallOption) (*pluginv2.QueryDataResponse, error) { + return m.client.QueryData(ctx, req, opts...) +} + +var _ DataServer = &dataGRPCServer{} +var _ DataClient = &dataGRPCClient{} diff --git a/vendor/github.com/grafana/grafana-plugin-sdk-go/backend/grpcplugin/grpc_diagnostics.go b/vendor/github.com/grafana/grafana-plugin-sdk-go/backend/grpcplugin/grpc_diagnostics.go new file mode 100644 index 00000000000..b883a092456 --- /dev/null +++ b/vendor/github.com/grafana/grafana-plugin-sdk-go/backend/grpcplugin/grpc_diagnostics.go @@ -0,0 +1,70 @@ +package grpcplugin + +import ( + "context" + + "github.com/grafana/grafana-plugin-sdk-go/genproto/pluginv2" + plugin "github.com/hashicorp/go-plugin" + "google.golang.org/grpc" +) + +// DiagnosticsServer is the server API for the Diagnostics service. +type DiagnosticsServer interface { + pluginv2.DiagnosticsServer +} + +// DiagnosticsClient is the client API for the Diagnostics service. +type DiagnosticsClient interface { + pluginv2.DiagnosticsClient +} + +// DiagnosticsGRPCPlugin implements the GRPCPlugin interface from github.com/hashicorp/go-plugin. +type DiagnosticsGRPCPlugin struct { + plugin.NetRPCUnsupportedPlugin + plugin.GRPCPlugin + DiagnosticsServer DiagnosticsServer +} + +// GRPCServer registers p as a diagnostics gRPC server. +func (p *DiagnosticsGRPCPlugin) GRPCServer(broker *plugin.GRPCBroker, s *grpc.Server) error { + pluginv2.RegisterDiagnosticsServer(s, &diagnosticsGRPCServer{ + server: p.DiagnosticsServer, + }) + return nil +} + +// GRPCClient returns c as a diagnostics gRPC client. +func (p *DiagnosticsGRPCPlugin) GRPCClient(ctx context.Context, broker *plugin.GRPCBroker, c *grpc.ClientConn) (interface{}, error) { + return &diagnosticsGRPCClient{client: pluginv2.NewDiagnosticsClient(c)}, nil +} + +type diagnosticsGRPCServer struct { + server DiagnosticsServer +} + +// CollectMetrics collects metrics. +func (s *diagnosticsGRPCServer) CollectMetrics(ctx context.Context, req *pluginv2.CollectMetricsRequest) (*pluginv2.CollectMetricsResponse, error) { + return s.server.CollectMetrics(ctx, req) +} + +// CheckHealth checks health. +func (s *diagnosticsGRPCServer) CheckHealth(ctx context.Context, req *pluginv2.CheckHealthRequest) (*pluginv2.CheckHealthResponse, error) { + return s.server.CheckHealth(ctx, req) +} + +type diagnosticsGRPCClient struct { + client pluginv2.DiagnosticsClient +} + +// CollectMetrics collects metrics. +func (s *diagnosticsGRPCClient) CollectMetrics(ctx context.Context, req *pluginv2.CollectMetricsRequest, opts ...grpc.CallOption) (*pluginv2.CollectMetricsResponse, error) { + return s.client.CollectMetrics(ctx, req, opts...) +} + +// CheckHealth checks health. +func (s *diagnosticsGRPCClient) CheckHealth(ctx context.Context, req *pluginv2.CheckHealthRequest, opts ...grpc.CallOption) (*pluginv2.CheckHealthResponse, error) { + return s.client.CheckHealth(ctx, req, opts...) +} + +var _ DiagnosticsServer = &diagnosticsGRPCServer{} +var _ DiagnosticsClient = &diagnosticsGRPCClient{} diff --git a/vendor/github.com/grafana/grafana-plugin-sdk-go/backend/grpcplugin/grpc_resource.go b/vendor/github.com/grafana/grafana-plugin-sdk-go/backend/grpcplugin/grpc_resource.go new file mode 100644 index 00000000000..20491000613 --- /dev/null +++ b/vendor/github.com/grafana/grafana-plugin-sdk-go/backend/grpcplugin/grpc_resource.go @@ -0,0 +1,60 @@ +package grpcplugin + +import ( + "context" + + "github.com/grafana/grafana-plugin-sdk-go/genproto/pluginv2" + plugin "github.com/hashicorp/go-plugin" + "google.golang.org/grpc" +) + +// ResourceServer is the server API for the Resource service. +type ResourceServer interface { + pluginv2.ResourceServer +} + +// ResourceClient is the client API for the Resource service. +type ResourceClient interface { + pluginv2.ResourceClient +} + +// ResourceGRPCPlugin implements the GRPCPlugin interface from github.com/hashicorp/go-plugin. +type ResourceGRPCPlugin struct { + plugin.NetRPCUnsupportedPlugin + plugin.GRPCPlugin + ResourceServer ResourceServer +} + +// GRPCServer registers p as a resource gRPC server. +func (p *ResourceGRPCPlugin) GRPCServer(broker *plugin.GRPCBroker, s *grpc.Server) error { + pluginv2.RegisterResourceServer(s, &resourceGRPCServer{ + server: p.ResourceServer, + }) + return nil +} + +// GRPCClient returns c as a resource gRPC client. +func (p *ResourceGRPCPlugin) GRPCClient(ctx context.Context, broker *plugin.GRPCBroker, c *grpc.ClientConn) (interface{}, error) { + return &resourceGRPCClient{client: pluginv2.NewResourceClient(c)}, nil +} + +type resourceGRPCServer struct { + server ResourceServer +} + +// CallResource calls a resource. +func (s *resourceGRPCServer) CallResource(req *pluginv2.CallResourceRequest, srv pluginv2.Resource_CallResourceServer) error { + return s.server.CallResource(req, srv) +} + +type resourceGRPCClient struct { + client pluginv2.ResourceClient +} + +// CallResource calls a resource. +func (m *resourceGRPCClient) CallResource(ctx context.Context, req *pluginv2.CallResourceRequest, opts ...grpc.CallOption) (pluginv2.Resource_CallResourceClient, error) { + return m.client.CallResource(ctx, req, opts...) +} + +var _ ResourceServer = &resourceGRPCServer{} +var _ ResourceClient = &resourceGRPCClient{} diff --git a/vendor/github.com/grafana/grafana-plugin-sdk-go/backend/grpcplugin/handshake.go b/vendor/github.com/grafana/grafana-plugin-sdk-go/backend/grpcplugin/handshake.go new file mode 100644 index 00000000000..d9823708a5b --- /dev/null +++ b/vendor/github.com/grafana/grafana-plugin-sdk-go/backend/grpcplugin/handshake.go @@ -0,0 +1,27 @@ +package grpcplugin + +import ( + plugin "github.com/hashicorp/go-plugin" +) + +const ( + // ProtocolVersion is the current (latest) supported protocol version. + ProtocolVersion = 2 + + // MagicCookieKey is the the magic cookie key that will be used for negotiating + // between plugin host and client. + // Should NEVER be changed. + MagicCookieKey = "grafana_plugin_type" + + // MagicCookieValue is the the magic cookie value that will be used for negotiating + // between plugin host and client. + // Should NEVER be changed. + MagicCookieValue = "datasource" +) + +// handshake is the HandshakeConfig used to configure clients and servers. +var handshake = plugin.HandshakeConfig{ + ProtocolVersion: ProtocolVersion, + MagicCookieKey: MagicCookieKey, + MagicCookieValue: MagicCookieValue, +} diff --git a/vendor/github.com/grafana/grafana-plugin-sdk-go/backend/grpcplugin/serve.go b/vendor/github.com/grafana/grafana-plugin-sdk-go/backend/grpcplugin/serve.go new file mode 100644 index 00000000000..047d74e85bf --- /dev/null +++ b/vendor/github.com/grafana/grafana-plugin-sdk-go/backend/grpcplugin/serve.go @@ -0,0 +1,62 @@ +package grpcplugin + +import ( + "github.com/grafana/grafana-plugin-sdk-go/backend/log" + plugin "github.com/hashicorp/go-plugin" + "google.golang.org/grpc" +) + +// ServeOpts contains options for serving plugins. +type ServeOpts struct { + DiagnosticsServer DiagnosticsServer + ResourceServer ResourceServer + DataServer DataServer + + // GRPCServer factory method for creating GRPC server. + // If nil, the default one will be used. + GRPCServer func(options []grpc.ServerOption) *grpc.Server +} + +// Serve starts serving the plugin over gRPC. +func Serve(opts ServeOpts) error { + versionedPlugins := make(map[int]plugin.PluginSet) + pSet := make(plugin.PluginSet) + + if opts.DiagnosticsServer != nil { + pSet["diagnostics"] = &DiagnosticsGRPCPlugin{ + DiagnosticsServer: opts.DiagnosticsServer, + } + } + + if opts.ResourceServer != nil { + pSet["resource"] = &ResourceGRPCPlugin{ + ResourceServer: opts.ResourceServer, + } + } + + if opts.DataServer != nil { + pSet["data"] = &DataGRPCPlugin{ + DataServer: opts.DataServer, + } + } + + versionedPlugins[ProtocolVersion] = pSet + + if opts.GRPCServer == nil { + opts.GRPCServer = plugin.DefaultGRPCServer + } + + plugKeys := []string{} + for k := range pSet { + plugKeys = append(plugKeys, k) + } + log.DefaultLogger.Debug("Serving plugin", "plugins", plugKeys) + plugin.Serve(&plugin.ServeConfig{ + HandshakeConfig: handshake, + VersionedPlugins: versionedPlugins, + GRPCServer: opts.GRPCServer, + }) + log.DefaultLogger.Debug("Plugin server exited") + + return nil +} diff --git a/vendor/github.com/grafana/grafana-plugin-sdk-go/backend/log.go b/vendor/github.com/grafana/grafana-plugin-sdk-go/backend/log.go new file mode 100644 index 00000000000..1b3268345f0 --- /dev/null +++ b/vendor/github.com/grafana/grafana-plugin-sdk-go/backend/log.go @@ -0,0 +1,9 @@ +package backend + +import ( + "github.com/grafana/grafana-plugin-sdk-go/backend/log" +) + +// Logger is the default logger instance. This can be used directly to log from +// your plugin to grafana-server with calls like backend.Logger.Debug(...). +var Logger log.Logger = log.DefaultLogger diff --git a/vendor/github.com/grafana/grafana-plugin-sdk-go/backend/log/log.go b/vendor/github.com/grafana/grafana-plugin-sdk-go/backend/log/log.go new file mode 100644 index 00000000000..781759155ab --- /dev/null +++ b/vendor/github.com/grafana/grafana-plugin-sdk-go/backend/log/log.go @@ -0,0 +1,50 @@ +// Package log provides a logging interface to send logs from plugins to Grafana server. +package log + +import ( + hclog "github.com/hashicorp/go-hclog" +) + +// Logger is the main Logger interface. +type Logger interface { + Debug(msg string, args ...interface{}) + Info(msg string, args ...interface{}) + Warn(msg string, args ...interface{}) + Error(msg string, args ...interface{}) +} + +// New creates a new logger. +func New() Logger { + return &hclogWrapper{ + logger: hclog.New(&hclog.LoggerOptions{ + // Use debug as level since anything less severe is suppressed. + Level: hclog.Debug, + // Use JSON format to make the output in Grafana format and work + // when using multiple arguments such as Debug("message", "key", "value"). + JSONFormat: true, + }), + } +} + +type hclogWrapper struct { + logger hclog.Logger +} + +func (l *hclogWrapper) Debug(msg string, args ...interface{}) { + l.logger.Debug(msg, args...) +} + +func (l *hclogWrapper) Info(msg string, args ...interface{}) { + l.logger.Info(msg, args...) +} + +func (l *hclogWrapper) Warn(msg string, args ...interface{}) { + l.logger.Warn(msg, args...) +} + +func (l *hclogWrapper) Error(msg string, args ...interface{}) { + l.logger.Error(msg, args...) +} + +// DefaultLogger is the default logger. +var DefaultLogger = New() diff --git a/vendor/github.com/grafana/grafana-plugin-sdk-go/backend/resource.go b/vendor/github.com/grafana/grafana-plugin-sdk-go/backend/resource.go new file mode 100644 index 00000000000..248f91670bb --- /dev/null +++ b/vendor/github.com/grafana/grafana-plugin-sdk-go/backend/resource.go @@ -0,0 +1,43 @@ +package backend + +import ( + "context" +) + +// CallResourceRequest represents a request for a resource call. +type CallResourceRequest struct { + PluginContext PluginContext + Path string + Method string + URL string + Headers map[string][]string + Body []byte +} + +// CallResourceResponse represents a response from a resource call. +type CallResourceResponse struct { + Status int + Headers map[string][]string + Body []byte +} + +// CallResourceResponseSender is used for sending resource call responses. +type CallResourceResponseSender interface { + Send(*CallResourceResponse) error +} + +// CallResourceHandler handles resource calls. +type CallResourceHandler interface { + CallResource(ctx context.Context, req *CallResourceRequest, sender CallResourceResponseSender) error +} + +// CallResourceHandlerFunc is an adapter to allow the use of +// ordinary functions as backend.CallResourceHandler. If f is a function +// with the appropriate signature, CallResourceHandlerFunc(f) is a +// Handler that calls f. +type CallResourceHandlerFunc func(ctx context.Context, req *CallResourceRequest, sender CallResourceResponseSender) error + +// CallResource calls fn(ctx, req, sender). +func (fn CallResourceHandlerFunc) CallResource(ctx context.Context, req *CallResourceRequest, sender CallResourceResponseSender) error { + return fn(ctx, req, sender) +} diff --git a/vendor/github.com/grafana/grafana-plugin-sdk-go/backend/resource_adapter.go b/vendor/github.com/grafana/grafana-plugin-sdk-go/backend/resource_adapter.go new file mode 100644 index 00000000000..10cc1fc49ee --- /dev/null +++ b/vendor/github.com/grafana/grafana-plugin-sdk-go/backend/resource_adapter.go @@ -0,0 +1,38 @@ +package backend + +import ( + "net/http" + + "github.com/grafana/grafana-plugin-sdk-go/genproto/pluginv2" +) + +// resourceSDKAdapter adapter between low level plugin protocol and SDK interfaces. +type resourceSDKAdapter struct { + callResourceHandler CallResourceHandler +} + +func newResourceSDKAdapter(handler CallResourceHandler) *resourceSDKAdapter { + return &resourceSDKAdapter{ + callResourceHandler: handler, + } +} + +type callResourceResponseSenderFunc func(resp *CallResourceResponse) error + +func (fn callResourceResponseSenderFunc) Send(resp *CallResourceResponse) error { + return fn(resp) +} + +func (a *resourceSDKAdapter) CallResource(protoReq *pluginv2.CallResourceRequest, protoSrv pluginv2.Resource_CallResourceServer) error { + if a.callResourceHandler == nil { + return protoSrv.Send(&pluginv2.CallResourceResponse{ + Code: http.StatusNotImplemented, + }) + } + + fn := callResourceResponseSenderFunc(func(resp *CallResourceResponse) error { + return protoSrv.Send(ToProto().CallResourceResponse(resp)) + }) + + return a.callResourceHandler.CallResource(protoSrv.Context(), FromProto().CallResourceRequest(protoReq), fn) +} diff --git a/vendor/github.com/grafana/grafana-plugin-sdk-go/backend/serve.go b/vendor/github.com/grafana/grafana-plugin-sdk-go/backend/serve.go new file mode 100644 index 00000000000..d2c98d97939 --- /dev/null +++ b/vendor/github.com/grafana/grafana-plugin-sdk-go/backend/serve.go @@ -0,0 +1,81 @@ +package backend + +import ( + "github.com/grafana/grafana-plugin-sdk-go/backend/grpcplugin" + grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware" + grpc_prometheus "github.com/grpc-ecosystem/go-grpc-prometheus" + "github.com/prometheus/client_golang/prometheus" + "google.golang.org/grpc" +) + +const defaultServerMaxReceiveMessageSize = 1024 * 1024 * 16 + +// GRPCSettings settings for gRPC. +type GRPCSettings struct { + // MaxReceiveMsgSize the max gRPC message size in bytes the plugin can receive. + // If this is <= 0, gRPC uses the default 16MB. + MaxReceiveMsgSize int + + // MaxSendMsgSize the max gRPC message size in bytes the plugin can send. + // If this is <= 0, gRPC uses the default `math.MaxInt32`. + MaxSendMsgSize int +} + +// ServeOpts options for serving plugins. +type ServeOpts struct { + // CheckHealthHandler handler for health checks. + CheckHealthHandler CheckHealthHandler + + // CallResourceHandler handler for resource calls. + // Optional to implement. + CallResourceHandler CallResourceHandler + + // QueryDataHandler handler for data queries. + // Required to implement if data source. + QueryDataHandler QueryDataHandler + + // GRPCSettings settings for gPRC. + GRPCSettings GRPCSettings +} + +// Serve starts serving the plugin over gRPC. +func Serve(opts ServeOpts) error { + pluginOpts := grpcplugin.ServeOpts{ + DiagnosticsServer: newDiagnosticsSDKAdapter(prometheus.DefaultGatherer, opts.CheckHealthHandler), + } + + if opts.CallResourceHandler != nil { + pluginOpts.ResourceServer = newResourceSDKAdapter(opts.CallResourceHandler) + } + + if opts.QueryDataHandler != nil { + pluginOpts.DataServer = newDataSDKAdapter(opts.QueryDataHandler) + } + + grpc_prometheus.EnableHandlingTimeHistogram() + grpcMiddlewares := []grpc.ServerOption{ + grpc.StreamInterceptor(grpc_middleware.ChainStreamServer( + grpc_prometheus.StreamServerInterceptor, + )), + grpc.UnaryInterceptor(grpc_middleware.ChainUnaryServer( + grpc_prometheus.UnaryServerInterceptor, + )), + } + + if opts.GRPCSettings.MaxReceiveMsgSize <= 0 { + opts.GRPCSettings.MaxReceiveMsgSize = defaultServerMaxReceiveMessageSize + } + + grpcMiddlewares = append([]grpc.ServerOption{grpc.MaxRecvMsgSize(opts.GRPCSettings.MaxReceiveMsgSize)}, grpcMiddlewares...) + + if opts.GRPCSettings.MaxSendMsgSize > 0 { + grpcMiddlewares = append([]grpc.ServerOption{grpc.MaxSendMsgSize(opts.GRPCSettings.MaxSendMsgSize)}, grpcMiddlewares...) + } + + pluginOpts.GRPCServer = func(opts []grpc.ServerOption) *grpc.Server { + opts = append(opts, grpcMiddlewares...) + return grpc.NewServer(opts...) + } + + return grpcplugin.Serve(pluginOpts) +} diff --git a/vendor/github.com/grafana/grafana-plugin-sdk-go/backend/setup.go b/vendor/github.com/grafana/grafana-plugin-sdk-go/backend/setup.go new file mode 100644 index 00000000000..5e84f51ced7 --- /dev/null +++ b/vendor/github.com/grafana/grafana-plugin-sdk-go/backend/setup.go @@ -0,0 +1,56 @@ +package backend + +import ( + "fmt" + "net/http" + "net/http/pprof" + "os" +) + +const ( + // PluginProfilerEnv is a constant for the GF_PLUGINS_PROFILER environment variable used to enable pprof. + PluginProfilerEnv = "GF_PLUGINS_PROFILER" + + // PluginProfilerPortEnv is a constant for the GF_PLUGINS_PROFILER_PORT environment variable use to specify a pprof port (default 6060). + PluginProfilerPortEnv = "GF_PLUGINS_PROFILER_PORT" +) + +// SetupPluginEnvironment will read the environment variables and apply the +// standard environment behavior. +// +// As the SDK evolves, this will likely change. +// +// Currently this function enables and configures profiling with pprof. +func SetupPluginEnvironment(pluginID string) { + // Enable profiler + profilerEnabled := false + if value, ok := os.LookupEnv(PluginProfilerEnv); ok { + // compare value to plugin name + if value == pluginID { + profilerEnabled = true + } + } + Logger.Info("Profiler", "enabled", profilerEnabled) + if profilerEnabled { + profilerPort := "6060" + if value, ok := os.LookupEnv(PluginProfilerPortEnv); ok { + profilerPort = value + } + + Logger.Info("Profiler", "port", profilerPort) + portConfig := fmt.Sprintf(":%s", profilerPort) + + r := http.NewServeMux() + r.HandleFunc("/debug/pprof/", pprof.Index) + r.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline) + r.HandleFunc("/debug/pprof/profile", pprof.Profile) + r.HandleFunc("/debug/pprof/symbol", pprof.Symbol) + r.HandleFunc("/debug/pprof/trace", pprof.Trace) + + go func() { + if err := http.ListenAndServe(portConfig, r); err != nil { + Logger.Error("Error Running profiler: %s", err.Error()) + } + }() + } +} diff --git a/vendor/github.com/grafana/grafana-plugin-sdk-go/genproto/pluginv2/backend.pb.go b/vendor/github.com/grafana/grafana-plugin-sdk-go/genproto/pluginv2/backend.pb.go new file mode 100644 index 00000000000..7eff21d506c --- /dev/null +++ b/vendor/github.com/grafana/grafana-plugin-sdk-go/genproto/pluginv2/backend.pb.go @@ -0,0 +1,1729 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.25.0 +// protoc v3.14.0 +// source: backend.proto + +package pluginv2 + +import ( + proto "github.com/golang/protobuf/proto" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +// This is a compile-time assertion that a sufficiently up-to-date version +// of the legacy proto package is being used. +const _ = proto.ProtoPackageIsVersion4 + +type CheckHealthResponse_HealthStatus int32 + +const ( + CheckHealthResponse_UNKNOWN CheckHealthResponse_HealthStatus = 0 + CheckHealthResponse_OK CheckHealthResponse_HealthStatus = 1 + CheckHealthResponse_ERROR CheckHealthResponse_HealthStatus = 2 +) + +// Enum value maps for CheckHealthResponse_HealthStatus. +var ( + CheckHealthResponse_HealthStatus_name = map[int32]string{ + 0: "UNKNOWN", + 1: "OK", + 2: "ERROR", + } + CheckHealthResponse_HealthStatus_value = map[string]int32{ + "UNKNOWN": 0, + "OK": 1, + "ERROR": 2, + } +) + +func (x CheckHealthResponse_HealthStatus) Enum() *CheckHealthResponse_HealthStatus { + p := new(CheckHealthResponse_HealthStatus) + *p = x + return p +} + +func (x CheckHealthResponse_HealthStatus) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (CheckHealthResponse_HealthStatus) Descriptor() protoreflect.EnumDescriptor { + return file_backend_proto_enumTypes[0].Descriptor() +} + +func (CheckHealthResponse_HealthStatus) Type() protoreflect.EnumType { + return &file_backend_proto_enumTypes[0] +} + +func (x CheckHealthResponse_HealthStatus) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use CheckHealthResponse_HealthStatus.Descriptor instead. +func (CheckHealthResponse_HealthStatus) EnumDescriptor() ([]byte, []int) { + return file_backend_proto_rawDescGZIP(), []int{15, 0} +} + +type AppInstanceSettings struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + JsonData []byte `protobuf:"bytes,3,opt,name=jsonData,proto3" json:"jsonData,omitempty"` + DecryptedSecureJsonData map[string]string `protobuf:"bytes,4,rep,name=decryptedSecureJsonData,proto3" json:"decryptedSecureJsonData,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` + LastUpdatedMS int64 `protobuf:"varint,5,opt,name=lastUpdatedMS,proto3" json:"lastUpdatedMS,omitempty"` +} + +func (x *AppInstanceSettings) Reset() { + *x = AppInstanceSettings{} + if protoimpl.UnsafeEnabled { + mi := &file_backend_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *AppInstanceSettings) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AppInstanceSettings) ProtoMessage() {} + +func (x *AppInstanceSettings) ProtoReflect() protoreflect.Message { + mi := &file_backend_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AppInstanceSettings.ProtoReflect.Descriptor instead. +func (*AppInstanceSettings) Descriptor() ([]byte, []int) { + return file_backend_proto_rawDescGZIP(), []int{0} +} + +func (x *AppInstanceSettings) GetJsonData() []byte { + if x != nil { + return x.JsonData + } + return nil +} + +func (x *AppInstanceSettings) GetDecryptedSecureJsonData() map[string]string { + if x != nil { + return x.DecryptedSecureJsonData + } + return nil +} + +func (x *AppInstanceSettings) GetLastUpdatedMS() int64 { + if x != nil { + return x.LastUpdatedMS + } + return 0 +} + +type DataSourceInstanceSettings struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Id int64 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"` + Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` + Url string `protobuf:"bytes,3,opt,name=url,proto3" json:"url,omitempty"` + User string `protobuf:"bytes,4,opt,name=user,proto3" json:"user,omitempty"` + Database string `protobuf:"bytes,5,opt,name=database,proto3" json:"database,omitempty"` + BasicAuthEnabled bool `protobuf:"varint,6,opt,name=basicAuthEnabled,proto3" json:"basicAuthEnabled,omitempty"` + BasicAuthUser string `protobuf:"bytes,7,opt,name=basicAuthUser,proto3" json:"basicAuthUser,omitempty"` + JsonData []byte `protobuf:"bytes,8,opt,name=jsonData,proto3" json:"jsonData,omitempty"` + DecryptedSecureJsonData map[string]string `protobuf:"bytes,9,rep,name=decryptedSecureJsonData,proto3" json:"decryptedSecureJsonData,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` + LastUpdatedMS int64 `protobuf:"varint,10,opt,name=lastUpdatedMS,proto3" json:"lastUpdatedMS,omitempty"` + Uid string `protobuf:"bytes,11,opt,name=uid,proto3" json:"uid,omitempty"` +} + +func (x *DataSourceInstanceSettings) Reset() { + *x = DataSourceInstanceSettings{} + if protoimpl.UnsafeEnabled { + mi := &file_backend_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *DataSourceInstanceSettings) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DataSourceInstanceSettings) ProtoMessage() {} + +func (x *DataSourceInstanceSettings) ProtoReflect() protoreflect.Message { + mi := &file_backend_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DataSourceInstanceSettings.ProtoReflect.Descriptor instead. +func (*DataSourceInstanceSettings) Descriptor() ([]byte, []int) { + return file_backend_proto_rawDescGZIP(), []int{1} +} + +func (x *DataSourceInstanceSettings) GetId() int64 { + if x != nil { + return x.Id + } + return 0 +} + +func (x *DataSourceInstanceSettings) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *DataSourceInstanceSettings) GetUrl() string { + if x != nil { + return x.Url + } + return "" +} + +func (x *DataSourceInstanceSettings) GetUser() string { + if x != nil { + return x.User + } + return "" +} + +func (x *DataSourceInstanceSettings) GetDatabase() string { + if x != nil { + return x.Database + } + return "" +} + +func (x *DataSourceInstanceSettings) GetBasicAuthEnabled() bool { + if x != nil { + return x.BasicAuthEnabled + } + return false +} + +func (x *DataSourceInstanceSettings) GetBasicAuthUser() string { + if x != nil { + return x.BasicAuthUser + } + return "" +} + +func (x *DataSourceInstanceSettings) GetJsonData() []byte { + if x != nil { + return x.JsonData + } + return nil +} + +func (x *DataSourceInstanceSettings) GetDecryptedSecureJsonData() map[string]string { + if x != nil { + return x.DecryptedSecureJsonData + } + return nil +} + +func (x *DataSourceInstanceSettings) GetLastUpdatedMS() int64 { + if x != nil { + return x.LastUpdatedMS + } + return 0 +} + +func (x *DataSourceInstanceSettings) GetUid() string { + if x != nil { + return x.Uid + } + return "" +} + +type User struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Login string `protobuf:"bytes,1,opt,name=login,proto3" json:"login,omitempty"` + Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` + Email string `protobuf:"bytes,3,opt,name=email,proto3" json:"email,omitempty"` + Role string `protobuf:"bytes,4,opt,name=role,proto3" json:"role,omitempty"` +} + +func (x *User) Reset() { + *x = User{} + if protoimpl.UnsafeEnabled { + mi := &file_backend_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *User) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*User) ProtoMessage() {} + +func (x *User) ProtoReflect() protoreflect.Message { + mi := &file_backend_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use User.ProtoReflect.Descriptor instead. +func (*User) Descriptor() ([]byte, []int) { + return file_backend_proto_rawDescGZIP(), []int{2} +} + +func (x *User) GetLogin() string { + if x != nil { + return x.Login + } + return "" +} + +func (x *User) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *User) GetEmail() string { + if x != nil { + return x.Email + } + return "" +} + +func (x *User) GetRole() string { + if x != nil { + return x.Role + } + return "" +} + +type PluginContext struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The Grafana organization id the request originating from. + OrgId int64 `protobuf:"varint,1,opt,name=orgId,proto3" json:"orgId,omitempty"` + // The unique identifier of the plugin the request originating from. + PluginId string `protobuf:"bytes,2,opt,name=pluginId,proto3" json:"pluginId,omitempty"` + // The Grafana user the request originating from. + // + // Will not be provided if Grafana backend initiated the request. + User *User `protobuf:"bytes,3,opt,name=user,proto3" json:"user,omitempty"` + // App plugin instance settings is the configured app instance settings. + // In Grafana an app instance is an enabled app plugin in a + // Grafana organization. + // + // Will only be set if request targeting an app instance. + AppInstanceSettings *AppInstanceSettings `protobuf:"bytes,4,opt,name=appInstanceSettings,proto3" json:"appInstanceSettings,omitempty"` + // Data source instance settings is the configured data source instance + // settings. In Grafana a data source instance is a created data source + // in a Grafana organization. + // + // Will only be set if request targeting a data source instance. + DataSourceInstanceSettings *DataSourceInstanceSettings `protobuf:"bytes,5,opt,name=dataSourceInstanceSettings,proto3" json:"dataSourceInstanceSettings,omitempty"` +} + +func (x *PluginContext) Reset() { + *x = PluginContext{} + if protoimpl.UnsafeEnabled { + mi := &file_backend_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *PluginContext) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PluginContext) ProtoMessage() {} + +func (x *PluginContext) ProtoReflect() protoreflect.Message { + mi := &file_backend_proto_msgTypes[3] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PluginContext.ProtoReflect.Descriptor instead. +func (*PluginContext) Descriptor() ([]byte, []int) { + return file_backend_proto_rawDescGZIP(), []int{3} +} + +func (x *PluginContext) GetOrgId() int64 { + if x != nil { + return x.OrgId + } + return 0 +} + +func (x *PluginContext) GetPluginId() string { + if x != nil { + return x.PluginId + } + return "" +} + +func (x *PluginContext) GetUser() *User { + if x != nil { + return x.User + } + return nil +} + +func (x *PluginContext) GetAppInstanceSettings() *AppInstanceSettings { + if x != nil { + return x.AppInstanceSettings + } + return nil +} + +func (x *PluginContext) GetDataSourceInstanceSettings() *DataSourceInstanceSettings { + if x != nil { + return x.DataSourceInstanceSettings + } + return nil +} + +type StringList struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Values []string `protobuf:"bytes,1,rep,name=values,proto3" json:"values,omitempty"` +} + +func (x *StringList) Reset() { + *x = StringList{} + if protoimpl.UnsafeEnabled { + mi := &file_backend_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *StringList) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*StringList) ProtoMessage() {} + +func (x *StringList) ProtoReflect() protoreflect.Message { + mi := &file_backend_proto_msgTypes[4] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use StringList.ProtoReflect.Descriptor instead. +func (*StringList) Descriptor() ([]byte, []int) { + return file_backend_proto_rawDescGZIP(), []int{4} +} + +func (x *StringList) GetValues() []string { + if x != nil { + return x.Values + } + return nil +} + +type CallResourceRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + PluginContext *PluginContext `protobuf:"bytes,1,opt,name=pluginContext,proto3" json:"pluginContext,omitempty"` + Path string `protobuf:"bytes,2,opt,name=path,proto3" json:"path,omitempty"` + Method string `protobuf:"bytes,3,opt,name=method,proto3" json:"method,omitempty"` + Url string `protobuf:"bytes,4,opt,name=url,proto3" json:"url,omitempty"` + Headers map[string]*StringList `protobuf:"bytes,5,rep,name=headers,proto3" json:"headers,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` + Body []byte `protobuf:"bytes,6,opt,name=body,proto3" json:"body,omitempty"` +} + +func (x *CallResourceRequest) Reset() { + *x = CallResourceRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_backend_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CallResourceRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CallResourceRequest) ProtoMessage() {} + +func (x *CallResourceRequest) ProtoReflect() protoreflect.Message { + mi := &file_backend_proto_msgTypes[5] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CallResourceRequest.ProtoReflect.Descriptor instead. +func (*CallResourceRequest) Descriptor() ([]byte, []int) { + return file_backend_proto_rawDescGZIP(), []int{5} +} + +func (x *CallResourceRequest) GetPluginContext() *PluginContext { + if x != nil { + return x.PluginContext + } + return nil +} + +func (x *CallResourceRequest) GetPath() string { + if x != nil { + return x.Path + } + return "" +} + +func (x *CallResourceRequest) GetMethod() string { + if x != nil { + return x.Method + } + return "" +} + +func (x *CallResourceRequest) GetUrl() string { + if x != nil { + return x.Url + } + return "" +} + +func (x *CallResourceRequest) GetHeaders() map[string]*StringList { + if x != nil { + return x.Headers + } + return nil +} + +func (x *CallResourceRequest) GetBody() []byte { + if x != nil { + return x.Body + } + return nil +} + +type CallResourceResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Code int32 `protobuf:"varint,1,opt,name=code,proto3" json:"code,omitempty"` + Headers map[string]*StringList `protobuf:"bytes,2,rep,name=headers,proto3" json:"headers,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` + Body []byte `protobuf:"bytes,3,opt,name=body,proto3" json:"body,omitempty"` +} + +func (x *CallResourceResponse) Reset() { + *x = CallResourceResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_backend_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CallResourceResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CallResourceResponse) ProtoMessage() {} + +func (x *CallResourceResponse) ProtoReflect() protoreflect.Message { + mi := &file_backend_proto_msgTypes[6] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CallResourceResponse.ProtoReflect.Descriptor instead. +func (*CallResourceResponse) Descriptor() ([]byte, []int) { + return file_backend_proto_rawDescGZIP(), []int{6} +} + +func (x *CallResourceResponse) GetCode() int32 { + if x != nil { + return x.Code + } + return 0 +} + +func (x *CallResourceResponse) GetHeaders() map[string]*StringList { + if x != nil { + return x.Headers + } + return nil +} + +func (x *CallResourceResponse) GetBody() []byte { + if x != nil { + return x.Body + } + return nil +} + +type TimeRange struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + FromEpochMS int64 `protobuf:"varint,1,opt,name=fromEpochMS,proto3" json:"fromEpochMS,omitempty"` + ToEpochMS int64 `protobuf:"varint,2,opt,name=toEpochMS,proto3" json:"toEpochMS,omitempty"` +} + +func (x *TimeRange) Reset() { + *x = TimeRange{} + if protoimpl.UnsafeEnabled { + mi := &file_backend_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *TimeRange) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TimeRange) ProtoMessage() {} + +func (x *TimeRange) ProtoReflect() protoreflect.Message { + mi := &file_backend_proto_msgTypes[7] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TimeRange.ProtoReflect.Descriptor instead. +func (*TimeRange) Descriptor() ([]byte, []int) { + return file_backend_proto_rawDescGZIP(), []int{7} +} + +func (x *TimeRange) GetFromEpochMS() int64 { + if x != nil { + return x.FromEpochMS + } + return 0 +} + +func (x *TimeRange) GetToEpochMS() int64 { + if x != nil { + return x.ToEpochMS + } + return 0 +} + +type DataQuery struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + RefId string `protobuf:"bytes,1,opt,name=refId,proto3" json:"refId,omitempty"` + MaxDataPoints int64 `protobuf:"varint,2,opt,name=maxDataPoints,proto3" json:"maxDataPoints,omitempty"` + IntervalMS int64 `protobuf:"varint,3,opt,name=intervalMS,proto3" json:"intervalMS,omitempty"` + TimeRange *TimeRange `protobuf:"bytes,4,opt,name=timeRange,proto3" json:"timeRange,omitempty"` + Json []byte `protobuf:"bytes,5,opt,name=json,proto3" json:"json,omitempty"` + QueryType string `protobuf:"bytes,6,opt,name=queryType,proto3" json:"queryType,omitempty"` +} + +func (x *DataQuery) Reset() { + *x = DataQuery{} + if protoimpl.UnsafeEnabled { + mi := &file_backend_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *DataQuery) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DataQuery) ProtoMessage() {} + +func (x *DataQuery) ProtoReflect() protoreflect.Message { + mi := &file_backend_proto_msgTypes[8] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DataQuery.ProtoReflect.Descriptor instead. +func (*DataQuery) Descriptor() ([]byte, []int) { + return file_backend_proto_rawDescGZIP(), []int{8} +} + +func (x *DataQuery) GetRefId() string { + if x != nil { + return x.RefId + } + return "" +} + +func (x *DataQuery) GetMaxDataPoints() int64 { + if x != nil { + return x.MaxDataPoints + } + return 0 +} + +func (x *DataQuery) GetIntervalMS() int64 { + if x != nil { + return x.IntervalMS + } + return 0 +} + +func (x *DataQuery) GetTimeRange() *TimeRange { + if x != nil { + return x.TimeRange + } + return nil +} + +func (x *DataQuery) GetJson() []byte { + if x != nil { + return x.Json + } + return nil +} + +func (x *DataQuery) GetQueryType() string { + if x != nil { + return x.QueryType + } + return "" +} + +// QueryDataRequest +type QueryDataRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + PluginContext *PluginContext `protobuf:"bytes,1,opt,name=pluginContext,proto3" json:"pluginContext,omitempty"` + // Environment info + Headers map[string]string `protobuf:"bytes,2,rep,name=headers,proto3" json:"headers,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` + // List of data queries + Queries []*DataQuery `protobuf:"bytes,3,rep,name=queries,proto3" json:"queries,omitempty"` +} + +func (x *QueryDataRequest) Reset() { + *x = QueryDataRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_backend_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *QueryDataRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*QueryDataRequest) ProtoMessage() {} + +func (x *QueryDataRequest) ProtoReflect() protoreflect.Message { + mi := &file_backend_proto_msgTypes[9] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use QueryDataRequest.ProtoReflect.Descriptor instead. +func (*QueryDataRequest) Descriptor() ([]byte, []int) { + return file_backend_proto_rawDescGZIP(), []int{9} +} + +func (x *QueryDataRequest) GetPluginContext() *PluginContext { + if x != nil { + return x.PluginContext + } + return nil +} + +func (x *QueryDataRequest) GetHeaders() map[string]string { + if x != nil { + return x.Headers + } + return nil +} + +func (x *QueryDataRequest) GetQueries() []*DataQuery { + if x != nil { + return x.Queries + } + return nil +} + +type QueryDataResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Map of refId to response + Responses map[string]*DataResponse `protobuf:"bytes,1,rep,name=responses,proto3" json:"responses,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` +} + +func (x *QueryDataResponse) Reset() { + *x = QueryDataResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_backend_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *QueryDataResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*QueryDataResponse) ProtoMessage() {} + +func (x *QueryDataResponse) ProtoReflect() protoreflect.Message { + mi := &file_backend_proto_msgTypes[10] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use QueryDataResponse.ProtoReflect.Descriptor instead. +func (*QueryDataResponse) Descriptor() ([]byte, []int) { + return file_backend_proto_rawDescGZIP(), []int{10} +} + +func (x *QueryDataResponse) GetResponses() map[string]*DataResponse { + if x != nil { + return x.Responses + } + return nil +} + +type DataResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Arrow encoded DataFrames + // Frame has its own meta, warnings, and repeats refId + Frames [][]byte `protobuf:"bytes,1,rep,name=frames,proto3" json:"frames,omitempty"` + Error string `protobuf:"bytes,2,opt,name=error,proto3" json:"error,omitempty"` + JsonMeta []byte `protobuf:"bytes,3,opt,name=jsonMeta,proto3" json:"jsonMeta,omitempty"` // Warning: Current ignored by frontend. Would be for metadata about the query. +} + +func (x *DataResponse) Reset() { + *x = DataResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_backend_proto_msgTypes[11] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *DataResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DataResponse) ProtoMessage() {} + +func (x *DataResponse) ProtoReflect() protoreflect.Message { + mi := &file_backend_proto_msgTypes[11] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DataResponse.ProtoReflect.Descriptor instead. +func (*DataResponse) Descriptor() ([]byte, []int) { + return file_backend_proto_rawDescGZIP(), []int{11} +} + +func (x *DataResponse) GetFrames() [][]byte { + if x != nil { + return x.Frames + } + return nil +} + +func (x *DataResponse) GetError() string { + if x != nil { + return x.Error + } + return "" +} + +func (x *DataResponse) GetJsonMeta() []byte { + if x != nil { + return x.JsonMeta + } + return nil +} + +type CollectMetricsRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + PluginContext *PluginContext `protobuf:"bytes,1,opt,name=pluginContext,proto3" json:"pluginContext,omitempty"` +} + +func (x *CollectMetricsRequest) Reset() { + *x = CollectMetricsRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_backend_proto_msgTypes[12] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CollectMetricsRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CollectMetricsRequest) ProtoMessage() {} + +func (x *CollectMetricsRequest) ProtoReflect() protoreflect.Message { + mi := &file_backend_proto_msgTypes[12] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CollectMetricsRequest.ProtoReflect.Descriptor instead. +func (*CollectMetricsRequest) Descriptor() ([]byte, []int) { + return file_backend_proto_rawDescGZIP(), []int{12} +} + +func (x *CollectMetricsRequest) GetPluginContext() *PluginContext { + if x != nil { + return x.PluginContext + } + return nil +} + +type CollectMetricsResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Metrics *CollectMetricsResponse_Payload `protobuf:"bytes,1,opt,name=metrics,proto3" json:"metrics,omitempty"` +} + +func (x *CollectMetricsResponse) Reset() { + *x = CollectMetricsResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_backend_proto_msgTypes[13] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CollectMetricsResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CollectMetricsResponse) ProtoMessage() {} + +func (x *CollectMetricsResponse) ProtoReflect() protoreflect.Message { + mi := &file_backend_proto_msgTypes[13] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CollectMetricsResponse.ProtoReflect.Descriptor instead. +func (*CollectMetricsResponse) Descriptor() ([]byte, []int) { + return file_backend_proto_rawDescGZIP(), []int{13} +} + +func (x *CollectMetricsResponse) GetMetrics() *CollectMetricsResponse_Payload { + if x != nil { + return x.Metrics + } + return nil +} + +type CheckHealthRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + PluginContext *PluginContext `protobuf:"bytes,1,opt,name=pluginContext,proto3" json:"pluginContext,omitempty"` +} + +func (x *CheckHealthRequest) Reset() { + *x = CheckHealthRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_backend_proto_msgTypes[14] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CheckHealthRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CheckHealthRequest) ProtoMessage() {} + +func (x *CheckHealthRequest) ProtoReflect() protoreflect.Message { + mi := &file_backend_proto_msgTypes[14] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CheckHealthRequest.ProtoReflect.Descriptor instead. +func (*CheckHealthRequest) Descriptor() ([]byte, []int) { + return file_backend_proto_rawDescGZIP(), []int{14} +} + +func (x *CheckHealthRequest) GetPluginContext() *PluginContext { + if x != nil { + return x.PluginContext + } + return nil +} + +type CheckHealthResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Status CheckHealthResponse_HealthStatus `protobuf:"varint,1,opt,name=status,proto3,enum=pluginv2.CheckHealthResponse_HealthStatus" json:"status,omitempty"` + Message string `protobuf:"bytes,2,opt,name=message,proto3" json:"message,omitempty"` + JsonDetails []byte `protobuf:"bytes,3,opt,name=jsonDetails,proto3" json:"jsonDetails,omitempty"` +} + +func (x *CheckHealthResponse) Reset() { + *x = CheckHealthResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_backend_proto_msgTypes[15] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CheckHealthResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CheckHealthResponse) ProtoMessage() {} + +func (x *CheckHealthResponse) ProtoReflect() protoreflect.Message { + mi := &file_backend_proto_msgTypes[15] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CheckHealthResponse.ProtoReflect.Descriptor instead. +func (*CheckHealthResponse) Descriptor() ([]byte, []int) { + return file_backend_proto_rawDescGZIP(), []int{15} +} + +func (x *CheckHealthResponse) GetStatus() CheckHealthResponse_HealthStatus { + if x != nil { + return x.Status + } + return CheckHealthResponse_UNKNOWN +} + +func (x *CheckHealthResponse) GetMessage() string { + if x != nil { + return x.Message + } + return "" +} + +func (x *CheckHealthResponse) GetJsonDetails() []byte { + if x != nil { + return x.JsonDetails + } + return nil +} + +type CollectMetricsResponse_Payload struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Prometheus []byte `protobuf:"bytes,1,opt,name=prometheus,proto3" json:"prometheus,omitempty"` +} + +func (x *CollectMetricsResponse_Payload) Reset() { + *x = CollectMetricsResponse_Payload{} + if protoimpl.UnsafeEnabled { + mi := &file_backend_proto_msgTypes[22] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CollectMetricsResponse_Payload) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CollectMetricsResponse_Payload) ProtoMessage() {} + +func (x *CollectMetricsResponse_Payload) ProtoReflect() protoreflect.Message { + mi := &file_backend_proto_msgTypes[22] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CollectMetricsResponse_Payload.ProtoReflect.Descriptor instead. +func (*CollectMetricsResponse_Payload) Descriptor() ([]byte, []int) { + return file_backend_proto_rawDescGZIP(), []int{13, 0} +} + +func (x *CollectMetricsResponse_Payload) GetPrometheus() []byte { + if x != nil { + return x.Prometheus + } + return nil +} + +var File_backend_proto protoreflect.FileDescriptor + +var file_backend_proto_rawDesc = []byte{ + 0x0a, 0x0d, 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, + 0x08, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x76, 0x32, 0x22, 0x99, 0x02, 0x0a, 0x13, 0x41, 0x70, + 0x70, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, + 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x6a, 0x73, 0x6f, 0x6e, 0x44, 0x61, 0x74, 0x61, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x0c, 0x52, 0x08, 0x6a, 0x73, 0x6f, 0x6e, 0x44, 0x61, 0x74, 0x61, 0x12, 0x74, 0x0a, + 0x17, 0x64, 0x65, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x53, 0x65, 0x63, 0x75, 0x72, 0x65, + 0x4a, 0x73, 0x6f, 0x6e, 0x44, 0x61, 0x74, 0x61, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x3a, + 0x2e, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x76, 0x32, 0x2e, 0x41, 0x70, 0x70, 0x49, 0x6e, 0x73, + 0x74, 0x61, 0x6e, 0x63, 0x65, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x2e, 0x44, 0x65, + 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x53, 0x65, 0x63, 0x75, 0x72, 0x65, 0x4a, 0x73, 0x6f, + 0x6e, 0x44, 0x61, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x17, 0x64, 0x65, 0x63, 0x72, + 0x79, 0x70, 0x74, 0x65, 0x64, 0x53, 0x65, 0x63, 0x75, 0x72, 0x65, 0x4a, 0x73, 0x6f, 0x6e, 0x44, + 0x61, 0x74, 0x61, 0x12, 0x24, 0x0a, 0x0d, 0x6c, 0x61, 0x73, 0x74, 0x55, 0x70, 0x64, 0x61, 0x74, + 0x65, 0x64, 0x4d, 0x53, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x6c, 0x61, 0x73, 0x74, + 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x4d, 0x53, 0x1a, 0x4a, 0x0a, 0x1c, 0x44, 0x65, 0x63, + 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x53, 0x65, 0x63, 0x75, 0x72, 0x65, 0x4a, 0x73, 0x6f, 0x6e, + 0x44, 0x61, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, + 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, + 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0xf1, 0x03, 0x0a, 0x1a, 0x44, 0x61, 0x74, 0x61, 0x53, 0x6f, + 0x75, 0x72, 0x63, 0x65, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x53, 0x65, 0x74, 0x74, + 0x69, 0x6e, 0x67, 0x73, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x02, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x6c, 0x12, 0x12, 0x0a, 0x04, 0x75, 0x73, + 0x65, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x75, 0x73, 0x65, 0x72, 0x12, 0x1a, + 0x0a, 0x08, 0x64, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x08, 0x64, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x12, 0x2a, 0x0a, 0x10, 0x62, 0x61, + 0x73, 0x69, 0x63, 0x41, 0x75, 0x74, 0x68, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x06, + 0x20, 0x01, 0x28, 0x08, 0x52, 0x10, 0x62, 0x61, 0x73, 0x69, 0x63, 0x41, 0x75, 0x74, 0x68, 0x45, + 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x24, 0x0a, 0x0d, 0x62, 0x61, 0x73, 0x69, 0x63, 0x41, + 0x75, 0x74, 0x68, 0x55, 0x73, 0x65, 0x72, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x62, + 0x61, 0x73, 0x69, 0x63, 0x41, 0x75, 0x74, 0x68, 0x55, 0x73, 0x65, 0x72, 0x12, 0x1a, 0x0a, 0x08, + 0x6a, 0x73, 0x6f, 0x6e, 0x44, 0x61, 0x74, 0x61, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, + 0x6a, 0x73, 0x6f, 0x6e, 0x44, 0x61, 0x74, 0x61, 0x12, 0x7b, 0x0a, 0x17, 0x64, 0x65, 0x63, 0x72, + 0x79, 0x70, 0x74, 0x65, 0x64, 0x53, 0x65, 0x63, 0x75, 0x72, 0x65, 0x4a, 0x73, 0x6f, 0x6e, 0x44, + 0x61, 0x74, 0x61, 0x18, 0x09, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x41, 0x2e, 0x70, 0x6c, 0x75, 0x67, + 0x69, 0x6e, 0x76, 0x32, 0x2e, 0x44, 0x61, 0x74, 0x61, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x49, + 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x2e, + 0x44, 0x65, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x53, 0x65, 0x63, 0x75, 0x72, 0x65, 0x4a, + 0x73, 0x6f, 0x6e, 0x44, 0x61, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x17, 0x64, 0x65, + 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x53, 0x65, 0x63, 0x75, 0x72, 0x65, 0x4a, 0x73, 0x6f, + 0x6e, 0x44, 0x61, 0x74, 0x61, 0x12, 0x24, 0x0a, 0x0d, 0x6c, 0x61, 0x73, 0x74, 0x55, 0x70, 0x64, + 0x61, 0x74, 0x65, 0x64, 0x4d, 0x53, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x6c, 0x61, + 0x73, 0x74, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x4d, 0x53, 0x12, 0x10, 0x0a, 0x03, 0x75, + 0x69, 0x64, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x69, 0x64, 0x1a, 0x4a, 0x0a, + 0x1c, 0x44, 0x65, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x53, 0x65, 0x63, 0x75, 0x72, 0x65, + 0x4a, 0x73, 0x6f, 0x6e, 0x44, 0x61, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, + 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, + 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, + 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x5a, 0x0a, 0x04, 0x55, 0x73, 0x65, + 0x72, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x6f, 0x67, 0x69, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x05, 0x6c, 0x6f, 0x67, 0x69, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x65, + 0x6d, 0x61, 0x69, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x6d, 0x61, 0x69, + 0x6c, 0x12, 0x12, 0x0a, 0x04, 0x72, 0x6f, 0x6c, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x04, 0x72, 0x6f, 0x6c, 0x65, 0x22, 0x9c, 0x02, 0x0a, 0x0d, 0x50, 0x6c, 0x75, 0x67, 0x69, 0x6e, + 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x6f, 0x72, 0x67, 0x49, 0x64, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x6f, 0x72, 0x67, 0x49, 0x64, 0x12, 0x1a, 0x0a, + 0x08, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x49, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x08, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x49, 0x64, 0x12, 0x22, 0x0a, 0x04, 0x75, 0x73, 0x65, + 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, + 0x76, 0x32, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x52, 0x04, 0x75, 0x73, 0x65, 0x72, 0x12, 0x4f, 0x0a, + 0x13, 0x61, 0x70, 0x70, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x53, 0x65, 0x74, 0x74, + 0x69, 0x6e, 0x67, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x70, 0x6c, 0x75, + 0x67, 0x69, 0x6e, 0x76, 0x32, 0x2e, 0x41, 0x70, 0x70, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, + 0x65, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x52, 0x13, 0x61, 0x70, 0x70, 0x49, 0x6e, + 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x12, 0x64, + 0x0a, 0x1a, 0x64, 0x61, 0x74, 0x61, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x49, 0x6e, 0x73, 0x74, + 0x61, 0x6e, 0x63, 0x65, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x18, 0x05, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x76, 0x32, 0x2e, 0x44, 0x61, + 0x74, 0x61, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, + 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x52, 0x1a, 0x64, 0x61, 0x74, 0x61, 0x53, 0x6f, + 0x75, 0x72, 0x63, 0x65, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x53, 0x65, 0x74, 0x74, + 0x69, 0x6e, 0x67, 0x73, 0x22, 0x24, 0x0a, 0x0a, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x4c, 0x69, + 0x73, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, + 0x28, 0x09, 0x52, 0x06, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x22, 0xbe, 0x02, 0x0a, 0x13, 0x43, + 0x61, 0x6c, 0x6c, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x12, 0x3d, 0x0a, 0x0d, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x43, 0x6f, 0x6e, 0x74, + 0x65, 0x78, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x70, 0x6c, 0x75, 0x67, + 0x69, 0x6e, 0x76, 0x32, 0x2e, 0x50, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x65, + 0x78, 0x74, 0x52, 0x0d, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, + 0x74, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x74, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x04, 0x70, 0x61, 0x74, 0x68, 0x12, 0x16, 0x0a, 0x06, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x12, 0x10, 0x0a, + 0x03, 0x75, 0x72, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x6c, 0x12, + 0x44, 0x0a, 0x07, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, + 0x32, 0x2a, 0x2e, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x76, 0x32, 0x2e, 0x43, 0x61, 0x6c, 0x6c, + 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, + 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x07, 0x68, 0x65, + 0x61, 0x64, 0x65, 0x72, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x62, 0x6f, 0x64, 0x79, 0x18, 0x06, 0x20, + 0x01, 0x28, 0x0c, 0x52, 0x04, 0x62, 0x6f, 0x64, 0x79, 0x1a, 0x50, 0x0a, 0x0c, 0x48, 0x65, 0x61, + 0x64, 0x65, 0x72, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x2a, 0x0a, 0x05, 0x76, + 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x70, 0x6c, 0x75, + 0x67, 0x69, 0x6e, 0x76, 0x32, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x4c, 0x69, 0x73, 0x74, + 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0xd7, 0x01, 0x0a, 0x14, + 0x43, 0x61, 0x6c, 0x6c, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x05, 0x52, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x12, 0x45, 0x0a, 0x07, 0x68, 0x65, 0x61, 0x64, + 0x65, 0x72, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2b, 0x2e, 0x70, 0x6c, 0x75, 0x67, + 0x69, 0x6e, 0x76, 0x32, 0x2e, 0x43, 0x61, 0x6c, 0x6c, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, + 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, + 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x07, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x12, + 0x12, 0x0a, 0x04, 0x62, 0x6f, 0x64, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x62, + 0x6f, 0x64, 0x79, 0x1a, 0x50, 0x0a, 0x0c, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x45, 0x6e, + 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x2a, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x76, 0x32, 0x2e, + 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, + 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x4b, 0x0a, 0x09, 0x54, 0x69, 0x6d, 0x65, 0x52, 0x61, 0x6e, + 0x67, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x66, 0x72, 0x6f, 0x6d, 0x45, 0x70, 0x6f, 0x63, 0x68, 0x4d, + 0x53, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x66, 0x72, 0x6f, 0x6d, 0x45, 0x70, 0x6f, + 0x63, 0x68, 0x4d, 0x53, 0x12, 0x1c, 0x0a, 0x09, 0x74, 0x6f, 0x45, 0x70, 0x6f, 0x63, 0x68, 0x4d, + 0x53, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x74, 0x6f, 0x45, 0x70, 0x6f, 0x63, 0x68, + 0x4d, 0x53, 0x22, 0xcc, 0x01, 0x0a, 0x09, 0x44, 0x61, 0x74, 0x61, 0x51, 0x75, 0x65, 0x72, 0x79, + 0x12, 0x14, 0x0a, 0x05, 0x72, 0x65, 0x66, 0x49, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x05, 0x72, 0x65, 0x66, 0x49, 0x64, 0x12, 0x24, 0x0a, 0x0d, 0x6d, 0x61, 0x78, 0x44, 0x61, 0x74, + 0x61, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x6d, + 0x61, 0x78, 0x44, 0x61, 0x74, 0x61, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x73, 0x12, 0x1e, 0x0a, 0x0a, + 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x4d, 0x53, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x0a, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x4d, 0x53, 0x12, 0x31, 0x0a, 0x09, + 0x74, 0x69, 0x6d, 0x65, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x13, 0x2e, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x76, 0x32, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x52, + 0x61, 0x6e, 0x67, 0x65, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x12, + 0x12, 0x0a, 0x04, 0x6a, 0x73, 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x6a, + 0x73, 0x6f, 0x6e, 0x12, 0x1c, 0x0a, 0x09, 0x71, 0x75, 0x65, 0x72, 0x79, 0x54, 0x79, 0x70, 0x65, + 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x71, 0x75, 0x65, 0x72, 0x79, 0x54, 0x79, 0x70, + 0x65, 0x22, 0xff, 0x01, 0x0a, 0x10, 0x51, 0x75, 0x65, 0x72, 0x79, 0x44, 0x61, 0x74, 0x61, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x3d, 0x0a, 0x0d, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, + 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, + 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x76, 0x32, 0x2e, 0x50, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x43, + 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x52, 0x0d, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x43, 0x6f, + 0x6e, 0x74, 0x65, 0x78, 0x74, 0x12, 0x41, 0x0a, 0x07, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, + 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x76, + 0x32, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x44, 0x61, 0x74, 0x61, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x2e, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, + 0x07, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x12, 0x2d, 0x0a, 0x07, 0x71, 0x75, 0x65, 0x72, + 0x69, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x70, 0x6c, 0x75, 0x67, + 0x69, 0x6e, 0x76, 0x32, 0x2e, 0x44, 0x61, 0x74, 0x61, 0x51, 0x75, 0x65, 0x72, 0x79, 0x52, 0x07, + 0x71, 0x75, 0x65, 0x72, 0x69, 0x65, 0x73, 0x1a, 0x3a, 0x0a, 0x0c, 0x48, 0x65, 0x61, 0x64, 0x65, + 0x72, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, + 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, + 0x02, 0x38, 0x01, 0x22, 0xb3, 0x01, 0x0a, 0x11, 0x51, 0x75, 0x65, 0x72, 0x79, 0x44, 0x61, 0x74, + 0x61, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x48, 0x0a, 0x09, 0x72, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x70, + 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x76, 0x32, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x44, 0x61, 0x74, + 0x61, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x09, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x73, 0x1a, 0x54, 0x0a, 0x0e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x73, + 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x2c, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x76, + 0x32, 0x2e, 0x44, 0x61, 0x74, 0x61, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x52, 0x05, + 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x58, 0x0a, 0x0c, 0x44, 0x61, 0x74, + 0x61, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x66, 0x72, 0x61, + 0x6d, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x06, 0x66, 0x72, 0x61, 0x6d, 0x65, + 0x73, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x1a, 0x0a, 0x08, 0x6a, 0x73, 0x6f, 0x6e, 0x4d, + 0x65, 0x74, 0x61, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x6a, 0x73, 0x6f, 0x6e, 0x4d, + 0x65, 0x74, 0x61, 0x22, 0x56, 0x0a, 0x15, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x4d, 0x65, + 0x74, 0x72, 0x69, 0x63, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x3d, 0x0a, 0x0d, + 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x76, 0x32, 0x2e, 0x50, + 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x52, 0x0d, 0x70, 0x6c, + 0x75, 0x67, 0x69, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x22, 0x87, 0x01, 0x0a, 0x16, + 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x42, 0x0a, 0x07, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, + 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x28, 0x2e, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, + 0x76, 0x32, 0x2e, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, + 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x50, 0x61, 0x79, 0x6c, 0x6f, 0x61, + 0x64, 0x52, 0x07, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x1a, 0x29, 0x0a, 0x07, 0x50, 0x61, + 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x12, 0x1e, 0x0a, 0x0a, 0x70, 0x72, 0x6f, 0x6d, 0x65, 0x74, 0x68, + 0x65, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x70, 0x72, 0x6f, 0x6d, 0x65, + 0x74, 0x68, 0x65, 0x75, 0x73, 0x22, 0x53, 0x0a, 0x12, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x48, 0x65, + 0x61, 0x6c, 0x74, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x3d, 0x0a, 0x0d, 0x70, + 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x76, 0x32, 0x2e, 0x50, 0x6c, + 0x75, 0x67, 0x69, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x52, 0x0d, 0x70, 0x6c, 0x75, + 0x67, 0x69, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x22, 0xc5, 0x01, 0x0a, 0x13, 0x43, + 0x68, 0x65, 0x63, 0x6b, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x42, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x0e, 0x32, 0x2a, 0x2e, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x76, 0x32, 0x2e, 0x43, 0x68, + 0x65, 0x63, 0x6b, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x2e, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, + 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, + 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, + 0x12, 0x20, 0x0a, 0x0b, 0x6a, 0x73, 0x6f, 0x6e, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x6a, 0x73, 0x6f, 0x6e, 0x44, 0x65, 0x74, 0x61, 0x69, + 0x6c, 0x73, 0x22, 0x2e, 0x0a, 0x0c, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x53, 0x74, 0x61, 0x74, + 0x75, 0x73, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, + 0x06, 0x0a, 0x02, 0x4f, 0x4b, 0x10, 0x01, 0x12, 0x09, 0x0a, 0x05, 0x45, 0x52, 0x52, 0x4f, 0x52, + 0x10, 0x02, 0x32, 0x5b, 0x0a, 0x08, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x4f, + 0x0a, 0x0c, 0x43, 0x61, 0x6c, 0x6c, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x1d, + 0x2e, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x76, 0x32, 0x2e, 0x43, 0x61, 0x6c, 0x6c, 0x52, 0x65, + 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, + 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x76, 0x32, 0x2e, 0x43, 0x61, 0x6c, 0x6c, 0x52, 0x65, 0x73, + 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x30, 0x01, 0x32, + 0x4c, 0x0a, 0x04, 0x44, 0x61, 0x74, 0x61, 0x12, 0x44, 0x0a, 0x09, 0x51, 0x75, 0x65, 0x72, 0x79, + 0x44, 0x61, 0x74, 0x61, 0x12, 0x1a, 0x2e, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x76, 0x32, 0x2e, + 0x51, 0x75, 0x65, 0x72, 0x79, 0x44, 0x61, 0x74, 0x61, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x1b, 0x2e, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x76, 0x32, 0x2e, 0x51, 0x75, 0x65, 0x72, + 0x79, 0x44, 0x61, 0x74, 0x61, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0xae, 0x01, + 0x0a, 0x0b, 0x44, 0x69, 0x61, 0x67, 0x6e, 0x6f, 0x73, 0x74, 0x69, 0x63, 0x73, 0x12, 0x4a, 0x0a, + 0x0b, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x12, 0x1c, 0x2e, 0x70, + 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x76, 0x32, 0x2e, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x48, 0x65, 0x61, + 0x6c, 0x74, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x70, 0x6c, 0x75, + 0x67, 0x69, 0x6e, 0x76, 0x32, 0x2e, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x48, 0x65, 0x61, 0x6c, 0x74, + 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x53, 0x0a, 0x0e, 0x43, 0x6f, 0x6c, + 0x6c, 0x65, 0x63, 0x74, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x12, 0x1f, 0x2e, 0x70, 0x6c, + 0x75, 0x67, 0x69, 0x6e, 0x76, 0x32, 0x2e, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x4d, 0x65, + 0x74, 0x72, 0x69, 0x63, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x70, + 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x76, 0x32, 0x2e, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x4d, + 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x0c, + 0x5a, 0x0a, 0x2e, 0x3b, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x76, 0x32, 0x62, 0x06, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_backend_proto_rawDescOnce sync.Once + file_backend_proto_rawDescData = file_backend_proto_rawDesc +) + +func file_backend_proto_rawDescGZIP() []byte { + file_backend_proto_rawDescOnce.Do(func() { + file_backend_proto_rawDescData = protoimpl.X.CompressGZIP(file_backend_proto_rawDescData) + }) + return file_backend_proto_rawDescData +} + +var file_backend_proto_enumTypes = make([]protoimpl.EnumInfo, 1) +var file_backend_proto_msgTypes = make([]protoimpl.MessageInfo, 23) +var file_backend_proto_goTypes = []interface{}{ + (CheckHealthResponse_HealthStatus)(0), // 0: pluginv2.CheckHealthResponse.HealthStatus + (*AppInstanceSettings)(nil), // 1: pluginv2.AppInstanceSettings + (*DataSourceInstanceSettings)(nil), // 2: pluginv2.DataSourceInstanceSettings + (*User)(nil), // 3: pluginv2.User + (*PluginContext)(nil), // 4: pluginv2.PluginContext + (*StringList)(nil), // 5: pluginv2.StringList + (*CallResourceRequest)(nil), // 6: pluginv2.CallResourceRequest + (*CallResourceResponse)(nil), // 7: pluginv2.CallResourceResponse + (*TimeRange)(nil), // 8: pluginv2.TimeRange + (*DataQuery)(nil), // 9: pluginv2.DataQuery + (*QueryDataRequest)(nil), // 10: pluginv2.QueryDataRequest + (*QueryDataResponse)(nil), // 11: pluginv2.QueryDataResponse + (*DataResponse)(nil), // 12: pluginv2.DataResponse + (*CollectMetricsRequest)(nil), // 13: pluginv2.CollectMetricsRequest + (*CollectMetricsResponse)(nil), // 14: pluginv2.CollectMetricsResponse + (*CheckHealthRequest)(nil), // 15: pluginv2.CheckHealthRequest + (*CheckHealthResponse)(nil), // 16: pluginv2.CheckHealthResponse + nil, // 17: pluginv2.AppInstanceSettings.DecryptedSecureJsonDataEntry + nil, // 18: pluginv2.DataSourceInstanceSettings.DecryptedSecureJsonDataEntry + nil, // 19: pluginv2.CallResourceRequest.HeadersEntry + nil, // 20: pluginv2.CallResourceResponse.HeadersEntry + nil, // 21: pluginv2.QueryDataRequest.HeadersEntry + nil, // 22: pluginv2.QueryDataResponse.ResponsesEntry + (*CollectMetricsResponse_Payload)(nil), // 23: pluginv2.CollectMetricsResponse.Payload +} +var file_backend_proto_depIdxs = []int32{ + 17, // 0: pluginv2.AppInstanceSettings.decryptedSecureJsonData:type_name -> pluginv2.AppInstanceSettings.DecryptedSecureJsonDataEntry + 18, // 1: pluginv2.DataSourceInstanceSettings.decryptedSecureJsonData:type_name -> pluginv2.DataSourceInstanceSettings.DecryptedSecureJsonDataEntry + 3, // 2: pluginv2.PluginContext.user:type_name -> pluginv2.User + 1, // 3: pluginv2.PluginContext.appInstanceSettings:type_name -> pluginv2.AppInstanceSettings + 2, // 4: pluginv2.PluginContext.dataSourceInstanceSettings:type_name -> pluginv2.DataSourceInstanceSettings + 4, // 5: pluginv2.CallResourceRequest.pluginContext:type_name -> pluginv2.PluginContext + 19, // 6: pluginv2.CallResourceRequest.headers:type_name -> pluginv2.CallResourceRequest.HeadersEntry + 20, // 7: pluginv2.CallResourceResponse.headers:type_name -> pluginv2.CallResourceResponse.HeadersEntry + 8, // 8: pluginv2.DataQuery.timeRange:type_name -> pluginv2.TimeRange + 4, // 9: pluginv2.QueryDataRequest.pluginContext:type_name -> pluginv2.PluginContext + 21, // 10: pluginv2.QueryDataRequest.headers:type_name -> pluginv2.QueryDataRequest.HeadersEntry + 9, // 11: pluginv2.QueryDataRequest.queries:type_name -> pluginv2.DataQuery + 22, // 12: pluginv2.QueryDataResponse.responses:type_name -> pluginv2.QueryDataResponse.ResponsesEntry + 4, // 13: pluginv2.CollectMetricsRequest.pluginContext:type_name -> pluginv2.PluginContext + 23, // 14: pluginv2.CollectMetricsResponse.metrics:type_name -> pluginv2.CollectMetricsResponse.Payload + 4, // 15: pluginv2.CheckHealthRequest.pluginContext:type_name -> pluginv2.PluginContext + 0, // 16: pluginv2.CheckHealthResponse.status:type_name -> pluginv2.CheckHealthResponse.HealthStatus + 5, // 17: pluginv2.CallResourceRequest.HeadersEntry.value:type_name -> pluginv2.StringList + 5, // 18: pluginv2.CallResourceResponse.HeadersEntry.value:type_name -> pluginv2.StringList + 12, // 19: pluginv2.QueryDataResponse.ResponsesEntry.value:type_name -> pluginv2.DataResponse + 6, // 20: pluginv2.Resource.CallResource:input_type -> pluginv2.CallResourceRequest + 10, // 21: pluginv2.Data.QueryData:input_type -> pluginv2.QueryDataRequest + 15, // 22: pluginv2.Diagnostics.CheckHealth:input_type -> pluginv2.CheckHealthRequest + 13, // 23: pluginv2.Diagnostics.CollectMetrics:input_type -> pluginv2.CollectMetricsRequest + 7, // 24: pluginv2.Resource.CallResource:output_type -> pluginv2.CallResourceResponse + 11, // 25: pluginv2.Data.QueryData:output_type -> pluginv2.QueryDataResponse + 16, // 26: pluginv2.Diagnostics.CheckHealth:output_type -> pluginv2.CheckHealthResponse + 14, // 27: pluginv2.Diagnostics.CollectMetrics:output_type -> pluginv2.CollectMetricsResponse + 24, // [24:28] is the sub-list for method output_type + 20, // [20:24] is the sub-list for method input_type + 20, // [20:20] is the sub-list for extension type_name + 20, // [20:20] is the sub-list for extension extendee + 0, // [0:20] is the sub-list for field type_name +} + +func init() { file_backend_proto_init() } +func file_backend_proto_init() { + if File_backend_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_backend_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*AppInstanceSettings); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_backend_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*DataSourceInstanceSettings); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_backend_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*User); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_backend_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*PluginContext); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_backend_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*StringList); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_backend_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*CallResourceRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_backend_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*CallResourceResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_backend_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*TimeRange); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_backend_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*DataQuery); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_backend_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*QueryDataRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_backend_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*QueryDataResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_backend_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*DataResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_backend_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*CollectMetricsRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_backend_proto_msgTypes[13].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*CollectMetricsResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_backend_proto_msgTypes[14].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*CheckHealthRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_backend_proto_msgTypes[15].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*CheckHealthResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_backend_proto_msgTypes[22].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*CollectMetricsResponse_Payload); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_backend_proto_rawDesc, + NumEnums: 1, + NumMessages: 23, + NumExtensions: 0, + NumServices: 3, + }, + GoTypes: file_backend_proto_goTypes, + DependencyIndexes: file_backend_proto_depIdxs, + EnumInfos: file_backend_proto_enumTypes, + MessageInfos: file_backend_proto_msgTypes, + }.Build() + File_backend_proto = out.File + file_backend_proto_rawDesc = nil + file_backend_proto_goTypes = nil + file_backend_proto_depIdxs = nil +} diff --git a/vendor/github.com/grafana/grafana-plugin-sdk-go/genproto/pluginv2/backend_grpc.pb.go b/vendor/github.com/grafana/grafana-plugin-sdk-go/genproto/pluginv2/backend_grpc.pb.go new file mode 100644 index 00000000000..a8e78b4c6c6 --- /dev/null +++ b/vendor/github.com/grafana/grafana-plugin-sdk-go/genproto/pluginv2/backend_grpc.pb.go @@ -0,0 +1,330 @@ +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. + +package pluginv2 + +import ( + context "context" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.32.0 or later. +const _ = grpc.SupportPackageIsVersion7 + +// ResourceClient is the client API for Resource service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type ResourceClient interface { + CallResource(ctx context.Context, in *CallResourceRequest, opts ...grpc.CallOption) (Resource_CallResourceClient, error) +} + +type resourceClient struct { + cc grpc.ClientConnInterface +} + +func NewResourceClient(cc grpc.ClientConnInterface) ResourceClient { + return &resourceClient{cc} +} + +func (c *resourceClient) CallResource(ctx context.Context, in *CallResourceRequest, opts ...grpc.CallOption) (Resource_CallResourceClient, error) { + stream, err := c.cc.NewStream(ctx, &Resource_ServiceDesc.Streams[0], "/pluginv2.Resource/CallResource", opts...) + if err != nil { + return nil, err + } + x := &resourceCallResourceClient{stream} + if err := x.ClientStream.SendMsg(in); err != nil { + return nil, err + } + if err := x.ClientStream.CloseSend(); err != nil { + return nil, err + } + return x, nil +} + +type Resource_CallResourceClient interface { + Recv() (*CallResourceResponse, error) + grpc.ClientStream +} + +type resourceCallResourceClient struct { + grpc.ClientStream +} + +func (x *resourceCallResourceClient) Recv() (*CallResourceResponse, error) { + m := new(CallResourceResponse) + if err := x.ClientStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + +// ResourceServer is the server API for Resource service. +// All implementations should embed UnimplementedResourceServer +// for forward compatibility +type ResourceServer interface { + CallResource(*CallResourceRequest, Resource_CallResourceServer) error +} + +// UnimplementedResourceServer should be embedded to have forward compatible implementations. +type UnimplementedResourceServer struct { +} + +func (UnimplementedResourceServer) CallResource(*CallResourceRequest, Resource_CallResourceServer) error { + return status.Errorf(codes.Unimplemented, "method CallResource not implemented") +} + +// UnsafeResourceServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to ResourceServer will +// result in compilation errors. +type UnsafeResourceServer interface { + mustEmbedUnimplementedResourceServer() +} + +func RegisterResourceServer(s grpc.ServiceRegistrar, srv ResourceServer) { + s.RegisterService(&Resource_ServiceDesc, srv) +} + +func _Resource_CallResource_Handler(srv interface{}, stream grpc.ServerStream) error { + m := new(CallResourceRequest) + if err := stream.RecvMsg(m); err != nil { + return err + } + return srv.(ResourceServer).CallResource(m, &resourceCallResourceServer{stream}) +} + +type Resource_CallResourceServer interface { + Send(*CallResourceResponse) error + grpc.ServerStream +} + +type resourceCallResourceServer struct { + grpc.ServerStream +} + +func (x *resourceCallResourceServer) Send(m *CallResourceResponse) error { + return x.ServerStream.SendMsg(m) +} + +// Resource_ServiceDesc is the grpc.ServiceDesc for Resource service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var Resource_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "pluginv2.Resource", + HandlerType: (*ResourceServer)(nil), + Methods: []grpc.MethodDesc{}, + Streams: []grpc.StreamDesc{ + { + StreamName: "CallResource", + Handler: _Resource_CallResource_Handler, + ServerStreams: true, + }, + }, + Metadata: "backend.proto", +} + +// DataClient is the client API for Data service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type DataClient interface { + QueryData(ctx context.Context, in *QueryDataRequest, opts ...grpc.CallOption) (*QueryDataResponse, error) +} + +type dataClient struct { + cc grpc.ClientConnInterface +} + +func NewDataClient(cc grpc.ClientConnInterface) DataClient { + return &dataClient{cc} +} + +func (c *dataClient) QueryData(ctx context.Context, in *QueryDataRequest, opts ...grpc.CallOption) (*QueryDataResponse, error) { + out := new(QueryDataResponse) + err := c.cc.Invoke(ctx, "/pluginv2.Data/QueryData", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// DataServer is the server API for Data service. +// All implementations should embed UnimplementedDataServer +// for forward compatibility +type DataServer interface { + QueryData(context.Context, *QueryDataRequest) (*QueryDataResponse, error) +} + +// UnimplementedDataServer should be embedded to have forward compatible implementations. +type UnimplementedDataServer struct { +} + +func (UnimplementedDataServer) QueryData(context.Context, *QueryDataRequest) (*QueryDataResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method QueryData not implemented") +} + +// UnsafeDataServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to DataServer will +// result in compilation errors. +type UnsafeDataServer interface { + mustEmbedUnimplementedDataServer() +} + +func RegisterDataServer(s grpc.ServiceRegistrar, srv DataServer) { + s.RegisterService(&Data_ServiceDesc, srv) +} + +func _Data_QueryData_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(QueryDataRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(DataServer).QueryData(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/pluginv2.Data/QueryData", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(DataServer).QueryData(ctx, req.(*QueryDataRequest)) + } + return interceptor(ctx, in, info, handler) +} + +// Data_ServiceDesc is the grpc.ServiceDesc for Data service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var Data_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "pluginv2.Data", + HandlerType: (*DataServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "QueryData", + Handler: _Data_QueryData_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "backend.proto", +} + +// DiagnosticsClient is the client API for Diagnostics service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type DiagnosticsClient interface { + CheckHealth(ctx context.Context, in *CheckHealthRequest, opts ...grpc.CallOption) (*CheckHealthResponse, error) + CollectMetrics(ctx context.Context, in *CollectMetricsRequest, opts ...grpc.CallOption) (*CollectMetricsResponse, error) +} + +type diagnosticsClient struct { + cc grpc.ClientConnInterface +} + +func NewDiagnosticsClient(cc grpc.ClientConnInterface) DiagnosticsClient { + return &diagnosticsClient{cc} +} + +func (c *diagnosticsClient) CheckHealth(ctx context.Context, in *CheckHealthRequest, opts ...grpc.CallOption) (*CheckHealthResponse, error) { + out := new(CheckHealthResponse) + err := c.cc.Invoke(ctx, "/pluginv2.Diagnostics/CheckHealth", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *diagnosticsClient) CollectMetrics(ctx context.Context, in *CollectMetricsRequest, opts ...grpc.CallOption) (*CollectMetricsResponse, error) { + out := new(CollectMetricsResponse) + err := c.cc.Invoke(ctx, "/pluginv2.Diagnostics/CollectMetrics", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// DiagnosticsServer is the server API for Diagnostics service. +// All implementations should embed UnimplementedDiagnosticsServer +// for forward compatibility +type DiagnosticsServer interface { + CheckHealth(context.Context, *CheckHealthRequest) (*CheckHealthResponse, error) + CollectMetrics(context.Context, *CollectMetricsRequest) (*CollectMetricsResponse, error) +} + +// UnimplementedDiagnosticsServer should be embedded to have forward compatible implementations. +type UnimplementedDiagnosticsServer struct { +} + +func (UnimplementedDiagnosticsServer) CheckHealth(context.Context, *CheckHealthRequest) (*CheckHealthResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method CheckHealth not implemented") +} +func (UnimplementedDiagnosticsServer) CollectMetrics(context.Context, *CollectMetricsRequest) (*CollectMetricsResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method CollectMetrics not implemented") +} + +// UnsafeDiagnosticsServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to DiagnosticsServer will +// result in compilation errors. +type UnsafeDiagnosticsServer interface { + mustEmbedUnimplementedDiagnosticsServer() +} + +func RegisterDiagnosticsServer(s grpc.ServiceRegistrar, srv DiagnosticsServer) { + s.RegisterService(&Diagnostics_ServiceDesc, srv) +} + +func _Diagnostics_CheckHealth_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(CheckHealthRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(DiagnosticsServer).CheckHealth(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/pluginv2.Diagnostics/CheckHealth", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(DiagnosticsServer).CheckHealth(ctx, req.(*CheckHealthRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Diagnostics_CollectMetrics_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(CollectMetricsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(DiagnosticsServer).CollectMetrics(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/pluginv2.Diagnostics/CollectMetrics", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(DiagnosticsServer).CollectMetrics(ctx, req.(*CollectMetricsRequest)) + } + return interceptor(ctx, in, info, handler) +} + +// Diagnostics_ServiceDesc is the grpc.ServiceDesc for Diagnostics service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var Diagnostics_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "pluginv2.Diagnostics", + HandlerType: (*DiagnosticsServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "CheckHealth", + Handler: _Diagnostics_CheckHealth_Handler, + }, + { + MethodName: "CollectMetrics", + Handler: _Diagnostics_CollectMetrics_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "backend.proto", +} diff --git a/vendor/github.com/grafana/grafana/pkg/bus/bus.go b/vendor/github.com/grafana/grafana/pkg/bus/bus.go new file mode 100644 index 00000000000..6daf24c602a --- /dev/null +++ b/vendor/github.com/grafana/grafana/pkg/bus/bus.go @@ -0,0 +1,222 @@ +package bus + +import ( + "context" + "errors" + "fmt" + "reflect" + + "github.com/opentracing/opentracing-go" +) + +// HandlerFunc defines a handler function interface. +type HandlerFunc interface{} + +// Msg defines a message interface. +type Msg interface{} + +// ErrHandlerNotFound defines an error if a handler is not found +var ErrHandlerNotFound = errors.New("handler not found") + +// TransactionManager defines a transaction interface +type TransactionManager interface { + InTransaction(ctx context.Context, fn func(ctx context.Context) error) error +} + +// Bus type defines the bus interface structure +type Bus interface { + Dispatch(msg Msg) error + DispatchCtx(ctx context.Context, msg Msg) error + Publish(msg Msg) error + + // InTransaction starts a transaction and store it in the context. + // The caller can then pass a function with multiple DispatchCtx calls that + // all will be executed in the same transaction. InTransaction will rollback if the + // callback returns an error. + InTransaction(ctx context.Context, fn func(ctx context.Context) error) error + + AddHandler(handler HandlerFunc) + AddHandlerCtx(handler HandlerFunc) + AddEventListener(handler HandlerFunc) + + // SetTransactionManager allows the user to replace the internal + // noop TransactionManager that is responsible for managing + // transactions in `InTransaction` + SetTransactionManager(tm TransactionManager) +} + +// InTransaction defines an in transaction function +func (b *InProcBus) InTransaction(ctx context.Context, fn func(ctx context.Context) error) error { + return b.txMng.InTransaction(ctx, fn) +} + +// InProcBus defines the bus structure +type InProcBus struct { + handlers map[string]HandlerFunc + handlersWithCtx map[string]HandlerFunc + listeners map[string][]HandlerFunc + txMng TransactionManager +} + +// temp stuff, not sure how to handle bus instance, and init yet +var globalBus = New() + +// New initialize the bus +func New() Bus { + bus := &InProcBus{} + bus.handlers = make(map[string]HandlerFunc) + bus.handlersWithCtx = make(map[string]HandlerFunc) + bus.listeners = make(map[string][]HandlerFunc) + bus.txMng = &noopTransactionManager{} + + return bus +} + +// Want to get rid of global bus +func GetBus() Bus { + return globalBus +} + +// SetTransactionManager function assign a transaction manager to the bus. +func (b *InProcBus) SetTransactionManager(tm TransactionManager) { + b.txMng = tm +} + +// DispatchCtx function dispatch a message to the bus context. +func (b *InProcBus) DispatchCtx(ctx context.Context, msg Msg) error { + var msgName = reflect.TypeOf(msg).Elem().Name() + + span, ctx := opentracing.StartSpanFromContext(ctx, "bus - "+msgName) + defer span.Finish() + + span.SetTag("msg", msgName) + + var handler = b.handlersWithCtx[msgName] + if handler == nil { + return ErrHandlerNotFound + } + + var params = []reflect.Value{} + params = append(params, reflect.ValueOf(ctx)) + params = append(params, reflect.ValueOf(msg)) + + ret := reflect.ValueOf(handler).Call(params) + err := ret[0].Interface() + if err == nil { + return nil + } + return err.(error) +} + +// Dispatch function dispatch a message to the bus. +func (b *InProcBus) Dispatch(msg Msg) error { + var msgName = reflect.TypeOf(msg).Elem().Name() + + withCtx := true + handler := b.handlersWithCtx[msgName] + if handler == nil { + withCtx = false + handler = b.handlers[msgName] + if handler == nil { + return ErrHandlerNotFound + } + } + + var params = []reflect.Value{} + if withCtx { + params = append(params, reflect.ValueOf(context.Background())) + } + params = append(params, reflect.ValueOf(msg)) + + ret := reflect.ValueOf(handler).Call(params) + err := ret[0].Interface() + if err == nil { + return nil + } + return err.(error) +} + +// Publish function publish a message to the bus listener. +func (b *InProcBus) Publish(msg Msg) error { + var msgName = reflect.TypeOf(msg).Elem().Name() + var listeners = b.listeners[msgName] + + var params = make([]reflect.Value, 1) + params[0] = reflect.ValueOf(msg) + + for _, listenerHandler := range listeners { + ret := reflect.ValueOf(listenerHandler).Call(params) + e := ret[0].Interface() + if e != nil { + err, ok := e.(error) + if ok { + return err + } + return fmt.Errorf("expected listener to return an error, got '%T'", e) + } + } + + return nil +} + +func (b *InProcBus) AddHandler(handler HandlerFunc) { + handlerType := reflect.TypeOf(handler) + queryTypeName := handlerType.In(0).Elem().Name() + b.handlers[queryTypeName] = handler +} + +func (b *InProcBus) AddHandlerCtx(handler HandlerFunc) { + handlerType := reflect.TypeOf(handler) + queryTypeName := handlerType.In(1).Elem().Name() + b.handlersWithCtx[queryTypeName] = handler +} + +func (b *InProcBus) AddEventListener(handler HandlerFunc) { + handlerType := reflect.TypeOf(handler) + eventName := handlerType.In(0).Elem().Name() + _, exists := b.listeners[eventName] + if !exists { + b.listeners[eventName] = make([]HandlerFunc, 0) + } + b.listeners[eventName] = append(b.listeners[eventName], handler) +} + +// AddHandler attaches a handler function to the global bus. +// Package level function. +func AddHandler(implName string, handler HandlerFunc) { + globalBus.AddHandler(handler) +} + +// AddHandlerCtx attaches a handler function to the global bus context. +// Package level function. +func AddHandlerCtx(implName string, handler HandlerFunc) { + globalBus.AddHandlerCtx(handler) +} + +// AddEventListener attaches a handler function to the event listener. +// Package level function. +func AddEventListener(handler HandlerFunc) { + globalBus.AddEventListener(handler) +} + +func Dispatch(msg Msg) error { + return globalBus.Dispatch(msg) +} + +func DispatchCtx(ctx context.Context, msg Msg) error { + return globalBus.DispatchCtx(ctx, msg) +} + +func Publish(msg Msg) error { + return globalBus.Publish(msg) +} + +func ClearBusHandlers() { + globalBus = New() +} + +type noopTransactionManager struct{} + +func (*noopTransactionManager) InTransaction(ctx context.Context, fn func(ctx context.Context) error) error { + return fn(ctx) +} diff --git a/vendor/github.com/grafana/grafana/pkg/components/null/float.go b/vendor/github.com/grafana/grafana/pkg/components/null/float.go new file mode 100644 index 00000000000..8dd87f03f13 --- /dev/null +++ b/vendor/github.com/grafana/grafana/pkg/components/null/float.go @@ -0,0 +1,141 @@ +package null + +import ( + "database/sql" + "encoding/json" + "fmt" + "math" + "reflect" + "strconv" +) + +const ( + nullString = "null" +) + +// Float is a nullable float64. +// It does not consider zero values to be null. +// It will decode to null, not zero, if null. +type Float struct { + sql.NullFloat64 +} + +// NewFloat creates a new Float +func NewFloat(f float64, valid bool) Float { + return Float{ + NullFloat64: sql.NullFloat64{ + Float64: f, + Valid: valid, + }, + } +} + +// FloatFrom creates a new Float that will always be valid. +func FloatFrom(f float64) Float { + return NewFloat(f, true) +} + +// FloatFromPtr creates a new Float that be null if f is nil. +func FloatFromPtr(f *float64) Float { + if f == nil { + return NewFloat(0, false) + } + return NewFloat(*f, true) +} + +// FloatFromString creates a new Float from string f. +// If the string is equal to the value of nullString then the Float will be null. +// An empty string f will return an error. +func FloatFromString(f string, nullString string) (Float, error) { + if f == nullString { + return FloatFromPtr(nil), nil + } + fV, err := strconv.ParseFloat(f, 64) + if err != nil { + return Float{}, err + } + return FloatFrom(fV), nil +} + +// UnmarshalJSON implements json.Unmarshaler. +// It supports number and null input. +// 0 will not be considered a null Float. +// It also supports unmarshaling a sql.NullFloat64. +func (f *Float) UnmarshalJSON(data []byte) error { + var err error + var v interface{} + if err = json.Unmarshal(data, &v); err != nil { + return err + } + switch x := v.(type) { + case float64: + f.Float64 = x + case map[string]interface{}: + err = json.Unmarshal(data, &f.NullFloat64) + case nil: + f.Valid = false + return nil + default: + err = fmt.Errorf("json: cannot unmarshal %v into Go value of type null.Float", reflect.TypeOf(v).Name()) + } + f.Valid = err == nil + return err +} + +// UnmarshalText implements encoding.TextUnmarshaler. +// It will unmarshal to a null Float if the input is a blank or not an integer. +// It will return an error if the input is not an integer, blank, or "null". +func (f *Float) UnmarshalText(text []byte) error { + str := string(text) + if str == "" || str == nullString { + f.Valid = false + return nil + } + var err error + f.Float64, err = strconv.ParseFloat(string(text), 64) + f.Valid = err == nil + return err +} + +// MarshalJSON implements json.Marshaler. +// It will encode null if this Float is null. +func (f Float) MarshalJSON() ([]byte, error) { + if !f.Valid || math.IsNaN(f.Float64) { + return []byte(nullString), nil + } + return []byte(strconv.FormatFloat(f.Float64, 'f', -1, 64)), nil +} + +// MarshalText implements encoding.TextMarshaler. +// It will encode a blank string if this Float is null. +func (f Float) MarshalText() ([]byte, error) { + if !f.Valid { + return []byte{}, nil + } + return []byte(strconv.FormatFloat(f.Float64, 'f', -1, 64)), nil +} + +// MarshalText implements encoding.TextMarshaler. +// It will encode a blank string if this Float is null. +func (f Float) String() string { + if !f.Valid { + return nullString + } + + return fmt.Sprintf("%1.3f", f.Float64) +} + +// FullString returns float as string in full precision +func (f Float) FullString() string { + if !f.Valid { + return nullString + } + + return fmt.Sprintf("%f", f.Float64) +} + +// IsZero returns true for invalid Floats, for future omitempty support (Go 1.4?) +// A non-null Float with a 0 value will not be considered zero. +func (f Float) IsZero() bool { + return !f.Valid +} diff --git a/vendor/github.com/grafana/grafana/pkg/expr/commands.go b/vendor/github.com/grafana/grafana/pkg/expr/commands.go new file mode 100644 index 00000000000..8f410f04b01 --- /dev/null +++ b/vendor/github.com/grafana/grafana/pkg/expr/commands.go @@ -0,0 +1,269 @@ +package expr + +import ( + "context" + "fmt" + "strings" + "time" + + "github.com/grafana/grafana-plugin-sdk-go/backend" + "github.com/grafana/grafana/pkg/components/gtime" + "github.com/grafana/grafana/pkg/expr/mathexp" +) + +// Command is an interface for all expression commands. +type Command interface { + NeedsVars() []string + Execute(c context.Context, vars mathexp.Vars) (mathexp.Results, error) +} + +// MathCommand is a command for a math expression such as "1 + $GA / 2" +type MathCommand struct { + RawExpression string + Expression *mathexp.Expr + refID string +} + +// NewMathCommand creates a new MathCommand. It will return an error +// if there is an error parsing expr. +func NewMathCommand(refID, expr string) (*MathCommand, error) { + parsedExpr, err := mathexp.New(expr) + if err != nil { + return nil, err + } + return &MathCommand{ + RawExpression: expr, + Expression: parsedExpr, + refID: refID, + }, nil +} + +// UnmarshalMathCommand creates a MathCommand from Grafana's frontend query. +func UnmarshalMathCommand(rn *rawNode) (*MathCommand, error) { + rawExpr, ok := rn.Query["expression"] + if !ok { + return nil, fmt.Errorf("math command for refId %v is missing an expression", rn.RefID) + } + exprString, ok := rawExpr.(string) + if !ok { + return nil, fmt.Errorf("expected math command for refId %v expression to be a string, got %T", rn.RefID, rawExpr) + } + + gm, err := NewMathCommand(rn.RefID, exprString) + if err != nil { + return nil, fmt.Errorf("invalid math command type in '%v': %v", rn.RefID, err) + } + return gm, nil +} + +// NeedsVars returns the variable names (refIds) that are dependencies +// to execute the command and allows the command to fulfill the Command interface. +func (gm *MathCommand) NeedsVars() []string { + return gm.Expression.VarNames +} + +// Execute runs the command and returns the results or an error if the command +// failed to execute. +func (gm *MathCommand) Execute(ctx context.Context, vars mathexp.Vars) (mathexp.Results, error) { + return gm.Expression.Execute(gm.refID, vars) +} + +// ReduceCommand is an expression command for reduction of a timeseries such as a min, mean, or max. +type ReduceCommand struct { + Reducer string + VarToReduce string + refID string +} + +// NewReduceCommand creates a new ReduceCMD. +func NewReduceCommand(refID, reducer, varToReduce string) *ReduceCommand { + // TODO: validate reducer here, before execution + return &ReduceCommand{ + Reducer: reducer, + VarToReduce: varToReduce, + refID: refID, + } +} + +// UnmarshalReduceCommand creates a MathCMD from Grafana's frontend query. +func UnmarshalReduceCommand(rn *rawNode) (*ReduceCommand, error) { + rawVar, ok := rn.Query["expression"] + if !ok { + return nil, fmt.Errorf("no variable specified to reduce for refId %v", rn.RefID) + } + varToReduce, ok := rawVar.(string) + if !ok { + return nil, fmt.Errorf("expected reduce variable to be a string, got %T for refId %v", rawVar, rn.RefID) + } + varToReduce = strings.TrimPrefix(varToReduce, "$") + + rawReducer, ok := rn.Query["reducer"] + if !ok { + return nil, fmt.Errorf("no reducer specified for refId %v", rn.RefID) + } + redFunc, ok := rawReducer.(string) + if !ok { + return nil, fmt.Errorf("expected reducer to be a string, got %T for refId %v", rawReducer, rn.RefID) + } + + return NewReduceCommand(rn.RefID, redFunc, varToReduce), nil +} + +// NeedsVars returns the variable names (refIds) that are dependencies +// to execute the command and allows the command to fulfill the Command interface. +func (gr *ReduceCommand) NeedsVars() []string { + return []string{gr.VarToReduce} +} + +// Execute runs the command and returns the results or an error if the command +// failed to execute. +func (gr *ReduceCommand) Execute(ctx context.Context, vars mathexp.Vars) (mathexp.Results, error) { + newRes := mathexp.Results{} + for _, val := range vars[gr.VarToReduce].Values { + series, ok := val.(mathexp.Series) + if !ok { + return newRes, fmt.Errorf("can only reduce type series, got type %v", val.Type()) + } + num, err := series.Reduce(gr.refID, gr.Reducer) + if err != nil { + return newRes, err + } + newRes.Values = append(newRes.Values, num) + } + return newRes, nil +} + +// ResampleCommand is an expression command for resampling of a timeseries. +type ResampleCommand struct { + Window time.Duration + VarToResample string + Downsampler string + Upsampler string + TimeRange backend.TimeRange + refID string +} + +// NewResampleCommand creates a new ResampleCMD. +func NewResampleCommand(refID, rawWindow, varToResample string, downsampler string, upsampler string, tr backend.TimeRange) (*ResampleCommand, error) { + // TODO: validate reducer here, before execution + window, err := gtime.ParseDuration(rawWindow) + if err != nil { + return nil, fmt.Errorf(`failed to parse resample "window" duration field %q: %w`, window, err) + } + return &ResampleCommand{ + Window: window, + VarToResample: varToResample, + Downsampler: downsampler, + Upsampler: upsampler, + TimeRange: tr, + refID: refID, + }, nil +} + +// UnmarshalResampleCommand creates a ResampleCMD from Grafana's frontend query. +func UnmarshalResampleCommand(rn *rawNode) (*ResampleCommand, error) { + rawVar, ok := rn.Query["expression"] + if !ok { + return nil, fmt.Errorf("no variable to resample for refId %v", rn.RefID) + } + varToReduce, ok := rawVar.(string) + if !ok { + return nil, fmt.Errorf("expected resample input variable to be type string, but got type %T for refId %v", rawVar, rn.RefID) + } + varToReduce = strings.TrimPrefix(varToReduce, "$") + varToResample := varToReduce + + rawWindow, ok := rn.Query["window"] + if !ok { + return nil, fmt.Errorf("no time duration specified for the window in resample command for refId %v", rn.RefID) + } + window, ok := rawWindow.(string) + if !ok { + return nil, fmt.Errorf("expected resample window to be a string, got %T for refId %v", rawWindow, rn.RefID) + } + + rawDownsampler, ok := rn.Query["downsampler"] + if !ok { + return nil, fmt.Errorf("no downsampler function specified in resample command for refId %v", rn.RefID) + } + downsampler, ok := rawDownsampler.(string) + if !ok { + return nil, fmt.Errorf("expected resample downsampler to be a string, got type %T for refId %v", downsampler, rn.RefID) + } + + rawUpsampler, ok := rn.Query["upsampler"] + if !ok { + return nil, fmt.Errorf("no downsampler specified in resample command for refId %v", rn.RefID) + } + upsampler, ok := rawUpsampler.(string) + if !ok { + return nil, fmt.Errorf("expected resample downsampler to be a string, got type %T for refId %v", upsampler, rn.RefID) + } + + return NewResampleCommand(rn.RefID, window, varToResample, downsampler, upsampler, rn.TimeRange) +} + +// NeedsVars returns the variable names (refIds) that are dependencies +// to execute the command and allows the command to fulfill the Command interface. +func (gr *ResampleCommand) NeedsVars() []string { + return []string{gr.VarToResample} +} + +// Execute runs the command and returns the results or an error if the command +// failed to execute. +func (gr *ResampleCommand) Execute(ctx context.Context, vars mathexp.Vars) (mathexp.Results, error) { + newRes := mathexp.Results{} + for _, val := range vars[gr.VarToResample].Values { + series, ok := val.(mathexp.Series) + if !ok { + return newRes, fmt.Errorf("can only resample type series, got type %v", val.Type()) + } + num, err := series.Resample(gr.refID, gr.Window, gr.Downsampler, gr.Upsampler, gr.TimeRange) + if err != nil { + return newRes, err + } + newRes.Values = append(newRes.Values, num) + } + return newRes, nil +} + +// CommandType is the type of the expression command. +type CommandType int + +const ( + // TypeUnknown is the CMDType for an unrecognized expression type. + TypeUnknown CommandType = iota + // TypeMath is the CMDType for a math expression. + TypeMath + // TypeReduce is the CMDType for a reduction expression. + TypeReduce + // TypeResample is the CMDType for a resampling expression. + TypeResample +) + +func (gt CommandType) String() string { + switch gt { + case TypeMath: + return "math" + case TypeReduce: + return "reduce" + case TypeResample: + return "resample" + default: + return "unknown" + } +} + +// ParseCommandType returns a CommandType from its string representation. +func ParseCommandType(s string) (CommandType, error) { + switch s { + case "math": + return TypeMath, nil + case "reduce": + return TypeReduce, nil + case "resample": + return TypeResample, nil + default: + return TypeUnknown, fmt.Errorf("'%v' is not a recognized expression type", s) + } +} diff --git a/vendor/github.com/grafana/grafana/pkg/expr/graph.go b/vendor/github.com/grafana/grafana/pkg/expr/graph.go new file mode 100644 index 00000000000..3fb60c1dc6c --- /dev/null +++ b/vendor/github.com/grafana/grafana/pkg/expr/graph.go @@ -0,0 +1,183 @@ +package expr + +import ( + "context" + "encoding/json" + "fmt" + + "github.com/grafana/grafana-plugin-sdk-go/backend" + "github.com/grafana/grafana/pkg/expr/mathexp" + + "gonum.org/v1/gonum/graph" + "gonum.org/v1/gonum/graph/simple" + "gonum.org/v1/gonum/graph/topo" +) + +// NodeType is the type of a DPNode. Currently either a expression command or datasource query. +type NodeType int + +const ( + // TypeCMDNode is a NodeType for expression commands. + TypeCMDNode NodeType = iota + // TypeDatasourceNode is a NodeType for datasource queries. + TypeDatasourceNode +) + +// Node is a node in a Data Pipeline. Node is either a expression command or a datasource query. +type Node interface { + ID() int64 // ID() allows the gonum graph node interface to be fulfilled + NodeType() NodeType + RefID() string + Execute(c context.Context, vars mathexp.Vars) (mathexp.Results, error) + String() string +} + +// DataPipeline is an ordered set of nodes returned from DPGraph processing. +type DataPipeline []Node + +// execute runs all the command/datasource requests in the pipeline return a +// map of the refId of the of each command +func (dp *DataPipeline) execute(c context.Context) (mathexp.Vars, error) { + vars := make(mathexp.Vars) + for _, node := range *dp { + res, err := node.Execute(c, vars) + if err != nil { + return nil, err + } + + vars[node.RefID()] = res + } + return vars, nil +} + +// BuildPipeline builds a graph of the nodes, and returns the nodes in an +// executable order. +func buildPipeline(req *backend.QueryDataRequest) (DataPipeline, error) { + graph, err := buildDependencyGraph(req) + if err != nil { + return nil, err + } + + nodes, err := buildExecutionOrder(graph) + if err != nil { + return nil, err + } + + return nodes, nil +} + +// buildDependencyGraph returns a dependency graph for a set of queries. +func buildDependencyGraph(req *backend.QueryDataRequest) (*simple.DirectedGraph, error) { + graph, err := buildGraph(req) + if err != nil { + return nil, err + } + + registry := buildNodeRegistry(graph) + + if err := buildGraphEdges(graph, registry); err != nil { + return nil, err + } + + return graph, nil +} + +// buildExecutionOrder returns a sequence of nodes ordered by dependency. +func buildExecutionOrder(graph *simple.DirectedGraph) ([]Node, error) { + sortedNodes, err := topo.Sort(graph) + if err != nil { + return nil, err + } + + nodes := make([]Node, len(sortedNodes)) + for i, v := range sortedNodes { + nodes[i] = v.(Node) + } + + return nodes, nil +} + +// buildNodeRegistry returns a lookup table for reference IDs to respective node. +func buildNodeRegistry(g *simple.DirectedGraph) map[string]Node { + res := make(map[string]Node) + + nodeIt := g.Nodes() + + for nodeIt.Next() { + if dpNode, ok := nodeIt.Node().(Node); ok { + res[dpNode.RefID()] = dpNode + } + } + + return res +} + +// buildGraph creates a new graph populated with nodes for every query. +func buildGraph(req *backend.QueryDataRequest) (*simple.DirectedGraph, error) { + dp := simple.NewDirectedGraph() + + for _, query := range req.Queries { + rawQueryProp := make(map[string]interface{}) + err := json.Unmarshal(query.JSON, &rawQueryProp) + if err != nil { + return nil, err + } + rn := &rawNode{ + Query: rawQueryProp, + RefID: query.RefID, + TimeRange: query.TimeRange, + QueryType: query.QueryType, + } + + dsName, err := rn.GetDatasourceName() + if err != nil { + return nil, err + } + + var node graph.Node + switch dsName { + case DatasourceName: + node, err = buildCMDNode(dp, rn) + default: // If it's not an expression query, it's a data source query. + node, err = buildDSNode(dp, rn, req.PluginContext.OrgID) + } + if err != nil { + return nil, err + } + dp.AddNode(node) + } + return dp, nil +} + +// buildGraphEdges generates graph edges based on each node's dependencies. +func buildGraphEdges(dp *simple.DirectedGraph, registry map[string]Node) error { + nodeIt := dp.Nodes() + + for nodeIt.Next() { + node := nodeIt.Node().(Node) + + if node.NodeType() != TypeCMDNode { + // datasource node, nothing to do for now. Although if we want expression results to be + // used as datasource query params some day this will need change + continue + } + + cmdNode := node.(*CMDNode) + + for _, neededVar := range cmdNode.Command.NeedsVars() { + neededNode, ok := registry[neededVar] + if !ok { + return fmt.Errorf("unable to find dependent node '%v'", neededVar) + } + + if neededNode.ID() == cmdNode.ID() { + return fmt.Errorf("can not add self referencing node for var '%v' ", neededVar) + } + + edge := dp.NewEdge(neededNode, cmdNode) + + dp.SetEdge(edge) + } + } + return nil +} diff --git a/vendor/github.com/grafana/grafana/pkg/expr/mathexp/exp.go b/vendor/github.com/grafana/grafana/pkg/expr/mathexp/exp.go new file mode 100644 index 00000000000..fe18378fd80 --- /dev/null +++ b/vendor/github.com/grafana/grafana/pkg/expr/mathexp/exp.go @@ -0,0 +1,539 @@ +package mathexp + +import ( + "fmt" + "math" + "reflect" + "runtime" + + "github.com/grafana/grafana-plugin-sdk-go/data" + "github.com/grafana/grafana/pkg/expr/mathexp/parse" +) + +// Expr holds a parsed math command expression. +type Expr struct { + *parse.Tree +} + +// State embeds a parsed Expr with variables and their results +// so the expression can act on them. +type State struct { + *Expr + Vars Vars + // Could hold more properties that change behavior around: + // - Unions (How many result A and many Result B in case A + B are joined) + // - NaN/Null behavior + RefID string +} + +// Vars holds the results of datasource queries or other expression commands. +type Vars map[string]Results + +// New creates a new expression tree +func New(expr string, funcs ...map[string]parse.Func) (*Expr, error) { + funcs = append(funcs, builtins) + t, err := parse.Parse(expr, funcs...) + if err != nil { + return nil, err + } + e := &Expr{ + Tree: t, + } + return e, nil +} + +// Execute applies a parse expression to the context and executes it +func (e *Expr) Execute(refID string, vars Vars) (r Results, err error) { + s := &State{ + Expr: e, + Vars: vars, + RefID: refID, + } + return e.executeState(s) +} + +func (e *Expr) executeState(s *State) (r Results, err error) { + defer errRecover(&err, s) + r, err = s.walk(e.Tree.Root) + return +} + +// errRecover is the handler that turns panics into returns from the top +// level of Parse. +func errRecover(errp *error, s *State) { + e := recover() + if e != nil { + switch err := e.(type) { + case runtime.Error: + panic(e) + case error: + *errp = err + default: + panic(e) + } + } +} + +// walk is the top level function to walk a parsed expression +// with its associate variables +func (e *State) walk(node parse.Node) (res Results, err error) { + switch node := node.(type) { + case *parse.ScalarNode: + res = NewScalarResults(e.RefID, &node.Float64) + case *parse.VarNode: + res = e.Vars[node.Name] + case *parse.BinaryNode: + res, err = e.walkBinary(node) + case *parse.UnaryNode: + res, err = e.walkUnary(node) + case *parse.FuncNode: + res, err = e.walkFunc(node) + default: + return res, fmt.Errorf("expr: can not walk node type: %s", node.Type()) + } + return +} + +func (e *State) walkUnary(node *parse.UnaryNode) (Results, error) { + a, err := e.walk(node.Arg) + if err != nil { + return Results{}, err + } + newResults := Results{} + for _, val := range a.Values { + var newVal Value + switch rt := val.(type) { + case Scalar: + newVal = NewScalar(e.RefID, nil) + f := rt.GetFloat64Value() + if f != nil { + newF, err := unaryOp(node.OpStr, *f) + if err != nil { + return newResults, err + } + newVal = NewScalar(e.RefID, &newF) + } + case Number: + newVal, err = e.unaryNumber(rt, node.OpStr) + case Series: + newVal, err = e.unarySeries(rt, node.OpStr) + default: + return newResults, fmt.Errorf("can not perform a unary operation on type %v", rt.Type()) + } + if err != nil { + return newResults, err + } + newResults.Values = append(newResults.Values, newVal) + } + return newResults, nil +} + +func (e *State) unarySeries(s Series, op string) (Series, error) { + newSeries := NewSeries(e.RefID, s.GetLabels(), s.TimeIdx, s.TimeIsNullable, s.ValueIdx, s.ValueIsNullable, s.Len()) + for i := 0; i < s.Len(); i++ { + t, f := s.GetPoint(i) + if f == nil { + if err := newSeries.SetPoint(i, t, nil); err != nil { + return newSeries, err + } + continue + } + newF, err := unaryOp(op, *f) + if err != nil { + return newSeries, err + } + if err := newSeries.SetPoint(i, t, &newF); err != nil { + return newSeries, err + } + } + return newSeries, nil +} + +func (e *State) unaryNumber(n Number, op string) (Number, error) { + newNumber := NewNumber(e.RefID, n.GetLabels()) + + f := n.GetFloat64Value() + if f != nil { + newF, err := unaryOp(op, *f) + if err != nil { + return newNumber, err + } + newNumber.SetValue(&newF) + } + return newNumber, nil +} + +// unaryOp performs a unary operation on a float. +func unaryOp(op string, a float64) (r float64, err error) { + if math.IsNaN(a) { + return math.NaN(), nil + } + switch op { + case "!": + if a == 0 { + r = 1 + } else { + r = 0 + } + case "-": + r = -a + default: + return r, fmt.Errorf("expr: unknown unary operator %s", op) + } + return +} + +// Union holds to Values from Two sets where their labels are compatible (TODO: define compatible). +// This is a intermediate container for Binary operations such (e.g. A + B). +type Union struct { + Labels data.Labels + A, B Value +} + +// union creates Union objects based on the Labels attached to each Series or Number +// within a collection of Series or Numbers. The Unions are used with binary +// operations. The labels of the Union will the taken from result with a greater +// number of tags. +func union(aResults, bResults Results) []*Union { + unions := []*Union{} + if len(aResults.Values) == 0 || len(bResults.Values) == 0 { + return unions + } + for _, a := range aResults.Values { + for _, b := range bResults.Values { + var labels data.Labels + aLabels := a.GetLabels() + bLabels := b.GetLabels() + switch { + case aLabels.Equals(bLabels) || len(aLabels) == 0 || len(bLabels) == 0: + l := aLabels + if len(aLabels) == 0 { + l = bLabels + } + labels = l + case len(aLabels) == len(bLabels): + continue // invalid union, drop for now + case aLabels.Contains(bLabels): + labels = aLabels + case bLabels.Contains(aLabels): + labels = bLabels + default: + continue + } + u := &Union{ + Labels: labels, + A: a, + B: b, + } + unions = append(unions, u) + } + } + if len(unions) == 0 && len(aResults.Values) == 1 && len(bResults.Values) == 1 { + // In the case of only 1 thing on each side of the operator, we combine them + // and strip the tags. + // This isn't ideal for understanding behavior, but will make more stuff work when + // combining different datasources without munging. + // This choice is highly questionable in the long term. + unions = append(unions, &Union{ + A: aResults.Values[0], + B: bResults.Values[0], + }) + } + return unions +} + +func (e *State) walkBinary(node *parse.BinaryNode) (Results, error) { + res := Results{Values{}} + ar, err := e.walk(node.Args[0]) + if err != nil { + return res, err + } + br, err := e.walk(node.Args[1]) + if err != nil { + return res, err + } + unions := union(ar, br) + for _, uni := range unions { + var value Value + switch at := uni.A.(type) { + case Scalar: + aFloat := at.GetFloat64Value() + switch bt := uni.B.(type) { + // Scalar op Scalar + case Scalar: + bFloat := bt.GetFloat64Value() + if aFloat == nil || bFloat == nil { + value = NewScalar(e.RefID, nil) + break + } + f := math.NaN() + if aFloat != nil && bFloat != nil { + f, err = binaryOp(node.OpStr, *aFloat, *bFloat) + if err != nil { + return res, err + } + } + value = NewScalar(e.RefID, &f) + // Scalar op Scalar + case Number: + value, err = e.biScalarNumber(uni.Labels, node.OpStr, bt, aFloat, false) + // Scalar op Series + case Series: + value, err = e.biSeriesNumber(uni.Labels, node.OpStr, bt, aFloat, false) + default: + return res, fmt.Errorf("not implemented: binary %v on %T and %T", node.OpStr, uni.A, uni.B) + } + case Series: + switch bt := uni.B.(type) { + // Series Op Scalar + case Scalar: + bFloat := bt.GetFloat64Value() + value, err = e.biSeriesNumber(uni.Labels, node.OpStr, at, bFloat, true) + // case Series Op Number + case Number: + bFloat := bt.GetFloat64Value() + value, err = e.biSeriesNumber(uni.Labels, node.OpStr, at, bFloat, true) + // case Series op Series + case Series: + value, err = e.biSeriesSeries(uni.Labels, node.OpStr, at, bt) + default: + return res, fmt.Errorf("not implemented: binary %v on %T and %T", node.OpStr, uni.A, uni.B) + } + case Number: + aFloat := at.GetFloat64Value() + switch bt := uni.B.(type) { + case Scalar: + bFloat := bt.GetFloat64Value() + value, err = e.biScalarNumber(uni.Labels, node.OpStr, at, bFloat, true) + case Number: + bFloat := bt.GetFloat64Value() + value, err = e.biScalarNumber(uni.Labels, node.OpStr, at, bFloat, true) + case Series: + value, err = e.biSeriesNumber(uni.Labels, node.OpStr, bt, aFloat, false) + default: + return res, fmt.Errorf("not implemented: binary %v on %T and %T", node.OpStr, uni.A, uni.B) + } + default: + return res, fmt.Errorf("not implemented: binary %v on %T and %T", node.OpStr, uni.A, uni.B) + } + if err != nil { + return res, err + } + res.Values = append(res.Values, value) + } + return res, nil +} + +// binaryOp performs a binary operations (e.g. A+B or A>B) on two +// float values +// nolint:gocyclo +func binaryOp(op string, a, b float64) (r float64, err error) { + // Test short circuit before NaN. + switch op { + case "||": + if a != 0 { + return 1, nil + } + case "&&": + if a == 0 { + return 0, nil + } + } + if math.IsNaN(a) || math.IsNaN(b) { + return math.NaN(), nil + } + switch op { + case "+": + r = a + b + case "*": + r = a * b + case "-": + r = a - b + case "/": + r = a / b + case "**": + r = math.Pow(a, b) + case "%": + r = math.Mod(a, b) + case "==": + if a == b { + r = 1 + } else { + r = 0 + } + case ">": + if a > b { + r = 1 + } else { + r = 0 + } + case "!=": + if a != b { + r = 1 + } else { + r = 0 + } + case "<": + if a < b { + r = 1 + } else { + r = 0 + } + case ">=": + if a >= b { + r = 1 + } else { + r = 0 + } + case "<=": + if a <= b { + r = 1 + } else { + r = 0 + } + case "||": + if a != 0 || b != 0 { + r = 1 + } else { + r = 0 + } + case "&&": + if a != 0 && b != 0 { + r = 1 + } else { + r = 0 + } + default: + return r, fmt.Errorf("expr: unknown operator %s", op) + } + return r, nil +} + +func (e *State) biScalarNumber(labels data.Labels, op string, number Number, scalarVal *float64, numberFirst bool) (Number, error) { + newNumber := NewNumber(e.RefID, labels) + f := number.GetFloat64Value() + if f == nil || scalarVal == nil { + newNumber.SetValue(nil) + return newNumber, nil + } + nF := math.NaN() + var err error + if numberFirst { + nF, err = binaryOp(op, *f, *scalarVal) + } else { + nF, err = binaryOp(op, *scalarVal, *f) + } + if err != nil { + return newNumber, err + } + newNumber.SetValue(&nF) + return newNumber, nil +} + +func (e *State) biSeriesNumber(labels data.Labels, op string, s Series, scalarVal *float64, seriesFirst bool) (Series, error) { + newSeries := NewSeries(e.RefID, labels, s.TimeIdx, s.TimeIsNullable, s.ValueIdx, s.ValueIsNullable, s.Len()) + var err error + for i := 0; i < s.Len(); i++ { + nF := math.NaN() + t, f := s.GetPoint(i) + if f == nil || scalarVal == nil { + if err := newSeries.SetPoint(i, t, nil); err != nil { + return newSeries, err + } + continue + } + if seriesFirst { + nF, err = binaryOp(op, *f, *scalarVal) + } else { + nF, err = binaryOp(op, *scalarVal, *f) + } + if err != nil { + return newSeries, err + } + if err := newSeries.SetPoint(i, t, &nF); err != nil { + return newSeries, err + } + } + return newSeries, nil +} + +// ... if would you like some series with your series and then get some series, or is that enough series? +// biSeriesSeries performs a the binary operation for each value in the two series where the times +// are equal. If there are datapoints in A or B that do not share a time, they will be dropped. +func (e *State) biSeriesSeries(labels data.Labels, op string, aSeries, bSeries Series) (Series, error) { + bPoints := make(map[string]*float64) + for i := 0; i < bSeries.Len(); i++ { + t, f := bSeries.GetPoint(i) + if t != nil { + bPoints[t.UTC().String()] = f + } + } + + newSeries := NewSeries( + e.RefID, labels, aSeries.TimeIdx, aSeries.TimeIsNullable || bSeries.TimeIsNullable, aSeries.ValueIdx, + aSeries.ValueIsNullable || bSeries.ValueIsNullable, 0, + ) + for aIdx := 0; aIdx < aSeries.Len(); aIdx++ { + aTime, aF := aSeries.GetPoint(aIdx) + bF, ok := bPoints[aTime.UTC().String()] + if !ok { + continue + } + if aF == nil || bF == nil { + if err := newSeries.AppendPoint(aIdx, aTime, nil); err != nil { + return newSeries, err + } + continue + } + nF, err := binaryOp(op, *aF, *bF) + if err != nil { + return newSeries, err + } + if err := newSeries.AppendPoint(aIdx, aTime, &nF); err != nil { + return newSeries, err + } + } + return newSeries, nil +} + +func (e *State) walkFunc(node *parse.FuncNode) (Results, error) { + var res Results + var err error + var in []reflect.Value + for _, a := range node.Args { + var v interface{} + switch t := a.(type) { + case *parse.StringNode: + v = t.Text + case *parse.VarNode: + v = e.Vars[t.Name] + case *parse.ScalarNode: + v = NewScalarResults(e.RefID, &t.Float64) + case *parse.FuncNode: + v, err = e.walkFunc(t) + case *parse.UnaryNode: + v, err = e.walkUnary(t) + case *parse.BinaryNode: + v, err = e.walkBinary(t) + default: + return res, fmt.Errorf("expr: unknown func arg type: %T", t) + } + if err != nil { + return res, err + } + in = append(in, reflect.ValueOf(v)) + } + + f := reflect.ValueOf(node.F.F) + + fr := f.Call(append([]reflect.Value{reflect.ValueOf(e)}, in...)) + + res = fr[0].Interface().(Results) + if len(fr) > 1 && !fr[1].IsNil() { + err := fr[1].Interface().(error) + if err != nil { + return res, err + } + } + return res, nil +} diff --git a/vendor/github.com/grafana/grafana/pkg/expr/mathexp/funcs.go b/vendor/github.com/grafana/grafana/pkg/expr/mathexp/funcs.go new file mode 100644 index 00000000000..d9a7ddb38bb --- /dev/null +++ b/vendor/github.com/grafana/grafana/pkg/expr/mathexp/funcs.go @@ -0,0 +1,118 @@ +package mathexp + +import ( + "math" + + "github.com/grafana/grafana/pkg/expr/mathexp/parse" +) + +var builtins = map[string]parse.Func{ + "abs": { + Args: []parse.ReturnType{parse.TypeVariantSet}, + VariantReturn: true, + F: abs, + }, + "log": { + Args: []parse.ReturnType{parse.TypeVariantSet}, + VariantReturn: true, + F: log, + }, + "nan": { + Return: parse.TypeScalar, + F: nan, + }, + "inf": { + Return: parse.TypeScalar, + F: inf, + }, + "null": { + Return: parse.TypeScalar, + F: null, + }, +} + +// abs returns the absolute value for each result in NumberSet, SeriesSet, or Scalar +func abs(e *State, varSet Results) (Results, error) { + newRes := Results{} + for _, res := range varSet.Values { + newVal, err := perFloat(e, res, math.Abs) + if err != nil { + return newRes, err + } + newRes.Values = append(newRes.Values, newVal) + } + return newRes, nil +} + +// log returns the natural logarithm value for each result in NumberSet, SeriesSet, or Scalar +func log(e *State, varSet Results) (Results, error) { + newRes := Results{} + for _, res := range varSet.Values { + newVal, err := perFloat(e, res, math.Log) + if err != nil { + return newRes, err + } + newRes.Values = append(newRes.Values, newVal) + } + return newRes, nil +} + +// nan returns a scalar nan value +func nan(e *State) Results { + aNaN := math.NaN() + return NewScalarResults(e.RefID, &aNaN) +} + +// inf returns a scalar positive infinity value +func inf(e *State) Results { + aInf := math.Inf(0) + return NewScalarResults(e.RefID, &aInf) +} + +// null returns a null scalar value +func null(e *State) Results { + return NewScalarResults(e.RefID, nil) +} + +func perFloat(e *State, val Value, floatF func(x float64) float64) (Value, error) { + var newVal Value + switch val.Type() { + case parse.TypeNumberSet: + n := NewNumber(e.RefID, val.GetLabels()) + f := val.(Number).GetFloat64Value() + nF := math.NaN() + if f != nil { + nF = floatF(*f) + } + n.SetValue(&nF) + newVal = n + case parse.TypeScalar: + f := val.(Scalar).GetFloat64Value() + nF := math.NaN() + if f != nil { + nF = floatF(*f) + } + newVal = NewScalar(e.RefID, &nF) + case parse.TypeSeriesSet: + resSeries := val.(Series) + newSeries := NewSeries( + e.RefID, resSeries.GetLabels(), resSeries.TimeIdx, resSeries.TimeIsNullable, resSeries.ValueIdx, + resSeries.ValueIsNullable, resSeries.Len(), + ) + for i := 0; i < resSeries.Len(); i++ { + t, f := resSeries.GetPoint(i) + nF := math.NaN() + if f != nil { + nF = floatF(*f) + } + if err := newSeries.SetPoint(i, t, &nF); err != nil { + return newSeries, err + } + } + newVal = newSeries + default: + // TODO: Should we deal with TypeString, TypeVariantSet? + } + + return newVal, nil +} diff --git a/vendor/github.com/grafana/grafana/pkg/expr/mathexp/parse/lex.go b/vendor/github.com/grafana/grafana/pkg/expr/mathexp/parse/lex.go new file mode 100644 index 00000000000..fc48211a741 --- /dev/null +++ b/vendor/github.com/grafana/grafana/pkg/expr/mathexp/parse/lex.go @@ -0,0 +1,354 @@ +// 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 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 + itemEOF + itemNot // '!' + itemAnd // '&&' + itemOr // '||' + itemGreater // '>' + itemLess // '<' + itemGreaterEq // '>=' + itemLessEq // '<=' + itemEq // '==' + itemNotEq // '!=' + itemPlus // '+' + itemMinus // '-' + itemMult // '*' + itemDiv // '/' + itemMod // '%' + itemNumber // simple number + itemComma + itemLeftParen + itemRightParen + itemString + itemFunc + itemVar // e.g. $A + itemPow // '**' +) + +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 { + input string // the string being scanned + 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 +} + +// 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. +// nolint:unused +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 +} + +// accept consumes the next rune if it's from the valid set. +func (l *lexer) accept(valid string) bool { + if strings.ContainsRune(valid, l.next()) { + return true + } + l.backup() + return false +} + +// acceptRun consumes a run of runes from the valid set. +func (l *lexer) acceptRun(valid string) { + for strings.ContainsRune(valid, l.next()) { + } + l.backup() +} + +// ignore skips over the pending input before this point. +func (l *lexer) ignore() { + l.start = l.pos +} + +// 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. +// nolint:unused +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(input string) *lexer { + l := &lexer{ + input: input, + items: make(chan item), + } + go l.run() + return l +} + +// run runs the state machine for the lexer. +func (l *lexer) run() { + for l.state = lexItem; l.state != nil; { + l.state = l.state(l) + } +} + +// state functions + +func lexItem(l *lexer) stateFn { +Loop: + for { + switch r := l.next(); { + case r == '$': + return lexVar + case isSymbol(r): + return lexSymbol + case isNumber(r): + l.backup() + return lexNumber + case unicode.IsLetter(r): + return lexFunc + case r == '(': + l.emit(itemLeftParen) + case r == ')': + l.emit(itemRightParen) + case r == '"': + return lexString + case r == ',': + l.emit(itemComma) + case isSpace(r): + l.ignore() + case r == eof: + l.emit(itemEOF) + break Loop + default: + return l.errorf("invalid character: %s", string(r)) + } + } + return nil +} + +// 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]) + } + l.emit(itemNumber) + return lexItem +} + +func (l *lexer) scanNumber() bool { + // 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") + } + return true +} + +const symbols = "!<>=&|+-*/%" + +func lexSymbol(l *lexer) stateFn { + l.acceptRun(symbols) + s := l.input[l.start:l.pos] + switch s { + case "!": + l.emit(itemNot) + case "&&": + l.emit(itemAnd) + case "||": + l.emit(itemOr) + case ">": + l.emit(itemGreater) + case "<": + l.emit(itemLess) + case ">=": + l.emit(itemGreaterEq) + case "<=": + l.emit(itemLessEq) + case "==": + l.emit(itemEq) + case "!=": + l.emit(itemNotEq) + case "+": + l.emit(itemPlus) + case "-": + l.emit(itemMinus) + case "*": + l.emit(itemMult) + case "**": + l.emit(itemPow) + case "/": + l.emit(itemDiv) + case "%": + l.emit(itemMod) + default: + l.emit(itemError) + } + return lexItem +} + +func lexFunc(l *lexer) stateFn { + for { + switch r := l.next(); { + case unicode.IsLetter(r): + // absorb + default: + l.backup() + l.emit(itemFunc) + return lexItem + } + } +} + +func lexVar(l *lexer) stateFn { + hasChar := false + if l.peek() == '{' { + _ = l.next() + for { + switch r := l.next(); { + case r == '}': + if !hasChar { + return l.errorf("incomplete variable") + } + l.emit(itemVar) + return lexItem + case r == eof: + return l.errorf("unterminated variable missing closing }") + case isVarchar(r) || isSpace(r): + hasChar = true + default: + return l.errorf("unsupported variable character") + } + } + } + + for { + switch r := l.next(); { + case isVarchar(r): + hasChar = true + // absorb + default: + if !hasChar { + return l.errorf("incomplete variable") + } + l.backup() + l.emit(itemVar) + return lexItem + } + } +} + +func lexString(l *lexer) stateFn { + for { + switch l.next() { + case '"': + l.emit(itemString) + return lexItem + case eof: + return l.errorf("unterminated string") + } + } +} + +// isSpace reports whether r is a space character. +func isSpace(r rune) bool { + return unicode.IsSpace(r) +} + +func isVarchar(r rune) bool { + return r == '_' || unicode.IsLetter(r) || unicode.IsDigit(r) +} + +func isSymbol(r rune) bool { + return strings.ContainsRune(symbols, r) +} + +func isNumber(r rune) bool { + return unicode.IsDigit(r) || r == '.' +} diff --git a/vendor/github.com/grafana/grafana/pkg/expr/mathexp/parse/node.go b/vendor/github.com/grafana/grafana/pkg/expr/mathexp/parse/node.go new file mode 100644 index 00000000000..711d0bf65e9 --- /dev/null +++ b/vendor/github.com/grafana/grafana/pkg/expr/mathexp/parse/node.go @@ -0,0 +1,422 @@ +// 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 ( + "fmt" + "strconv" +) + +// 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 + StringAST() string + Position() Pos // byte position of start of node in full original input string + Check(*Tree) error // performs type checking for itself and sub-nodes + Return() ReturnType + + // Make sure only functions in this package can create Nodes. + unexported() +} + +// 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 + +// Position returns the integer Position of p +func (p Pos) Position() Pos { + return p +} + +// unexported keeps Node implementations local to the package. +// All implementations embed Pos, so this takes care of it. +func (Pos) unexported() { +} + +// 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 ( + // NodeFunc is a function call. + NodeFunc NodeType = iota + // NodeBinary is a binary operator: math, logical, compare + NodeBinary + // NodeUnary is unary operator: !, - + NodeUnary + // NodeString is string constant. + NodeString + // NodeNumber is a numerical constant (Scalar). + NodeNumber + // NodeVar is variable: $A + NodeVar +) + +// String returns the string representation of the NodeType +func (t NodeType) String() string { + switch t { + case NodeFunc: + return "NodeFunc" + case NodeBinary: + return "NodeBinary" + case NodeUnary: + return "NodeUnary" + case NodeString: + return "NodeString" + case NodeNumber: + return "NodeNumber" + default: + return "NodeUnknown" + } +} + +// Nodes. + +// VarNode holds a variable reference. +type VarNode struct { + NodeType + Pos + Name string // Without the $ or {} + Text string // Raw +} + +func newVar(pos Pos, name, text string) *VarNode { + return &VarNode{NodeType: NodeVar, Pos: pos, Name: name, Text: text} +} + +// Type returns the Type of the VarNode so it fulfills the Node interface. +func (n *VarNode) Type() NodeType { return NodeVar } + +// String returns the string representation of the VarNode so it fulfills the Node interface. +func (n *VarNode) String() string { return n.Text } + +// StringAST returns the string representation of abstract syntax tree of the VarNode so it fulfills the Node interface. +func (n *VarNode) StringAST() string { return n.String() } + +// Check performs parse time checking on the VarNode so it fulfills the Node interface. +func (n *VarNode) Check(*Tree) error { + return nil +} + +// Return returns the result type of the VarNode so it fulfills the Node interface. +func (n *VarNode) Return() ReturnType { + return TypeSeriesSet // Vars are only time series for now I guess.... +} + +// FuncNode holds a function invocation. +type FuncNode struct { + NodeType + Pos + Name string + F *Func + Args []Node + Prefix string +} + +func newFunc(pos Pos, name string, f Func) *FuncNode { + return &FuncNode{NodeType: NodeFunc, Pos: pos, Name: name, F: &f} +} + +func (f *FuncNode) append(arg Node) { + f.Args = append(f.Args, arg) +} + +// String returns the string representation of the FuncNode so it fulfills the Node interface. +func (f *FuncNode) String() string { + s := f.Name + "(" + for i, arg := range f.Args { + if i > 0 { + s += ", " + } + s += arg.String() + } + s += ")" + return s +} + +// StringAST returns the string representation of abstract syntax tree of the FuncNode so it fulfills the Node interface. +func (f *FuncNode) StringAST() string { + s := f.Name + "(" + for i, arg := range f.Args { + if i > 0 { + s += ", " + } + s += arg.StringAST() + } + s += ")" + return s +} + +// Check performs parse time checking on the FuncNode so it fulfills the Node interface. +func (f *FuncNode) Check(t *Tree) error { + if len(f.Args) < len(f.F.Args) { + return fmt.Errorf("parse: not enough arguments for %s", f.Name) + } else if len(f.Args) > len(f.F.Args) { + return fmt.Errorf("parse: too many arguments for %s", f.Name) + } + + for i, arg := range f.Args { + funcType := f.F.Args[i] + argType := arg.Return() + // if funcType == TypeNumberSet && argType == TypeScalar { + // argType = TypeNumberSet + // } + if funcType == TypeVariantSet { + if !(argType == TypeNumberSet || argType == TypeSeriesSet || argType == TypeScalar) { + return fmt.Errorf("parse: expected %v or %v for argument %v, got %v", TypeNumberSet, TypeSeriesSet, i, argType) + } + } else if funcType != argType { + return fmt.Errorf("parse: expected %v, got %v for argument %v (%v)", funcType, argType, i, arg.String()) + } + if err := arg.Check(t); err != nil { + return err + } + } + + if f.F.Check != nil { + return f.F.Check(t, f) + } + return nil +} + +// Return returns the result type of the FuncNode so it fulfills the Node interface. +func (f *FuncNode) Return() ReturnType { + return f.F.Return +} + +// ScalarNode holds a number: signed or unsigned integer or float. +// 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 ScalarNode struct { + NodeType + Pos + IsUint bool // Number has an unsigned integral value. + IsFloat bool // Number has a floating-point value. + Uint64 uint64 // The unsigned integer value. + Float64 float64 // The floating-point value. + Text string // The original textual representation from the input. +} + +func newNumber(pos Pos, text string) (*ScalarNode, error) { + n := &ScalarNode{NodeType: NodeNumber, Pos: pos, Text: text} + // Do integer test first so we get 0x123 etc. + u, err := strconv.ParseUint(text, 0, 64) // will fail for -0. + if err == nil { + n.IsUint = true + n.Uint64 = u + } + // If an integer extraction succeeded, promote the float. + 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.IsUint && float64(uint64(f)) == f { + n.IsUint = true + n.Uint64 = uint64(f) + } + } + } + if !n.IsUint && !n.IsFloat { + return nil, fmt.Errorf("illegal number syntax: %q", text) + } + return n, nil +} + +// String returns the string representation of the ScalarNode so it fulfills the Node interface. +func (n *ScalarNode) String() string { + return n.Text +} + +// StringAST returns the string representation of abstract syntax tree of the ScalarNode so it fulfills the Node interface. +func (n *ScalarNode) StringAST() string { + return n.String() +} + +// Check performs parse time checking on the ScalarNode so it fulfills the Node interface. +func (n *ScalarNode) Check(*Tree) error { + return nil +} + +// Return returns the result type of the ScalarNode so it fulfills the Node interface. +func (n *ScalarNode) Return() ReturnType { + return TypeScalar +} + +// StringNode holds a string constant. The value has been "unquoted". +type StringNode struct { + NodeType + Pos + Quoted string // The original text of the string, with quotes. + Text string // The string, after quote processing. +} + +func newString(pos Pos, orig, text string) *StringNode { + return &StringNode{NodeType: NodeString, Pos: pos, Quoted: orig, Text: text} +} + +// String returns the string representation of the StringNode so it fulfills the Node interface. +func (s *StringNode) String() string { + return s.Quoted +} + +// StringAST returns the string representation of abstract syntax tree of the StringNode so it fulfills the Node interface. +func (s *StringNode) StringAST() string { + return s.String() +} + +// Check performs parse time checking on the StringNode so it fulfills the Node interface. +func (s *StringNode) Check(*Tree) error { + return nil +} + +// Return returns the result type of the TypeString so it fulfills the Node interface. +func (s *StringNode) Return() ReturnType { + return TypeString +} + +// BinaryNode holds two arguments and an operator. +type BinaryNode struct { + NodeType + Pos + Args [2]Node + Operator item + OpStr string +} + +func newBinary(operator item, arg1, arg2 Node) *BinaryNode { + return &BinaryNode{NodeType: NodeBinary, Pos: operator.pos, Args: [2]Node{arg1, arg2}, Operator: operator, OpStr: operator.val} +} + +// String returns the string representation of the BinaryNode so it fulfills the Node interface. +func (b *BinaryNode) String() string { + return fmt.Sprintf("%s %s %s", b.Args[0], b.Operator.val, b.Args[1]) +} + +// StringAST returns the string representation of abstract syntax tree of the BinaryNode so it fulfills the Node interface. +func (b *BinaryNode) StringAST() string { + return fmt.Sprintf("%s(%s, %s)", b.Operator.val, b.Args[0], b.Args[1]) +} + +// Check performs parse time checking on the BinaryNode so it fulfills the Node interface. +func (b *BinaryNode) Check(t *Tree) error { + return nil +} + +// Return returns the result type of the BinaryNode so it fulfills the Node interface. +func (b *BinaryNode) Return() ReturnType { + t0 := b.Args[0].Return() + t1 := b.Args[1].Return() + if t1 > t0 { + return t1 + } + return t0 +} + +// UnaryNode holds one argument and an operator. +type UnaryNode struct { + NodeType + Pos + Arg Node + Operator item + OpStr string +} + +func newUnary(operator item, arg Node) *UnaryNode { + return &UnaryNode{NodeType: NodeUnary, Pos: operator.pos, Arg: arg, Operator: operator, OpStr: operator.val} +} + +// String returns the string representation of the UnaryNode so it fulfills the Node interface. +func (u *UnaryNode) String() string { + return fmt.Sprintf("%s%s", u.Operator.val, u.Arg) +} + +// StringAST returns the string representation of abstract syntax tree of the UnaryNode so it fulfills the Node interface. +func (u *UnaryNode) StringAST() string { + return fmt.Sprintf("%s(%s)", u.Operator.val, u.Arg) +} + +// Check performs parse time checking on the UnaryNode so it fulfills the Node interface. +func (u *UnaryNode) Check(t *Tree) error { + switch rt := u.Arg.Return(); rt { + case TypeNumberSet, TypeSeriesSet, TypeScalar: + return u.Arg.Check(t) + default: + return fmt.Errorf(`parse: type error in %s, expected "number", got %s`, u, rt) + } +} + +// Return returns the result type of the UnaryNode so it fulfills the Node interface. +func (u *UnaryNode) Return() ReturnType { + return u.Arg.Return() +} + +// Walk invokes f on n and sub-nodes of n. +func Walk(n Node, f func(Node)) { + f(n) + switch n := n.(type) { + case *BinaryNode: + Walk(n.Args[0], f) + Walk(n.Args[1], f) + case *FuncNode: + for _, a := range n.Args { + Walk(a, f) + } + case *ScalarNode, *StringNode: + // Ignore since these node types have no sub nodes. + case *UnaryNode: + Walk(n.Arg, f) + default: + panic(fmt.Errorf("other type: %T", n)) + } +} + +// ReturnType represents the type that is returned from a node. +type ReturnType int + +const ( + // TypeString is a single string. + TypeString ReturnType = iota + // TypeScalar is a unlabled number constant. + TypeScalar + // TypeNumberSet is a collection of labelled numbers. + TypeNumberSet + // TypeSeriesSet is a collection of labelled time series. + TypeSeriesSet + // TypeVariantSet is a collection of the same type Number, Series, or Scalar. + TypeVariantSet +) + +// String returns a string representation of the ReturnType. +func (f ReturnType) String() string { + switch f { + case TypeNumberSet: + return "numberSet" + case TypeString: + return "string" + case TypeSeriesSet: + return "seriesSet" + case TypeScalar: + return "scalar" + case TypeVariantSet: + return "variant" + default: + return "unknown" + } +} diff --git a/vendor/github.com/grafana/grafana/pkg/expr/mathexp/parse/parse.go b/vendor/github.com/grafana/grafana/pkg/expr/mathexp/parse/parse.go new file mode 100644 index 00000000000..4652390f29c --- /dev/null +++ b/vendor/github.com/grafana/grafana/pkg/expr/mathexp/parse/parse.go @@ -0,0 +1,354 @@ +// 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 expressions as defined by expr. Clients +// should use that package to construct expressions rather than this one, which +// provides shared internal data structures not intended for general use. +package parse + +import ( + "fmt" + "runtime" + "strconv" + "strings" +) + +// Tree is the representation of a single parsed expression. +type Tree struct { + Text string // text parsed to create the expression. + Root Node // top-level root of the tree, returns a number. + VarNames []string + + funcs []map[string]Func + + // Parsing only; cleared after parse. + lex *lexer + token [1]item // one-token lookahead for parser. + peekCount int +} + +// Func holds the structure of a parsed function call. +type Func struct { + Args []ReturnType + Return ReturnType + F interface{} + VariantReturn bool + Check func(*Tree, *FuncNode) error +} + +// Parse returns a Tree, created by parsing the expression described in the +// argument string. If an error is encountered, parsing stops and an empty Tree +// is returned with the error. +func Parse(text string, funcs ...map[string]Func) (t *Tree, err error) { + t = New() + t.Text = text + err = t.Parse(text, 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++ +} + +// 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] +} + +// Parsing. + +// New allocates a new parse tree with the given name. +func New(funcs ...map[string]Func) *Tree { + return &Tree{ + funcs: funcs, + } +} + +// errorf formats the error and terminates processing. +func (t *Tree) errorf(format string, args ...interface{}) { + t.Root = nil + format = fmt.Sprintf("expr: %s", 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.next() + if token.typ != expected { + t.unexpected(token, context) + } + return token +} + +// expectOneOf consumes the next token and guarantees it has one of the required types. +// nolint:unused +func (t *Tree) expectOneOf(expected1, expected2 itemType, context string) item { + token := t.next() + 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) + } +} + +// startParse initializes the parser, using the lexer. +func (t *Tree) startParse(funcs []map[string]Func, lex *lexer) { + t.Root = nil + t.lex = lex + t.funcs = funcs +} + +// stopParse terminates parsing. +func (t *Tree) stopParse() { + t.lex = nil +} + +// Parse parses the expression definition string to construct a representation +// of the expression for execution. +func (t *Tree) Parse(text string, funcs ...map[string]Func) (err error) { + defer t.recover(&err) + t.startParse(funcs, lex(text)) + t.Text = text + t.parse() + t.stopParse() + return nil +} + +// parse is the top-level parser for an expression. +// It runs to EOF. +func (t *Tree) parse() { + t.Root = t.O() + t.expect(itemEOF, "root input") + if err := t.Root.Check(t); err != nil { + t.error(err) + } +} + +/* Grammar: +O -> A {"||" A} +A -> C {"&&" C} +C -> P {( "==" | "!=" | ">" | ">=" | "<" | "<=") P} +P -> M {( "+" | "-" ) M} +M -> E {( "*" | "/" ) F} +E -> F {( "**" ) F} +F -> v | "(" O ")" | "!" O | "-" O +v -> number | func(..) | queryVar +Func -> name "(" param {"," param} ")" +param -> number | "string" | queryVar +*/ + +// expr: + +// O is A {"||" A} in the grammar. +func (t *Tree) O() Node { + n := t.A() + for { + switch t.peek().typ { + case itemOr: + n = newBinary(t.next(), n, t.A()) + default: + return n + } + } +} + +// A is C {"&&" C} in the grammar. +func (t *Tree) A() Node { + n := t.C() + for { + switch t.peek().typ { + case itemAnd: + n = newBinary(t.next(), n, t.C()) + default: + return n + } + } +} + +// C is C -> P {( "==" | "!=" | ">" | ">=" | "<" | "<=") P} in the grammar. +func (t *Tree) C() Node { + n := t.P() + for { + switch t.peek().typ { + case itemEq, itemNotEq, itemGreater, itemGreaterEq, itemLess, itemLessEq: + n = newBinary(t.next(), n, t.P()) + default: + return n + } + } +} + +// P is M {( "+" | "-" ) M} in the grammar. +func (t *Tree) P() Node { + n := t.M() + for { + switch t.peek().typ { + case itemPlus, itemMinus: + n = newBinary(t.next(), n, t.M()) + default: + return n + } + } +} + +// M is E {( "*" | "/" ) F} in the grammar. +func (t *Tree) M() Node { + n := t.E() + for { + switch t.peek().typ { + case itemMult, itemDiv, itemMod: + n = newBinary(t.next(), n, t.E()) + default: + return n + } + } +} + +// E is F {( "**" ) F} in the grammar. +func (t *Tree) E() Node { + n := t.F() + for { + switch t.peek().typ { + case itemPow: + n = newBinary(t.next(), n, t.F()) + default: + return n + } + } +} + +// F is v | "(" O ")" | "!" O | "-" O in the grammar. +func (t *Tree) F() Node { + switch token := t.peek(); token.typ { + case itemNumber, itemFunc, itemVar: + return t.v() + case itemNot, itemMinus: + return newUnary(t.next(), t.F()) + case itemLeftParen: + t.next() + n := t.O() + t.expect(itemRightParen, "input: F()") + return n + default: + t.unexpected(token, "input: F()") + } + return nil +} + +// V is number | func(..) | queryVar in the grammar. +func (t *Tree) v() Node { + switch token := t.next(); token.typ { + case itemNumber: + n, err := newNumber(token.pos, token.val) + if err != nil { + t.error(err) + } + return n + case itemFunc: + t.backup() + return t.Func() + case itemVar: + t.backup() + return t.Var() + default: + t.unexpected(token, "input: v()") + } + return nil +} + +// Var is queryVar in the grammar. +func (t *Tree) Var() (v *VarNode) { + token := t.next() + varNoPrefix := strings.TrimPrefix(token.val, "$") + varNoBraces := strings.TrimSuffix(strings.TrimPrefix(varNoPrefix, "{"), "}") + t.VarNames = append(t.VarNames, varNoBraces) + return newVar(token.pos, varNoBraces, token.val) +} + +// Func parses a FuncNode. +func (t *Tree) Func() (f *FuncNode) { + token := t.next() + funcv, ok := t.GetFunction(token.val) + if !ok { + t.errorf("non existent function %s", token.val) + } + f = newFunc(token.pos, token.val, funcv) + t.expect(itemLeftParen, "func") + for { + switch token = t.next(); token.typ { + default: + t.backup() + node := t.O() + f.append(node) + if len(f.Args) == 1 && f.F.VariantReturn { + f.F.Return = node.Return() + } + case itemString: + s, err := strconv.Unquote(token.val) + if err != nil { + t.errorf("Unquoting error: %s", err) + } + f.append(newString(token.pos, token.val, s)) + case itemRightParen: + return + } + } +} + +// GetFunction gets a parsed Func from the functions available on the tree's func property. +func (t *Tree) GetFunction(name string) (v Func, ok bool) { + for _, funcMap := range t.funcs { + if funcMap == nil { + continue + } + if v, ok = funcMap[name]; ok { + return + } + } + return +} + +// String returns a string representation of the parse tree. +func (t *Tree) String() string { + return t.Root.String() +} diff --git a/vendor/github.com/grafana/grafana/pkg/expr/mathexp/reduce.go b/vendor/github.com/grafana/grafana/pkg/expr/mathexp/reduce.go new file mode 100644 index 00000000000..34a5a862058 --- /dev/null +++ b/vendor/github.com/grafana/grafana/pkg/expr/mathexp/reduce.go @@ -0,0 +1,101 @@ +package mathexp + +import ( + "fmt" + "math" + + "github.com/grafana/grafana-plugin-sdk-go/data" +) + +func Sum(v *data.Field) *float64 { + var sum float64 + for i := 0; i < v.Len(); i++ { + if f, ok := v.At(i).(*float64); ok { + if f == nil || math.IsNaN(*f) { + nan := math.NaN() + return &nan + } + sum += *f + } + } + return &sum +} + +func Avg(v *data.Field) *float64 { + sum := Sum(v) + f := *sum / float64(v.Len()) + return &f +} + +func Min(fv *data.Field) *float64 { + var f float64 + if fv.Len() == 0 { + nan := math.NaN() + return &nan + } + for i := 0; i < fv.Len(); i++ { + if v, ok := fv.At(i).(*float64); ok { + if v == nil || math.IsNaN(*v) { + nan := math.NaN() + return &nan + } + if i == 0 || *v < f { + f = *v + } + } + } + return &f +} + +func Max(fv *data.Field) *float64 { + var f float64 + if fv.Len() == 0 { + nan := math.NaN() + return &nan + } + for i := 0; i < fv.Len(); i++ { + if v, ok := fv.At(i).(*float64); ok { + if v == nil || math.IsNaN(*v) { + nan := math.NaN() + return &nan + } + if i == 0 || *v > f { + f = *v + } + } + } + return &f +} + +func Count(fv *data.Field) *float64 { + f := float64(fv.Len()) + return &f +} + +// Reduce turns the Series into a Number based on the given reduction function +func (s Series) Reduce(refID, rFunc string) (Number, error) { + var l data.Labels + if s.GetLabels() != nil { + l = s.GetLabels().Copy() + } + number := NewNumber(refID, l) + var f *float64 + fVec := s.Frame.Fields[1] + switch rFunc { + case "sum": + f = Sum(fVec) + case "mean": + f = Avg(fVec) + case "min": + f = Min(fVec) + case "max": + f = Max(fVec) + case "count": + f = Count(fVec) + default: + return number, fmt.Errorf("reduction %v not implemented", rFunc) + } + number.SetValue(f) + + return number, nil +} diff --git a/vendor/github.com/grafana/grafana/pkg/expr/mathexp/resample.go b/vendor/github.com/grafana/grafana/pkg/expr/mathexp/resample.go new file mode 100644 index 00000000000..b7773bfa066 --- /dev/null +++ b/vendor/github.com/grafana/grafana/pkg/expr/mathexp/resample.go @@ -0,0 +1,83 @@ +package mathexp + +import ( + "fmt" + "time" + + "github.com/grafana/grafana-plugin-sdk-go/backend" + "github.com/grafana/grafana-plugin-sdk-go/data" +) + +// Resample turns the Series into a Number based on the given reduction function +func (s Series) Resample(refID string, interval time.Duration, downsampler string, upsampler string, tr backend.TimeRange) (Series, error) { + newSeriesLength := int(float64(tr.To.Sub(tr.From).Nanoseconds()) / float64(interval.Nanoseconds())) + if newSeriesLength <= 0 { + return s, fmt.Errorf("the series cannot be sampled further; the time range is shorter than the interval") + } + resampled := NewSeries(refID, s.GetLabels(), s.TimeIdx, s.TimeIsNullable, s.ValueIdx, s.ValueIsNullable, newSeriesLength+1) + bookmark := 0 + var lastSeen *float64 + idx := 0 + t := tr.From + for !t.After(tr.To) && idx <= newSeriesLength { + vals := make([]*float64, 0) + sIdx := bookmark + for { + if sIdx == s.Len() { + break + } + st, v := s.GetPoint(sIdx) + if st.After(t) { + break + } + bookmark++ + sIdx++ + lastSeen = v + vals = append(vals, v) + } + var value *float64 + if len(vals) == 0 { // upsampling + switch upsampler { + case "pad": + if lastSeen != nil { + value = lastSeen + } else { + value = nil + } + case "backfilling": + if sIdx == s.Len() { // no vals left + value = nil + } else { + _, value = s.GetPoint(sIdx) + } + case "fillna": + value = nil + default: + return s, fmt.Errorf("upsampling %v not implemented", upsampler) + } + } else { // downsampling + fVec := data.NewField("", s.GetLabels(), vals) + var tmp *float64 + switch downsampler { + case "sum": + tmp = Sum(fVec) + case "mean": + tmp = Avg(fVec) + case "min": + tmp = Min(fVec) + case "max": + tmp = Max(fVec) + default: + return s, fmt.Errorf("downsampling %v not implemented", downsampler) + } + value = tmp + } + tv := t // his is required otherwise all points keep the latest timestamp; anything better? + if err := resampled.SetPoint(idx, &tv, value); err != nil { + return resampled, err + } + t = t.Add(interval) + idx++ + } + return resampled, nil +} diff --git a/vendor/github.com/grafana/grafana/pkg/expr/mathexp/type_series.go b/vendor/github.com/grafana/grafana/pkg/expr/mathexp/type_series.go new file mode 100644 index 00000000000..3ffd39e81f7 --- /dev/null +++ b/vendor/github.com/grafana/grafana/pkg/expr/mathexp/type_series.go @@ -0,0 +1,201 @@ +package mathexp + +import ( + "fmt" + "sort" + "time" + + "github.com/grafana/grafana-plugin-sdk-go/data" + "github.com/grafana/grafana/pkg/expr/mathexp/parse" +) + +// Series has time.Time and ...? *float64 fields. +type Series struct { + Frame *data.Frame + TimeIsNullable bool + TimeIdx int + ValueIsNullable bool + ValueIdx int + // TODO: + // - Multiple Value Fields + // - Value can be different number types +} + +// SeriesFromFrame validates that the dataframe can be considered a Series type +// and populate meta information on Series about the frame. +func SeriesFromFrame(frame *data.Frame) (s Series, err error) { + if len(frame.Fields) != 2 { + return s, fmt.Errorf("frame must have exactly two fields to be a series, has %v", len(frame.Fields)) + } + + foundTime := false + foundValue := false + for i, field := range frame.Fields { + switch field.Type() { + case data.FieldTypeTime: + s.TimeIdx = i + foundTime = true + case data.FieldTypeNullableTime: + s.TimeIsNullable = true + foundTime = true + s.TimeIdx = i + case data.FieldTypeFloat64: + foundValue = true + s.ValueIdx = i + case data.FieldTypeNullableFloat64: + s.ValueIsNullable = true + foundValue = true + s.ValueIdx = i + default: + // Handle default case + } + } + if !foundTime { + return s, fmt.Errorf("no time column found in frame %v", frame.Name) + } + if !foundValue { + return s, fmt.Errorf("no float64 value column found in frame %v", frame.Name) + } + s.Frame = frame + return s, nil +} + +// NewSeries returns a dataframe of type Series. +func NewSeries(refID string, labels data.Labels, timeIdx int, nullableTime bool, valueIdx int, nullableValue bool, size int) Series { + fields := make([]*data.Field, 2) + + if nullableValue { + fields[valueIdx] = data.NewField(refID, labels, make([]*float64, size)) + } else { + fields[valueIdx] = data.NewField(refID, labels, make([]float64, size)) + } + + if nullableTime { + fields[timeIdx] = data.NewField("Time", nil, make([]*time.Time, size)) + } else { + fields[timeIdx] = data.NewField("Time", nil, make([]time.Time, size)) + } + + return Series{ + Frame: data.NewFrame("", fields...), + TimeIsNullable: nullableTime, + TimeIdx: timeIdx, + ValueIsNullable: nullableValue, + ValueIdx: valueIdx, + } +} + +// Type returns the Value type and allows it to fulfill the Value interface. +func (s Series) Type() parse.ReturnType { return parse.TypeSeriesSet } + +// Value returns the actual value allows it to fulfill the Value interface. +func (s Series) Value() interface{} { return &s } + +func (s Series) GetLabels() data.Labels { return s.Frame.Fields[s.ValueIdx].Labels } + +func (s Series) SetLabels(ls data.Labels) { s.Frame.Fields[s.ValueIdx].Labels = ls } + +func (s Series) GetName() string { return s.Frame.Name } + +// AsDataFrame returns the underlying *data.Frame. +func (s Series) AsDataFrame() *data.Frame { return s.Frame } + +// GetPoint returns the time and value at the specified index. +func (s Series) GetPoint(pointIdx int) (*time.Time, *float64) { + return s.GetTime(pointIdx), s.GetValue(pointIdx) +} + +// SetPoint sets the time and value on the corresponding vectors at the specified index. +func (s Series) SetPoint(pointIdx int, t *time.Time, f *float64) (err error) { + if s.TimeIsNullable { + s.Frame.Fields[s.TimeIdx].Set(pointIdx, t) + } else { + if t == nil { + return fmt.Errorf("can not set null time value on non-nullable time field for series name %v", s.Frame.Name) + } + s.Frame.Fields[s.TimeIdx].Set(pointIdx, *t) + } + if s.ValueIsNullable { + s.Frame.Fields[s.ValueIdx].Set(pointIdx, f) + } else { + if f == nil { + return fmt.Errorf("can not set null float value on non-nullable float field for series name %v", s.Frame.Name) + } + s.Frame.Fields[s.ValueIdx].Set(pointIdx, *f) + } + return +} + +// AppendPoint appends a point (time/value). +func (s Series) AppendPoint(pointIdx int, t *time.Time, f *float64) (err error) { + if s.TimeIsNullable { + s.Frame.Fields[s.TimeIdx].Append(t) + } else { + if t == nil { + return fmt.Errorf("can not append null time value on non-nullable time field for series name %v", s.Frame.Name) + } + s.Frame.Fields[s.TimeIdx].Append(*t) + } + if s.ValueIsNullable { + s.Frame.Fields[s.ValueIdx].Append(f) + } else { + if f == nil { + return fmt.Errorf("can not append null float value on non-nullable float field for series name %v", s.Frame.Name) + } + s.Frame.Fields[s.ValueIdx].Append(*f) + } + return +} + +// Len returns the length of the series. +func (s Series) Len() int { + return s.Frame.Fields[0].Len() +} + +// GetTime returns the time at the specified index. +func (s Series) GetTime(pointIdx int) *time.Time { + if s.TimeIsNullable { + return s.Frame.Fields[s.TimeIdx].At(pointIdx).(*time.Time) + } + t := s.Frame.Fields[s.TimeIdx].At(pointIdx).(time.Time) + return &t +} + +// GetValue returns the float value at the specified index. +func (s Series) GetValue(pointIdx int) *float64 { + if s.ValueIsNullable { + return s.Frame.Fields[s.ValueIdx].At(pointIdx).(*float64) + } + f := s.Frame.Fields[s.ValueIdx].At(pointIdx).(float64) + return &f +} + +// SortByTime sorts the series by the time from oldest to newest. +// If desc is true, it will sort from newest to oldest. +// If any time values are nil, it will panic. +func (s Series) SortByTime(desc bool) { + if desc { + sort.Sort(sort.Reverse(SortSeriesByTime(s))) + return + } + sort.Sort(SortSeriesByTime(s)) +} + +// SortSeriesByTime allows a Series to be sorted by time +// the sort interface will panic if any timestamps are null +type SortSeriesByTime Series + +func (ss SortSeriesByTime) Len() int { return Series(ss).Len() } + +func (ss SortSeriesByTime) Swap(i, j int) { + iTimeVal, iFVal := Series(ss).GetPoint(i) + jTimeVal, jFVal := Series(ss).GetPoint(j) + _ = Series(ss).SetPoint(j, iTimeVal, iFVal) + _ = Series(ss).SetPoint(i, jTimeVal, jFVal) +} + +func (ss SortSeriesByTime) Less(i, j int) bool { + iTimeVal := Series(ss).GetTime(i) + jTimeVal := Series(ss).GetTime(j) + return iTimeVal.Before(*jTimeVal) +} diff --git a/vendor/github.com/grafana/grafana/pkg/expr/mathexp/types.go b/vendor/github.com/grafana/grafana/pkg/expr/mathexp/types.go new file mode 100644 index 00000000000..867796eaa70 --- /dev/null +++ b/vendor/github.com/grafana/grafana/pkg/expr/mathexp/types.go @@ -0,0 +1,107 @@ +package mathexp + +import ( + "github.com/grafana/grafana-plugin-sdk-go/data" + "github.com/grafana/grafana/pkg/expr/mathexp/parse" +) + +// Results is a container for Value interfaces. +type Results struct { + Values Values +} + +// Values is a slice of Value interfaces +type Values []Value + +// AsDataFrames returns each value as a slice of frames. +func (vals Values) AsDataFrames(refID string) []*data.Frame { + frames := make([]*data.Frame, len(vals)) + for i, v := range vals { + frames[i] = v.AsDataFrame() + frames[i].RefID = refID + } + return frames +} + +// Value is the interface that holds different types such as a Scalar, Series, or Number. +// all Value implementations should be a *data.Frame +type Value interface { + Type() parse.ReturnType + Value() interface{} + GetLabels() data.Labels + SetLabels(data.Labels) + AsDataFrame() *data.Frame +} + +// Scalar is the type that holds a single number constant. +// Before returning from an expression it will be wrapped in a +// data frame. +type Scalar struct{ Frame *data.Frame } + +// Type returns the Value type and allows it to fulfill the Value interface. +func (s Scalar) Type() parse.ReturnType { return parse.TypeScalar } + +// Value returns the actual value allows it to fulfill the Value interface. +func (s Scalar) Value() interface{} { return s } + +func (s Scalar) GetLabels() data.Labels { return nil } + +func (s Scalar) SetLabels(ls data.Labels) {} + +// AsDataFrame returns the underlying *data.Frame. +func (s Scalar) AsDataFrame() *data.Frame { return s.Frame } + +// NewScalar creates a Scalar holding value f. +func NewScalar(name string, f *float64) Scalar { + frame := data.NewFrame("", + data.NewField(name, nil, []*float64{f}), + ) + return Scalar{frame} +} + +// NewScalarResults creates a Results holding a single Scalar +func NewScalarResults(name string, f *float64) Results { + return Results{ + Values: []Value{NewScalar(name, f)}, + } +} + +// GetFloat64Value retrieves the single scalar value from the data +func (s Scalar) GetFloat64Value() *float64 { + return s.Frame.At(0, 0).(*float64) +} + +// Number hold a labelled single number values. +type Number struct{ Frame *data.Frame } + +// Type returns the Value type and allows it to fulfill the Value interface. +func (n Number) Type() parse.ReturnType { return parse.TypeNumberSet } + +// Value returns the actual value allows it to fulfill the Value interface. +func (n Number) Value() interface{} { return &n } + +func (n Number) GetLabels() data.Labels { return n.Frame.Fields[0].Labels } + +func (n Number) SetLabels(ls data.Labels) { n.Frame.Fields[0].Labels = ls } + +// AsDataFrame returns the underlying *data.Frame. +func (n Number) AsDataFrame() *data.Frame { return n.Frame } + +// SetValue sets the value of the Number to float64 pointer f +func (n Number) SetValue(f *float64) { + n.Frame.Set(0, 0, f) +} + +// GetFloat64Value retrieves the single scalar value from the data +func (n Number) GetFloat64Value() *float64 { + return n.Frame.At(0, 0).(*float64) +} + +// NewNumber returns a data that holds a float64Vector +func NewNumber(name string, labels data.Labels) Number { + return Number{ + data.NewFrame("", + data.NewField(name, labels, make([]*float64, 1)), + ), + } +} diff --git a/vendor/github.com/grafana/grafana/pkg/expr/nodes.go b/vendor/github.com/grafana/grafana/pkg/expr/nodes.go new file mode 100644 index 00000000000..c6a099da8b5 --- /dev/null +++ b/vendor/github.com/grafana/grafana/pkg/expr/nodes.go @@ -0,0 +1,362 @@ +package expr + +import ( + "context" + "encoding/json" + "fmt" + "time" + + "github.com/grafana/grafana-plugin-sdk-go/backend" + "github.com/grafana/grafana-plugin-sdk-go/data" + "github.com/grafana/grafana/pkg/expr/mathexp" + + "gonum.org/v1/gonum/graph/simple" +) + +// baseNode includes commmon properties used across DPNodes. +type baseNode struct { + id int64 + refID string +} + +type rawNode struct { + RefID string `json:"refId"` + Query map[string]interface{} + QueryType string + TimeRange backend.TimeRange +} + +func (rn *rawNode) GetDatasourceName() (string, error) { + rawDs, ok := rn.Query["datasource"] + if !ok { + return "", fmt.Errorf("no datasource in query for refId %v", rn.RefID) + } + dsName, ok := rawDs.(string) + if !ok { + return "", fmt.Errorf("expted datasource identifier to be a string, got %T", rawDs) + } + return dsName, nil +} + +func (rn *rawNode) GetCommandType() (c CommandType, err error) { + rawType, ok := rn.Query["type"] + if !ok { + return c, fmt.Errorf("no expression command type in query for refId %v", rn.RefID) + } + typeString, ok := rawType.(string) + if !ok { + return c, fmt.Errorf("expected expression command type to be a string, got type %T", rawType) + } + return ParseCommandType(typeString) +} + +// String returns a string representation of the node. In particular for +// %v formating in error messages. +func (b *baseNode) String() string { + return b.refID +} + +// CMDNode is a DPNode that holds an expression command. +type CMDNode struct { + baseNode + CMDType CommandType + Command Command +} + +// ID returns the id of the node so it can fulfill the gonum's graph Node interface. +func (b *baseNode) ID() int64 { + return b.id +} + +// RefID returns the refId of the node. +func (b *baseNode) RefID() string { + return b.refID +} + +// NodeType returns the data pipeline node type. +func (gn *CMDNode) NodeType() NodeType { + return TypeCMDNode +} + +// Execute runs the node and adds the results to vars. If the node requires +// other nodes they must have already been executed and their results must +// already by in vars. +func (gn *CMDNode) Execute(ctx context.Context, vars mathexp.Vars) (mathexp.Results, error) { + return gn.Command.Execute(ctx, vars) +} + +func buildCMDNode(dp *simple.DirectedGraph, rn *rawNode) (*CMDNode, error) { + commandType, err := rn.GetCommandType() + if err != nil { + return nil, fmt.Errorf("invalid expression command type in '%v'", rn.RefID) + } + + node := &CMDNode{ + baseNode: baseNode{ + id: dp.NewNode().ID(), + refID: rn.RefID, + }, + } + + switch commandType { + case TypeMath: + node.Command, err = UnmarshalMathCommand(rn) + case TypeReduce: + node.Command, err = UnmarshalReduceCommand(rn) + case TypeResample: + node.Command, err = UnmarshalResampleCommand(rn) + default: + return nil, fmt.Errorf("expression command type '%v' in '%v' not implemented", commandType, rn.RefID) + } + if err != nil { + return nil, err + } + + return node, nil +} + +const ( + defaultIntervalMS = int64(64) + defaultMaxDP = int64(5000) +) + +// DSNode is a DPNode that holds a datasource request. +type DSNode struct { + baseNode + query json.RawMessage + datasourceID int64 + datasourceUID string + + orgID int64 + queryType string + timeRange backend.TimeRange + intervalMS int64 + maxDP int64 +} + +// NodeType returns the data pipeline node type. +func (dn *DSNode) NodeType() NodeType { + return TypeDatasourceNode +} + +func buildDSNode(dp *simple.DirectedGraph, rn *rawNode, orgID int64) (*DSNode, error) { + encodedQuery, err := json.Marshal(rn.Query) + if err != nil { + return nil, err + } + + dsNode := &DSNode{ + baseNode: baseNode{ + id: dp.NewNode().ID(), + refID: rn.RefID, + }, + orgID: orgID, + query: json.RawMessage(encodedQuery), + queryType: rn.QueryType, + intervalMS: defaultIntervalMS, + maxDP: defaultMaxDP, + timeRange: rn.TimeRange, + } + + rawDsID, ok := rn.Query["datasourceId"] + switch ok { + case true: + floatDsID, ok := rawDsID.(float64) + if !ok { + return nil, fmt.Errorf("expected datasourceId to be a float64, got type %T for refId %v", rawDsID, rn.RefID) + } + dsNode.datasourceID = int64(floatDsID) + default: + rawDsUID, ok := rn.Query["datasourceUid"] + if !ok { + return nil, fmt.Errorf("neither datasourceId or datasourceUid in expression data source request for refId %v", rn.RefID) + } + strDsUID, ok := rawDsUID.(string) + if !ok { + return nil, fmt.Errorf("expected datasourceUid to be a string, got type %T for refId %v", rawDsUID, rn.RefID) + } + dsNode.datasourceUID = strDsUID + } + + var floatIntervalMS float64 + if rawIntervalMS := rn.Query["intervalMs"]; ok { + if floatIntervalMS, ok = rawIntervalMS.(float64); !ok { + return nil, fmt.Errorf("expected intervalMs to be an float64, got type %T for refId %v", rawIntervalMS, rn.RefID) + } + dsNode.intervalMS = int64(floatIntervalMS) + } + + var floatMaxDP float64 + if rawMaxDP := rn.Query["maxDataPoints"]; ok { + if floatMaxDP, ok = rawMaxDP.(float64); !ok { + return nil, fmt.Errorf("expected maxDataPoints to be an float64, got type %T for refId %v", rawMaxDP, rn.RefID) + } + dsNode.maxDP = int64(floatMaxDP) + } + + return dsNode, nil +} + +// Execute runs the node and adds the results to vars. If the node requires +// other nodes they must have already been executed and their results must +// already by in vars. +func (dn *DSNode) Execute(ctx context.Context, vars mathexp.Vars) (mathexp.Results, error) { + pc := backend.PluginContext{ + OrgID: dn.orgID, + DataSourceInstanceSettings: &backend.DataSourceInstanceSettings{ + ID: dn.datasourceID, + UID: dn.datasourceUID, + }, + } + + q := []backend.DataQuery{ + { + RefID: dn.refID, + MaxDataPoints: dn.maxDP, + Interval: time.Duration(int64(time.Millisecond) * dn.intervalMS), + JSON: dn.query, + TimeRange: dn.timeRange, + QueryType: dn.queryType, + }, + } + + resp, err := QueryData(ctx, &backend.QueryDataRequest{ + PluginContext: pc, + Queries: q, + }) + + if err != nil { + return mathexp.Results{}, err + } + + vals := make([]mathexp.Value, 0) + for refID, qr := range resp.Responses { + if len(qr.Frames) == 1 { + frame := qr.Frames[0] + if frame.TimeSeriesSchema().Type == data.TimeSeriesTypeNot && isNumberTable(frame) { + backend.Logger.Debug("expression datasource query (numberSet)", "query", refID) + numberSet, err := extractNumberSet(frame) + if err != nil { + return mathexp.Results{}, err + } + for _, n := range numberSet { + vals = append(vals, n) + } + + return mathexp.Results{ + Values: vals, + }, nil + } + } + + for _, frame := range qr.Frames { + backend.Logger.Debug("expression datasource query (seriesSet)", "query", refID) + series, err := WideToMany(frame) + if err != nil { + return mathexp.Results{}, err + } + for _, s := range series { + vals = append(vals, s) + } + } + } + return mathexp.Results{ + Values: vals, + }, nil +} + +func isNumberTable(frame *data.Frame) bool { + if frame == nil || frame.Fields == nil { + return false + } + numericCount := 0 + stringCount := 0 + otherCount := 0 + for _, field := range frame.Fields { + fType := field.Type() + switch { + case fType.Numeric(): + numericCount++ + case fType == data.FieldTypeString || fType == data.FieldTypeNullableString: + stringCount++ + default: + otherCount++ + } + } + return numericCount == 1 && otherCount == 0 +} + +func extractNumberSet(frame *data.Frame) ([]mathexp.Number, error) { + numericField := 0 + stringFieldIdxs := []int{} + stringFieldNames := []string{} + for i, field := range frame.Fields { + fType := field.Type() + switch { + case fType.Numeric(): + numericField = i + case fType == data.FieldTypeString || fType == data.FieldTypeNullableString: + stringFieldIdxs = append(stringFieldIdxs, i) + stringFieldNames = append(stringFieldNames, field.Name) + } + } + numbers := make([]mathexp.Number, frame.Rows()) + + for rowIdx := 0; rowIdx < frame.Rows(); rowIdx++ { + val, _ := frame.FloatAt(numericField, rowIdx) + var labels data.Labels + for i := 0; i < len(stringFieldIdxs); i++ { + if i == 0 { + labels = make(data.Labels) + } + key := stringFieldNames[i] // TODO check for duplicate string column names + val, _ := frame.ConcreteAt(stringFieldIdxs[i], rowIdx) + labels[key] = val.(string) // TODO check assertion / return error + } + + n := mathexp.NewNumber("", labels) + n.SetValue(&val) + numbers[rowIdx] = n + } + return numbers, nil +} + +// WideToMany converts a data package wide type Frame to one or multiple Series. A series +// is created for each value type column of wide frame. +// +// This might not be a good idea long term, but works now as an adapter/shim. +func WideToMany(frame *data.Frame) ([]mathexp.Series, error) { + tsSchema := frame.TimeSeriesSchema() + if tsSchema.Type != data.TimeSeriesTypeWide { + return nil, fmt.Errorf("input data must be a wide series but got type %s (input refid)", tsSchema.Type) + } + + if len(tsSchema.ValueIndices) == 1 { + s, err := mathexp.SeriesFromFrame(frame) + if err != nil { + return nil, err + } + return []mathexp.Series{s}, nil + } + + series := []mathexp.Series{} + for _, valIdx := range tsSchema.ValueIndices { + l := frame.Rows() + f := data.NewFrameOfFieldTypes(frame.Name, l, frame.Fields[tsSchema.TimeIndex].Type(), frame.Fields[valIdx].Type()) + f.Fields[0].Name = frame.Fields[tsSchema.TimeIndex].Name + f.Fields[1].Name = frame.Fields[valIdx].Name + if frame.Fields[valIdx].Labels != nil { + f.Fields[1].Labels = frame.Fields[valIdx].Labels.Copy() + } + for i := 0; i < l; i++ { + f.SetRow(i, frame.Fields[tsSchema.TimeIndex].CopyAt(i), frame.Fields[valIdx].CopyAt(i)) + } + s, err := mathexp.SeriesFromFrame(f) + if err != nil { + return nil, err + } + series = append(series, s) + } + + return series, nil +} diff --git a/vendor/github.com/grafana/grafana/pkg/expr/service.go b/vendor/github.com/grafana/grafana/pkg/expr/service.go new file mode 100644 index 00000000000..39a7bd93026 --- /dev/null +++ b/vendor/github.com/grafana/grafana/pkg/expr/service.go @@ -0,0 +1,52 @@ +package expr + +import ( + "context" + + "github.com/grafana/grafana-plugin-sdk-go/backend" + "github.com/grafana/grafana/pkg/setting" +) + +// DatasourceName is the string constant used as the datasource name in requests +// to identify it as an expression command. +const DatasourceName = "__expr__" + +// DatasourceID is the fake datasource id used in requests to identify it as an +// expression command. +const DatasourceID = -100 + +// DatasourceUID is the fake datasource uid used in requests to identify it as an +// expression command. +const DatasourceUID = "-100" + +// Service is service representation for expression handling. +type Service struct { + Cfg *setting.Cfg +} + +func (s *Service) isDisabled() bool { + if s.Cfg == nil { + return true + } + return !s.Cfg.ExpressionsEnabled +} + +// BuildPipeline builds a pipeline from a request. +func (s *Service) BuildPipeline(req *backend.QueryDataRequest) (DataPipeline, error) { + return buildPipeline(req) +} + +// ExecutePipeline executes an expression pipeline and returns all the results. +func (s *Service) ExecutePipeline(ctx context.Context, pipeline DataPipeline) (*backend.QueryDataResponse, error) { + res := backend.NewQueryDataResponse() + vars, err := pipeline.execute(ctx) + if err != nil { + return nil, err + } + for refID, val := range vars { + res.Responses[refID] = backend.DataResponse{ + Frames: val.Values.AsDataFrames(refID), + } + } + return res, nil +} diff --git a/vendor/github.com/grafana/grafana/pkg/expr/transform.go b/vendor/github.com/grafana/grafana/pkg/expr/transform.go new file mode 100644 index 00000000000..262ca6cc1d0 --- /dev/null +++ b/vendor/github.com/grafana/grafana/pkg/expr/transform.go @@ -0,0 +1,249 @@ +package expr + +import ( + "encoding/json" + "fmt" + "strconv" + "time" + + "github.com/grafana/grafana-plugin-sdk-go/backend" + "github.com/grafana/grafana/pkg/bus" + "github.com/grafana/grafana/pkg/components/simplejson" + "github.com/grafana/grafana/pkg/models" + "github.com/grafana/grafana/pkg/tsdb" + "github.com/prometheus/client_golang/prometheus" + "golang.org/x/net/context" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +var ( + expressionsQuerySummary *prometheus.SummaryVec +) + +func init() { + expressionsQuerySummary = prometheus.NewSummaryVec( + prometheus.SummaryOpts{ + Name: "expressions_queries_duration_milliseconds", + Help: "Expressions query summary", + Objectives: map[float64]float64{0.5: 0.05, 0.9: 0.01, 0.99: 0.001}, + }, + []string{"status"}, + ) + + prometheus.MustRegister(expressionsQuerySummary) +} + +// WrapTransformData creates and executes transform requests +func (s *Service) WrapTransformData(ctx context.Context, query *tsdb.TsdbQuery) (*tsdb.Response, error) { + sdkReq := &backend.QueryDataRequest{ + PluginContext: backend.PluginContext{ + OrgID: query.User.OrgId, + }, + Queries: []backend.DataQuery{}, + } + + for _, q := range query.Queries { + modelJSON, err := q.Model.MarshalJSON() + if err != nil { + return nil, err + } + sdkReq.Queries = append(sdkReq.Queries, backend.DataQuery{ + JSON: modelJSON, + Interval: time.Duration(q.IntervalMs) * time.Millisecond, + RefID: q.RefId, + MaxDataPoints: q.MaxDataPoints, + QueryType: q.QueryType, + TimeRange: backend.TimeRange{ + From: query.TimeRange.GetFromAsTimeUTC(), + To: query.TimeRange.GetToAsTimeUTC(), + }, + }) + } + pbRes, err := s.TransformData(ctx, sdkReq) + if err != nil { + return nil, err + } + + tR := &tsdb.Response{ + Results: make(map[string]*tsdb.QueryResult, len(pbRes.Responses)), + } + for refID, res := range pbRes.Responses { + tRes := &tsdb.QueryResult{ + RefId: refID, + Dataframes: tsdb.NewDecodedDataFrames(res.Frames), + } + // if len(res.JsonMeta) != 0 { + // tRes.Meta = simplejson.NewFromAny(res.JsonMeta) + // } + if res.Error != nil { + tRes.Error = res.Error + tRes.ErrorString = res.Error.Error() + } + tR.Results[refID] = tRes + } + + return tR, nil +} + +// TransformData takes Queries which are either expressions nodes +// or are datasource requests. +func (s *Service) TransformData(ctx context.Context, req *backend.QueryDataRequest) (r *backend.QueryDataResponse, err error) { + if s.isDisabled() { + return nil, status.Error(codes.PermissionDenied, "Expressions are disabled") + } + + start := time.Now() + defer func() { + var respStatus string + switch { + case err == nil: + respStatus = "success" + default: + respStatus = "failure" + } + duration := float64(time.Since(start).Nanoseconds()) / float64(time.Millisecond) + expressionsQuerySummary.WithLabelValues(respStatus).Observe(duration) + }() + + // Build the pipeline from the request, checking for ordering issues (e.g. loops) + // and parsing graph nodes from the queries. + pipeline, err := s.BuildPipeline(req) + if err != nil { + return nil, status.Error(codes.InvalidArgument, err.Error()) + } + + // Execute the pipeline + responses, err := s.ExecutePipeline(ctx, pipeline) + if err != nil { + return nil, status.Error(codes.Unknown, err.Error()) + } + + // Get which queries have the Hide property so they those queries' results + // can be excluded from the response. + hidden, err := hiddenRefIDs(req.Queries) + if err != nil { + return nil, status.Error((codes.Internal), err.Error()) + } + + if len(hidden) != 0 { + filteredRes := backend.NewQueryDataResponse() + for refID, res := range responses.Responses { + if _, ok := hidden[refID]; !ok { + filteredRes.Responses[refID] = res + } + } + responses = filteredRes + } + + return responses, nil +} + +func hiddenRefIDs(queries []backend.DataQuery) (map[string]struct{}, error) { + hidden := make(map[string]struct{}) + + for _, query := range queries { + hide := struct { + Hide bool `json:"hide"` + }{} + + if err := json.Unmarshal(query.JSON, &hide); err != nil { + return nil, err + } + + if hide.Hide { + hidden[query.RefID] = struct{}{} + } + } + return hidden, nil +} + +// QueryData is called used to query datasources that are not expression commands, but are used +// alongside expressions and/or are the input of an expression command. +func QueryData(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) { + if len(req.Queries) == 0 { + return nil, fmt.Errorf("zero queries found in datasource request") + } + + datasourceID := int64(0) + var datasourceUID string + + if req.PluginContext.DataSourceInstanceSettings != nil { + datasourceID = req.PluginContext.DataSourceInstanceSettings.ID + datasourceUID = req.PluginContext.DataSourceInstanceSettings.UID + } + + getDsInfo := &models.GetDataSourceQuery{ + OrgId: req.PluginContext.OrgID, + Id: datasourceID, + Uid: datasourceUID, + } + + if err := bus.Dispatch(getDsInfo); err != nil { + return nil, fmt.Errorf("could not find datasource: %w", err) + } + + // Convert plugin-model (datasource) queries to tsdb queries + queries := make([]*tsdb.Query, len(req.Queries)) + for i, query := range req.Queries { + sj, err := simplejson.NewJson(query.JSON) + if err != nil { + return nil, err + } + queries[i] = &tsdb.Query{ + RefId: query.RefID, + IntervalMs: query.Interval.Milliseconds(), + MaxDataPoints: query.MaxDataPoints, + QueryType: query.QueryType, + DataSource: getDsInfo.Result, + Model: sj, + } + } + + // For now take Time Range from first query. + timeRange := tsdb.NewTimeRange(strconv.FormatInt(req.Queries[0].TimeRange.From.Unix()*1000, 10), strconv.FormatInt(req.Queries[0].TimeRange.To.Unix()*1000, 10)) + + tQ := &tsdb.TsdbQuery{ + TimeRange: timeRange, + Queries: queries, + } + + // Execute the converted queries + tsdbRes, err := tsdb.HandleRequest(ctx, getDsInfo.Result, tQ) + if err != nil { + return nil, err + } + // Convert tsdb results (map) to plugin-model/datasource (slice) results. + // Only error, tsdb.Series, and encoded Dataframes responses are mapped. + responses := make(map[string]backend.DataResponse, len(tsdbRes.Results)) + for refID, res := range tsdbRes.Results { + pRes := backend.DataResponse{} + if res.Error != nil { + pRes.Error = res.Error + } + + if res.Dataframes != nil { + decoded, err := res.Dataframes.Decoded() + if err != nil { + return nil, err + } + pRes.Frames = decoded + responses[refID] = pRes + continue + } + + for _, series := range res.Series { + frame, err := tsdb.SeriesToFrame(series) + frame.RefID = refID + if err != nil { + return nil, err + } + pRes.Frames = append(pRes.Frames, frame) + } + + responses[refID] = pRes + } + return &backend.QueryDataResponse{ + Responses: responses, + }, nil +} diff --git a/vendor/github.com/grafana/grafana/pkg/services/ngalert/eval/alert_query.go b/vendor/github.com/grafana/grafana/pkg/services/ngalert/eval/alert_query.go new file mode 100644 index 00000000000..fb829952e66 --- /dev/null +++ b/vendor/github.com/grafana/grafana/pkg/services/ngalert/eval/alert_query.go @@ -0,0 +1,288 @@ +package eval + +import ( + "encoding/json" + "fmt" + "time" + + "github.com/grafana/grafana-plugin-sdk-go/backend" + "github.com/grafana/grafana/pkg/expr" +) + +const defaultMaxDataPoints float64 = 100 +const defaultIntervalMS float64 = 1000 + +// Duration is a type used for marshalling durations. +type Duration time.Duration + +func (d Duration) String() string { + return time.Duration(d).String() +} + +func (d Duration) MarshalJSON() ([]byte, error) { + return json.Marshal(time.Duration(d).Seconds()) +} + +func (d *Duration) UnmarshalJSON(b []byte) error { + var v interface{} + if err := json.Unmarshal(b, &v); err != nil { + return err + } + switch value := v.(type) { + case float64: + *d = Duration(time.Duration(value) * time.Second) + return nil + default: + return fmt.Errorf("invalid duration %v", v) + } +} + +// RelativeTimeRange is the per query start and end time +// for requests. +type RelativeTimeRange struct { + From Duration `json:"from"` + To Duration `json:"to"` +} + +// isValid checks that From duration is greater than To duration. +func (rtr *RelativeTimeRange) isValid() bool { + return rtr.From > rtr.To +} + +func (rtr *RelativeTimeRange) toTimeRange(now time.Time) backend.TimeRange { + return backend.TimeRange{ + From: now.Add(-time.Duration(rtr.From)), + To: now.Add(-time.Duration(rtr.To)), + } +} + +// AlertQuery represents a single query associated with an alert definition. +type AlertQuery struct { + // RefID is the unique identifier of the query, set by the frontend call. + RefID string `json:"refId"` + + // QueryType is an optional identifier for the type of query. + // It can be used to distinguish different types of queries. + QueryType string `json:"queryType"` + + // RelativeTimeRange is the relative Start and End of the query as sent by the frontend. + RelativeTimeRange RelativeTimeRange `json:"relativeTimeRange"` + + DatasourceUID string `json:"-"` + + // JSON is the raw JSON query and includes the above properties as well as custom properties. + Model json.RawMessage `json:"model"` + + modelProps map[string]interface{} +} + +func (aq *AlertQuery) setModelProps() error { + aq.modelProps = make(map[string]interface{}) + err := json.Unmarshal(aq.Model, &aq.modelProps) + if err != nil { + return fmt.Errorf("failed to unmarshal query model: %w", err) + } + + return nil +} + +// setDatasource sets DatasourceID. +// If it's an expression sets DefaultExprDatasourceID. +func (aq *AlertQuery) setDatasource() error { + if aq.modelProps == nil { + err := aq.setModelProps() + if err != nil { + return err + } + } + + dsName, ok := aq.modelProps["datasource"] + if !ok { + return fmt.Errorf("failed to get datasource from query model") + } + + if dsName == expr.DatasourceName { + aq.DatasourceUID = expr.DatasourceUID + aq.modelProps["datasourceUid"] = expr.DatasourceUID + return nil + } + + i, ok := aq.modelProps["datasourceUid"] + if !ok { + return fmt.Errorf("failed to get datasourceUid from query model") + } + dsUID, ok := i.(string) + if !ok { + return fmt.Errorf("failed to cast datasourceUid to string: %v", i) + } + aq.DatasourceUID = dsUID + return nil +} + +// IsExpression returns true if the alert query is an expression. +func (aq *AlertQuery) IsExpression() (bool, error) { + err := aq.setDatasource() + if err != nil { + return false, err + } + return aq.DatasourceUID == expr.DatasourceUID, nil +} + +// setMaxDatapoints sets the model maxDataPoints if it's missing or invalid +func (aq *AlertQuery) setMaxDatapoints() error { + if aq.modelProps == nil { + err := aq.setModelProps() + if err != nil { + return err + } + } + i, ok := aq.modelProps["maxDataPoints"] // GEL requires maxDataPoints inside the query JSON + if !ok { + aq.modelProps["maxDataPoints"] = defaultMaxDataPoints + } + maxDataPoints, ok := i.(float64) + if !ok || maxDataPoints == 0 { + aq.modelProps["maxDataPoints"] = defaultMaxDataPoints + } + return nil +} + +func (aq *AlertQuery) getMaxDatapoints() (int64, error) { + err := aq.setMaxDatapoints() + if err != nil { + return 0, err + } + + maxDataPoints, ok := aq.modelProps["maxDataPoints"].(float64) + if !ok { + return 0, fmt.Errorf("failed to cast maxDataPoints to float64: %v", aq.modelProps["maxDataPoints"]) + } + return int64(maxDataPoints), nil +} + +// setIntervalMS sets the model IntervalMs if it's missing or invalid +func (aq *AlertQuery) setIntervalMS() error { + if aq.modelProps == nil { + err := aq.setModelProps() + if err != nil { + return err + } + } + i, ok := aq.modelProps["intervalMs"] // GEL requires intervalMs inside the query JSON + if !ok { + aq.modelProps["intervalMs"] = defaultIntervalMS + } + intervalMs, ok := i.(float64) + if !ok || intervalMs == 0 { + aq.modelProps["intervalMs"] = defaultIntervalMS + } + return nil +} + +func (aq *AlertQuery) getIntervalMS() (int64, error) { + err := aq.setIntervalMS() + if err != nil { + return 0, err + } + + intervalMs, ok := aq.modelProps["intervalMs"].(float64) + if !ok { + return 0, fmt.Errorf("failed to cast intervalMs to float64: %v", aq.modelProps["intervalMs"]) + } + return int64(intervalMs), nil +} + +func (aq *AlertQuery) getIntervalDuration() (time.Duration, error) { + err := aq.setIntervalMS() + if err != nil { + return 0, err + } + + intervalMs, ok := aq.modelProps["intervalMs"].(float64) + if !ok { + return 0, fmt.Errorf("failed to cast intervalMs to float64: %v", aq.modelProps["intervalMs"]) + } + return time.Duration(intervalMs) * time.Millisecond, nil +} + +// GetDatasource returns the query datasource identifier. +func (aq *AlertQuery) GetDatasource() (string, error) { + err := aq.setDatasource() + if err != nil { + return "", err + } + return aq.DatasourceUID, nil +} + +func (aq *AlertQuery) getModel() ([]byte, error) { + err := aq.setDatasource() + if err != nil { + return nil, err + } + + err = aq.setMaxDatapoints() + if err != nil { + return nil, err + } + + err = aq.setIntervalMS() + if err != nil { + return nil, err + } + + model, err := json.Marshal(aq.modelProps) + if err != nil { + return nil, fmt.Errorf("unable to marshal query model: %w", err) + } + return model, nil +} + +func (aq *AlertQuery) setQueryType() error { + if aq.modelProps == nil { + err := aq.setModelProps() + if err != nil { + return err + } + } + i, ok := aq.modelProps["queryType"] + if !ok { + return nil + } + + queryType, ok := i.(string) + if !ok { + return fmt.Errorf("failed to get queryType from query model: %v", i) + } + + aq.QueryType = queryType + return nil +} + +// PreSave sets query's properties. +// It should be called before being saved. +func (aq *AlertQuery) PreSave() error { + if err := aq.setDatasource(); err != nil { + return fmt.Errorf("failed to set datasource to query model: %w", err) + } + + if err := aq.setQueryType(); err != nil { + return fmt.Errorf("failed to set query type to query model: %w", err) + } + + // override model + model, err := aq.getModel() + if err != nil { + return err + } + aq.Model = model + + isExpression, err := aq.IsExpression() + if err != nil { + return err + } + + if ok := isExpression || aq.RelativeTimeRange.isValid(); !ok { + return fmt.Errorf("invalid relative time range: %+v", aq.RelativeTimeRange) + } + return nil +} diff --git a/vendor/github.com/grafana/grafana/pkg/services/ngalert/eval/eval.go b/vendor/github.com/grafana/grafana/pkg/services/ngalert/eval/eval.go new file mode 100644 index 00000000000..72d4be0548d --- /dev/null +++ b/vendor/github.com/grafana/grafana/pkg/services/ngalert/eval/eval.go @@ -0,0 +1,237 @@ +// Package eval executes the condition for an alert definition, evaluates the condition results, and +// returns the alert instance states. +package eval + +import ( + "context" + "fmt" + "time" + + "github.com/grafana/grafana/pkg/setting" + + "github.com/grafana/grafana-plugin-sdk-go/backend" + "github.com/grafana/grafana-plugin-sdk-go/data" + "github.com/grafana/grafana/pkg/expr" +) + +const alertingEvaluationTimeout = 30 * time.Second + +type Evaluator struct { + Cfg *setting.Cfg +} + +// invalidEvalResultFormatError is an error for invalid format of the alert definition evaluation results. +type invalidEvalResultFormatError struct { + refID string + reason string + err error +} + +func (e *invalidEvalResultFormatError) Error() string { + s := fmt.Sprintf("invalid format of evaluation results for the alert definition %s: %s", e.refID, e.reason) + if e.err != nil { + s = fmt.Sprintf("%s: %s", s, e.err.Error()) + } + return s +} + +func (e *invalidEvalResultFormatError) Unwrap() error { + return e.err +} + +// Condition contains backend expressions and queries and the RefID +// of the query or expression that will be evaluated. +type Condition struct { + RefID string `json:"refId"` + OrgID int64 `json:"-"` + + QueriesAndExpressions []AlertQuery `json:"queriesAndExpressions"` +} + +// ExecutionResults contains the unevaluated results from executing +// a condition. +type ExecutionResults struct { + AlertDefinitionID int64 + + Error error + + Results data.Frames +} + +// Results is a slice of evaluated alert instances states. +type Results []result + +// result contains the evaluated state of an alert instance +// identified by its labels. +type result struct { + Instance data.Labels + State state // Enum +} + +// state is an enum of the evaluation state for an alert instance. +type state int + +const ( + // Normal is the eval state for an alert instance condition + // that evaluated to false. + Normal state = iota + + // Alerting is the eval state for an alert instance condition + // that evaluated to false. + Alerting +) + +func (s state) String() string { + return [...]string{"Normal", "Alerting"}[s] +} + +// IsValid checks the condition's validity. +func (c Condition) IsValid() bool { + // TODO search for refIDs in QueriesAndExpressions + return len(c.QueriesAndExpressions) != 0 +} + +// AlertExecCtx is the context provided for executing an alert condition. +type AlertExecCtx struct { + OrgID int64 + ExpressionsEnabled bool + + Ctx context.Context +} + +// execute runs the Condition's expressions or queries. +func (c *Condition) execute(ctx AlertExecCtx, now time.Time) (*ExecutionResults, error) { + result := ExecutionResults{} + if !c.IsValid() { + return nil, fmt.Errorf("invalid conditions") + // TODO: Things probably + } + + queryDataReq := &backend.QueryDataRequest{ + PluginContext: backend.PluginContext{ + OrgID: ctx.OrgID, + }, + Queries: []backend.DataQuery{}, + } + + for i := range c.QueriesAndExpressions { + q := c.QueriesAndExpressions[i] + model, err := q.getModel() + if err != nil { + return nil, fmt.Errorf("failed to get query model: %w", err) + } + interval, err := q.getIntervalDuration() + if err != nil { + return nil, fmt.Errorf("failed to retrieve intervalMs from the model: %w", err) + } + + maxDatapoints, err := q.getMaxDatapoints() + if err != nil { + return nil, fmt.Errorf("failed to retrieve maxDatapoints from the model: %w", err) + } + + queryDataReq.Queries = append(queryDataReq.Queries, backend.DataQuery{ + JSON: model, + Interval: interval, + RefID: q.RefID, + MaxDataPoints: maxDatapoints, + QueryType: q.QueryType, + TimeRange: q.RelativeTimeRange.toTimeRange(now), + }) + } + + exprService := expr.Service{Cfg: &setting.Cfg{ExpressionsEnabled: ctx.ExpressionsEnabled}} + pbRes, err := exprService.TransformData(ctx.Ctx, queryDataReq) + if err != nil { + return &result, err + } + + for refID, res := range pbRes.Responses { + if refID != c.RefID { + continue + } + result.Results = res.Frames + } + + if len(result.Results) == 0 { + err = fmt.Errorf("no GEL results") + result.Error = err + return &result, err + } + + return &result, nil +} + +// evaluateExecutionResult takes the ExecutionResult, and returns a frame where +// each column is a string type that holds a string representing its state. +func evaluateExecutionResult(results *ExecutionResults) (Results, error) { + evalResults := make([]result, 0) + labels := make(map[string]bool) + for _, f := range results.Results { + rowLen, err := f.RowLen() + if err != nil { + return nil, &invalidEvalResultFormatError{refID: f.RefID, reason: "unable to get frame row length", err: err} + } + if rowLen > 1 { + return nil, &invalidEvalResultFormatError{refID: f.RefID, reason: fmt.Sprintf("unexpected row length: %d instead of 1", rowLen)} + } + + if len(f.Fields) > 1 { + return nil, &invalidEvalResultFormatError{refID: f.RefID, reason: fmt.Sprintf("unexpected field length: %d instead of 1", len(f.Fields))} + } + + if f.Fields[0].Type() != data.FieldTypeNullableFloat64 { + return nil, &invalidEvalResultFormatError{refID: f.RefID, reason: fmt.Sprintf("invalid field type: %d", f.Fields[0].Type())} + } + + labelsStr := f.Fields[0].Labels.String() + _, ok := labels[labelsStr] + if ok { + return nil, &invalidEvalResultFormatError{refID: f.RefID, reason: fmt.Sprintf("frame cannot uniquely be identified by its labels: %s", labelsStr)} + } + labels[labelsStr] = true + + state := Normal + val, err := f.Fields[0].FloatAt(0) + if err != nil || val != 0 { + state = Alerting + } + + evalResults = append(evalResults, result{ + Instance: f.Fields[0].Labels, + State: state, + }) + } + return evalResults, nil +} + +// AsDataFrame forms the EvalResults in Frame suitable for displaying in the table panel of the front end. +// This may be temporary, as there might be a fair amount we want to display in the frontend, and it might not make sense to store that in data.Frame. +// For the first pass, I would expect a Frame with a single row, and a column for each instance with a boolean value. +func (evalResults Results) AsDataFrame() data.Frame { + fields := make([]*data.Field, 0) + for _, evalResult := range evalResults { + fields = append(fields, data.NewField("", evalResult.Instance, []bool{evalResult.State != Normal})) + } + f := data.NewFrame("", fields...) + return *f +} + +// ConditionEval executes conditions and evaluates the result. +func (e *Evaluator) ConditionEval(condition *Condition, now time.Time) (Results, error) { + alertCtx, cancelFn := context.WithTimeout(context.Background(), alertingEvaluationTimeout) + defer cancelFn() + + alertExecCtx := AlertExecCtx{OrgID: condition.OrgID, Ctx: alertCtx, ExpressionsEnabled: e.Cfg.ExpressionsEnabled} + + execResult, err := condition.execute(alertExecCtx, now) + if err != nil { + return nil, fmt.Errorf("failed to execute conditions: %w", err) + } + + evalResults, err := evaluateExecutionResult(execResult) + if err != nil { + return nil, fmt.Errorf("failed to evaluate results: %w", err) + } + return evalResults, nil +} diff --git a/vendor/github.com/grafana/grafana/pkg/tsdb/frame_util.go b/vendor/github.com/grafana/grafana/pkg/tsdb/frame_util.go new file mode 100644 index 00000000000..f88ad39e583 --- /dev/null +++ b/vendor/github.com/grafana/grafana/pkg/tsdb/frame_util.go @@ -0,0 +1,37 @@ +package tsdb + +import ( + "time" + + "github.com/grafana/grafana-plugin-sdk-go/data" +) + +// SeriesToFrame converts a TimeSeries to a sdk Frame +func SeriesToFrame(series *TimeSeries) (*data.Frame, error) { + timeVec := make([]*time.Time, len(series.Points)) + floatVec := make([]*float64, len(series.Points)) + for idx, point := range series.Points { + timeVec[idx], floatVec[idx] = convertTSDBTimePoint(point) + } + frame := data.NewFrame(series.Name, + data.NewField("time", nil, timeVec), + data.NewField("value", data.Labels(series.Tags), floatVec), + ) + + return frame, nil +} + +// convertTSDBTimePoint coverts a tsdb.TimePoint into two values appropriate +// for Series values. +func convertTSDBTimePoint(point TimePoint) (t *time.Time, f *float64) { + timeIdx, valueIdx := 1, 0 + if point[timeIdx].Valid { // Assuming valid is null? + tI := int64(point[timeIdx].Float64) + uT := time.Unix(tI/int64(1e+3), (tI%int64(1e+3))*int64(1e+6)) // time.Time from millisecond unix ts + t = &uT + } + if point[valueIdx].Valid { + f = &point[valueIdx].Float64 + } + return +} diff --git a/vendor/github.com/grafana/grafana/pkg/tsdb/interval.go b/vendor/github.com/grafana/grafana/pkg/tsdb/interval.go new file mode 100644 index 00000000000..781cd30b41a --- /dev/null +++ b/vendor/github.com/grafana/grafana/pkg/tsdb/interval.go @@ -0,0 +1,222 @@ +package tsdb + +import ( + "fmt" + "regexp" + "strings" + "time" + + "github.com/grafana/grafana/pkg/components/simplejson" + "github.com/grafana/grafana/pkg/models" +) + +var ( + defaultRes int64 = 1500 + defaultMinInterval = time.Millisecond * 1 + year = time.Hour * 24 * 365 + day = time.Hour * 24 +) + +type Interval struct { + Text string + Value time.Duration +} + +type intervalCalculator struct { + minInterval time.Duration +} + +type IntervalCalculator interface { + Calculate(timeRange *TimeRange, minInterval time.Duration) Interval +} + +type IntervalOptions struct { + MinInterval time.Duration +} + +func NewIntervalCalculator(opt *IntervalOptions) *intervalCalculator { + if opt == nil { + opt = &IntervalOptions{} + } + + calc := &intervalCalculator{} + + if opt.MinInterval == 0 { + calc.minInterval = defaultMinInterval + } else { + calc.minInterval = opt.MinInterval + } + + return calc +} + +func (i *Interval) Milliseconds() int64 { + return i.Value.Nanoseconds() / int64(time.Millisecond) +} + +func (ic *intervalCalculator) Calculate(timerange *TimeRange, minInterval time.Duration) Interval { + to := timerange.MustGetTo().UnixNano() + from := timerange.MustGetFrom().UnixNano() + interval := time.Duration((to - from) / defaultRes) + + if interval < minInterval { + return Interval{Text: FormatDuration(minInterval), Value: minInterval} + } + + rounded := roundInterval(interval) + return Interval{Text: FormatDuration(rounded), Value: rounded} +} + +func GetIntervalFrom(dsInfo *models.DataSource, queryModel *simplejson.Json, defaultInterval time.Duration) (time.Duration, error) { + interval := queryModel.Get("interval").MustString("") + + if interval == "" && dsInfo.JsonData != nil { + dsInterval := dsInfo.JsonData.Get("timeInterval").MustString("") + if dsInterval != "" { + interval = dsInterval + } + } + + if interval == "" { + return defaultInterval, nil + } + + interval = strings.Replace(strings.Replace(interval, "<", "", 1), ">", "", 1) + isPureNum, err := regexp.MatchString(`^\d+$`, interval) + if err != nil { + return time.Duration(0), err + } + if isPureNum { + interval += "s" + } + parsedInterval, err := time.ParseDuration(interval) + if err != nil { + return time.Duration(0), err + } + + return parsedInterval, nil +} + +// FormatDuration converts a duration into the kbn format e.g. 1m 2h or 3d +func FormatDuration(inter time.Duration) string { + if inter >= year { + return fmt.Sprintf("%dy", inter/year) + } + + if inter >= day { + return fmt.Sprintf("%dd", inter/day) + } + + if inter >= time.Hour { + return fmt.Sprintf("%dh", inter/time.Hour) + } + + if inter >= time.Minute { + return fmt.Sprintf("%dm", inter/time.Minute) + } + + if inter >= time.Second { + return fmt.Sprintf("%ds", inter/time.Second) + } + + if inter >= time.Millisecond { + return fmt.Sprintf("%dms", inter/time.Millisecond) + } + + return "1ms" +} + +//nolint: gocyclo +func roundInterval(interval time.Duration) time.Duration { + switch { + // 0.015s + case interval <= 15*time.Millisecond: + return time.Millisecond * 10 // 0.01s + // 0.035s + case interval <= 35*time.Millisecond: + return time.Millisecond * 20 // 0.02s + // 0.075s + case interval <= 75*time.Millisecond: + return time.Millisecond * 50 // 0.05s + // 0.15s + case interval <= 150*time.Millisecond: + return time.Millisecond * 100 // 0.1s + // 0.35s + case interval <= 350*time.Millisecond: + return time.Millisecond * 200 // 0.2s + // 0.75s + case interval <= 750*time.Millisecond: + return time.Millisecond * 500 // 0.5s + // 1.5s + case interval <= 1500*time.Millisecond: + return time.Millisecond * 1000 // 1s + // 3.5s + case interval <= 3500*time.Millisecond: + return time.Millisecond * 2000 // 2s + // 7.5s + case interval <= 7500*time.Millisecond: + return time.Millisecond * 5000 // 5s + // 12.5s + case interval <= 12500*time.Millisecond: + return time.Millisecond * 10000 // 10s + // 17.5s + case interval <= 17500*time.Millisecond: + return time.Millisecond * 15000 // 15s + // 25s + case interval <= 25000*time.Millisecond: + return time.Millisecond * 20000 // 20s + // 45s + case interval <= 45000*time.Millisecond: + return time.Millisecond * 30000 // 30s + // 1.5m + case interval <= 90000*time.Millisecond: + return time.Millisecond * 60000 // 1m + // 3.5m + case interval <= 210000*time.Millisecond: + return time.Millisecond * 120000 // 2m + // 7.5m + case interval <= 450000*time.Millisecond: + return time.Millisecond * 300000 // 5m + // 12.5m + case interval <= 750000*time.Millisecond: + return time.Millisecond * 600000 // 10m + // 12.5m + case interval <= 1050000*time.Millisecond: + return time.Millisecond * 900000 // 15m + // 25m + case interval <= 1500000*time.Millisecond: + return time.Millisecond * 1200000 // 20m + // 45m + case interval <= 2700000*time.Millisecond: + return time.Millisecond * 1800000 // 30m + // 1.5h + case interval <= 5400000*time.Millisecond: + return time.Millisecond * 3600000 // 1h + // 2.5h + case interval <= 9000000*time.Millisecond: + return time.Millisecond * 7200000 // 2h + // 4.5h + case interval <= 16200000*time.Millisecond: + return time.Millisecond * 10800000 // 3h + // 9h + case interval <= 32400000*time.Millisecond: + return time.Millisecond * 21600000 // 6h + // 24h + case interval <= 86400000*time.Millisecond: + return time.Millisecond * 43200000 // 12h + // 48h + case interval <= 172800000*time.Millisecond: + return time.Millisecond * 86400000 // 24h + // 1w + case interval <= 604800000*time.Millisecond: + return time.Millisecond * 86400000 // 24h + // 3w + case interval <= 1814400000*time.Millisecond: + return time.Millisecond * 604800000 // 1w + // 2y + case interval < 3628800000*time.Millisecond: + return time.Millisecond * 2592000000 // 30d + default: + return time.Millisecond * 31536000000 // 1y + } +} diff --git a/vendor/github.com/grafana/grafana/pkg/tsdb/models.go b/vendor/github.com/grafana/grafana/pkg/tsdb/models.go new file mode 100644 index 00000000000..6ff35fd6a5a --- /dev/null +++ b/vendor/github.com/grafana/grafana/pkg/tsdb/models.go @@ -0,0 +1,272 @@ +package tsdb + +import ( + "encoding/base64" + "encoding/json" + "fmt" + + "github.com/grafana/grafana-plugin-sdk-go/data" + "github.com/grafana/grafana/pkg/components/null" + "github.com/grafana/grafana/pkg/components/simplejson" + "github.com/grafana/grafana/pkg/models" + jsoniter "github.com/json-iterator/go" +) + +// TsdbQuery contains all information about a query request. +type TsdbQuery struct { + TimeRange *TimeRange + Queries []*Query + Headers map[string]string + Debug bool + User *models.SignedInUser +} + +type Query struct { + RefId string `json:"refId"` + Model *simplejson.Json `json:"model,omitempty"` + DataSource *models.DataSource `json:"datasource"` + MaxDataPoints int64 `json:"maxDataPoints"` + IntervalMs int64 `json:"intervalMs"` + QueryType string `json:"queryType"` +} + +type Response struct { + Results map[string]*QueryResult `json:"results"` + Message string `json:"message,omitempty"` +} + +type QueryResult struct { + Error error `json:"-"` + ErrorString string `json:"error,omitempty"` + RefId string `json:"refId"` + Meta *simplejson.Json `json:"meta,omitempty"` + Series TimeSeriesSlice `json:"series"` + Tables []*Table `json:"tables"` + Dataframes DataFrames `json:"dataframes"` +} + +// UnmarshalJSON deserializes a QueryResult from JSON. +// +// Deserialization support is required by tests. +func (r *QueryResult) UnmarshalJSON(b []byte) error { + m := map[string]interface{}{} + // TODO: Use JSON decoder + if err := json.Unmarshal(b, &m); err != nil { + return err + } + + refID, ok := m["refId"].(string) + if !ok { + return fmt.Errorf("can't decode field refId - not a string") + } + var meta *simplejson.Json + if m["meta"] != nil { + mm, ok := m["meta"].(map[string]interface{}) + if !ok { + return fmt.Errorf("can't decode field meta - not a JSON object") + } + meta = simplejson.NewFromAny(mm) + } + var series TimeSeriesSlice + /* TODO + if m["series"] != nil { + } + */ + var tables []*Table + if m["tables"] != nil { + ts, ok := m["tables"].([]interface{}) + if !ok { + return fmt.Errorf("can't decode field tables - not an array of Tables") + } + for _, ti := range ts { + tm, ok := ti.(map[string]interface{}) + if !ok { + return fmt.Errorf("can't decode field tables - not an array of Tables") + } + var columns []TableColumn + cs, ok := tm["columns"].([]interface{}) + if !ok { + return fmt.Errorf("can't decode field tables - not an array of Tables") + } + for _, ci := range cs { + cm, ok := ci.(map[string]interface{}) + if !ok { + return fmt.Errorf("can't decode field tables - not an array of Tables") + } + val, ok := cm["text"].(string) + if !ok { + return fmt.Errorf("can't decode field tables - not an array of Tables") + } + + columns = append(columns, TableColumn{Text: val}) + } + + rs, ok := tm["rows"].([]interface{}) + if !ok { + return fmt.Errorf("can't decode field tables - not an array of Tables") + } + var rows []RowValues + for _, ri := range rs { + vals, ok := ri.([]interface{}) + if !ok { + return fmt.Errorf("can't decode field tables - not an array of Tables") + } + rows = append(rows, vals) + } + + tables = append(tables, &Table{ + Columns: columns, + Rows: rows, + }) + } + } + + var dfs *dataFrames + if m["dataframes"] != nil { + raw, ok := m["dataframes"].([]interface{}) + if !ok { + return fmt.Errorf("can't decode field dataframes - not an array of byte arrays") + } + + var encoded [][]byte + for _, ra := range raw { + encS, ok := ra.(string) + if !ok { + return fmt.Errorf("can't decode field dataframes - not an array of byte arrays") + } + enc, err := base64.StdEncoding.DecodeString(encS) + if err != nil { + return fmt.Errorf("can't decode field dataframes - not an array of arrow frames") + } + encoded = append(encoded, enc) + } + decoded, err := data.UnmarshalArrowFrames(encoded) + if err != nil { + return err + } + dfs = &dataFrames{ + decoded: decoded, + encoded: encoded, + } + } + + r.RefId = refID + r.Meta = meta + r.Series = series + r.Tables = tables + if dfs != nil { + r.Dataframes = dfs + } + return nil +} + +type TimeSeries struct { + Name string `json:"name"` + Points TimeSeriesPoints `json:"points"` + Tags map[string]string `json:"tags,omitempty"` +} + +type Table struct { + Columns []TableColumn `json:"columns"` + Rows []RowValues `json:"rows"` +} + +type TableColumn struct { + Text string `json:"text"` +} + +type RowValues []interface{} +type TimePoint [2]null.Float +type TimeSeriesPoints []TimePoint +type TimeSeriesSlice []*TimeSeries + +func NewQueryResult() *QueryResult { + return &QueryResult{ + Series: make(TimeSeriesSlice, 0), + } +} + +func NewTimePoint(value null.Float, timestamp float64) TimePoint { + return TimePoint{value, null.FloatFrom(timestamp)} +} + +// DataFrames is an interface for retrieving encoded and decoded data frames. +// +// See NewDecodedDataFrames and NewEncodedDataFrames for more information. +type DataFrames interface { + // Encoded encodes Frames into a slice of []byte. + // If an error occurs [][]byte will be nil. + // The encoded result, if any, will be cached and returned next time Encoded is called. + Encoded() ([][]byte, error) + + // Decoded decodes a slice of Arrow encoded frames to data.Frames ([]*data.Frame). + // If an error occurs Frames will be nil. + // The decoded result, if any, will be cached and returned next time Decoded is called. + Decoded() (data.Frames, error) +} + +type dataFrames struct { + decoded data.Frames + encoded [][]byte +} + +// NewDecodedDataFrames instantiates DataFrames from decoded frames. +// +// This should be the primary function for creating DataFrames if you're implementing a plugin. +// In a Grafana alerting scenario it needs to operate on decoded frames, which is why this function is +// preferrable. When encoded data frames are needed, e.g. returned from Grafana HTTP API, it will +// happen automatically when MarshalJSON() is called. +func NewDecodedDataFrames(decodedFrames data.Frames) DataFrames { + return &dataFrames{ + decoded: decodedFrames, + } +} + +// NewEncodedDataFrames instantiates DataFrames from encoded frames. +// +// This one is primarily used for creating DataFrames when receiving encoded data frames from an external +// plugin or similar. This may allow the encoded data frames to be returned to Grafana UI without any additional +// decoding/encoding required. In Grafana alerting scenario it needs to operate on decoded data frames why encoded +// frames needs to be decoded before usage. +func NewEncodedDataFrames(encodedFrames [][]byte) DataFrames { + return &dataFrames{ + encoded: encodedFrames, + } +} + +func (df *dataFrames) Encoded() ([][]byte, error) { + if df.encoded == nil { + encoded, err := df.decoded.MarshalArrow() + if err != nil { + return nil, err + } + df.encoded = encoded + } + + return df.encoded, nil +} + +func (df *dataFrames) Decoded() (data.Frames, error) { + if df.decoded == nil { + decoded, err := data.UnmarshalArrowFrames(df.encoded) + if err != nil { + return nil, err + } + df.decoded = decoded + } + + return df.decoded, nil +} + +func (df *dataFrames) MarshalJSON() ([]byte, error) { + encoded, err := df.Encoded() + if err != nil { + return nil, err + } + + // Use a configuration that's compatible with the standard library + // to minimize the risk of introducing bugs. This will make sure + // that map keys is ordered. + jsonCfg := jsoniter.ConfigCompatibleWithStandardLibrary + return jsonCfg.Marshal(encoded) +} diff --git a/vendor/github.com/grafana/grafana/pkg/tsdb/query_endpoint.go b/vendor/github.com/grafana/grafana/pkg/tsdb/query_endpoint.go new file mode 100644 index 00000000000..856b7066832 --- /dev/null +++ b/vendor/github.com/grafana/grafana/pkg/tsdb/query_endpoint.go @@ -0,0 +1,23 @@ +package tsdb + +import ( + "context" + + "github.com/grafana/grafana/pkg/models" +) + +type TsdbQueryEndpoint interface { + Query(ctx context.Context, ds *models.DataSource, query *TsdbQuery) (*Response, error) +} + +var registry map[string]GetTsdbQueryEndpointFn + +type GetTsdbQueryEndpointFn func(dsInfo *models.DataSource) (TsdbQueryEndpoint, error) + +func init() { + registry = make(map[string]GetTsdbQueryEndpointFn) +} + +func RegisterTsdbQueryEndpoint(pluginId string, fn GetTsdbQueryEndpointFn) { + registry[pluginId] = fn +} diff --git a/vendor/github.com/grafana/grafana/pkg/tsdb/request.go b/vendor/github.com/grafana/grafana/pkg/tsdb/request.go new file mode 100644 index 00000000000..6d751374174 --- /dev/null +++ b/vendor/github.com/grafana/grafana/pkg/tsdb/request.go @@ -0,0 +1,26 @@ +package tsdb + +import ( + "context" + "fmt" + + "github.com/grafana/grafana/pkg/models" +) + +type HandleRequestFunc func(ctx context.Context, dsInfo *models.DataSource, req *TsdbQuery) (*Response, error) + +func HandleRequest(ctx context.Context, dsInfo *models.DataSource, req *TsdbQuery) (*Response, error) { + var endpoint TsdbQueryEndpoint + fn, exists := registry[dsInfo.Type] + if !exists { + return nil, fmt.Errorf("could not find executor for data source type: %s", dsInfo.Type) + } + + var err error + endpoint, err = fn(dsInfo) + if err != nil { + return nil, err + } + + return endpoint.Query(ctx, dsInfo, req) +} diff --git a/vendor/github.com/grafana/grafana/pkg/tsdb/time_range.go b/vendor/github.com/grafana/grafana/pkg/tsdb/time_range.go new file mode 100644 index 00000000000..eb855c0976b --- /dev/null +++ b/vendor/github.com/grafana/grafana/pkg/tsdb/time_range.go @@ -0,0 +1,132 @@ +package tsdb + +import ( + "strconv" + "strings" + "time" + + "github.com/timberio/go-datemath" +) + +func NewTimeRange(from, to string) *TimeRange { + return &TimeRange{ + From: from, + To: to, + now: time.Now(), + } +} + +func NewFakeTimeRange(from, to string, now time.Time) *TimeRange { + return &TimeRange{ + From: from, + To: to, + now: now, + } +} + +type TimeRange struct { + From string + To string + now time.Time +} + +func (tr *TimeRange) GetFromAsMsEpoch() int64 { + return tr.MustGetFrom().UnixNano() / int64(time.Millisecond) +} + +func (tr *TimeRange) GetFromAsSecondsEpoch() int64 { + return tr.GetFromAsMsEpoch() / 1000 +} + +func (tr *TimeRange) GetFromAsTimeUTC() time.Time { + return tr.MustGetFrom().UTC() +} + +func (tr *TimeRange) GetToAsMsEpoch() int64 { + return tr.MustGetTo().UnixNano() / int64(time.Millisecond) +} + +func (tr *TimeRange) GetToAsSecondsEpoch() int64 { + return tr.GetToAsMsEpoch() / 1000 +} + +func (tr *TimeRange) GetToAsTimeUTC() time.Time { + return tr.MustGetTo().UTC() +} + +func (tr *TimeRange) MustGetFrom() time.Time { + res, err := tr.ParseFrom() + if err != nil { + return time.Unix(0, 0) + } + return res +} + +func (tr *TimeRange) MustGetTo() time.Time { + res, err := tr.ParseTo() + if err != nil { + return time.Unix(0, 0) + } + return res +} + +func tryParseUnixMsEpoch(val string) (time.Time, bool) { + if val, err := strconv.ParseInt(val, 10, 64); err == nil { + seconds := val / 1000 + nano := (val - seconds*1000) * 1000000 + return time.Unix(seconds, nano), true + } + return time.Time{}, false +} + +func (tr *TimeRange) ParseFrom() (time.Time, error) { + return parse(tr.From, tr.now, false, nil) +} + +func (tr *TimeRange) ParseTo() (time.Time, error) { + return parse(tr.To, tr.now, true, nil) +} + +func (tr *TimeRange) ParseFromWithLocation(location *time.Location) (time.Time, error) { + return parse(tr.From, tr.now, false, location) +} + +func (tr *TimeRange) ParseToWithLocation(location *time.Location) (time.Time, error) { + return parse(tr.To, tr.now, true, location) +} + +func parse(s string, now time.Time, withRoundUp bool, location *time.Location) (time.Time, error) { + if res, ok := tryParseUnixMsEpoch(s); ok { + return res, nil + } + + diff, err := time.ParseDuration("-" + s) + if err != nil { + options := []func(*datemath.Options){ + datemath.WithNow(now), + datemath.WithRoundUp(withRoundUp), + } + if location != nil { + options = append(options, datemath.WithLocation(location)) + } + + return datemath.ParseAndEvaluate(s, options...) + } + + return now.Add(diff), nil +} + +// EpochPrecisionToMs converts epoch precision to millisecond, if needed. +// Only seconds to milliseconds supported right now +func EpochPrecisionToMs(value float64) float64 { + s := strconv.FormatFloat(value, 'e', -1, 64) + if strings.HasSuffix(s, "e+09") { + return value * float64(1e3) + } + + if strings.HasSuffix(s, "e+18") { + return value / float64(time.Millisecond) + } + + return value +} diff --git a/vendor/github.com/grpc-ecosystem/go-grpc-middleware/.gitignore b/vendor/github.com/grpc-ecosystem/go-grpc-middleware/.gitignore new file mode 100644 index 00000000000..826caa3902b --- /dev/null +++ b/vendor/github.com/grpc-ecosystem/go-grpc-middleware/.gitignore @@ -0,0 +1,204 @@ +# Created by .ignore support plugin (hsz.mobi) +### Go template +# Compiled Object files, Static and Dynamic libs (Shared Objects) +*.o +*.a +*.so + +# Folders +_obj +_test + +# Architecture specific extensions/prefixes +*.[568vq] +[568vq].out + +*.cgo1.go +*.cgo2.c +_cgo_defun.c +_cgo_gotypes.go +_cgo_export.* + +_testmain.go + +*.exe +*.test +*.prof +### Windows template +# Windows image file caches +Thumbs.db +ehthumbs.db + +# Folder config file +Desktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msm +*.msp + +# Windows shortcuts +*.lnk +### Kate template +# Swap Files # +.*.kate-swp +.swp.* +### SublimeText template +# cache files for sublime text +*.tmlanguage.cache +*.tmPreferences.cache +*.stTheme.cache + +# workspace files are user-specific +*.sublime-workspace + +# project files should be checked into the repository, unless a significant +# proportion of contributors will probably not be using SublimeText +# *.sublime-project + +# sftp configuration file +sftp-config.json +### Linux template +*~ + +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* +### JetBrains template +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff: +.idea +.idea/tasks.xml +.idea/dictionaries +.idea/vcs.xml +.idea/jsLibraryMappings.xml + +# Sensitive or high-churn files: +.idea/dataSources.ids +.idea/dataSources.xml +.idea/dataSources.local.xml +.idea/sqlDataSources.xml +.idea/dynamic.xml +.idea/uiDesigner.xml + +# Gradle: +.idea/gradle.xml +.idea/libraries + +# Mongo Explorer plugin: +.idea/mongoSettings.xml + +## File-based project format: +*.iws + +## Plugin-specific files: + +# IntelliJ +/out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties +### Xcode template +# Xcode +# +# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore + +## Build generated +build/ +DerivedData/ + +## Various settings +*.pbxuser +!default.pbxuser +*.mode1v3 +!default.mode1v3 +*.mode2v3 +!default.mode2v3 +*.perspectivev3 +!default.perspectivev3 +xcuserdata/ + +## Other +*.moved-aside +*.xccheckout +*.xcscmblueprint +### Eclipse template + +.metadata +bin/ +tmp/ +*.tmp +*.bak +*.swp +*~.nib +local.properties +.settings/ +.loadpath +.recommenders + +# Eclipse Core +.project + +# External tool builders +.externalToolBuilders/ + +# Locally stored "Eclipse launch configurations" +*.launch + +# PyDev specific (Python IDE for Eclipse) +*.pydevproject + +# CDT-specific (C/C++ Development Tooling) +.cproject + +# JDT-specific (Eclipse Java Development Tools) +.classpath + +# Java annotation processor (APT) +.factorypath + +# PDT-specific (PHP Development Tools) +.buildpath + +# sbteclipse plugin +.target + +# Tern plugin +.tern-project + +# TeXlipse plugin +.texlipse + +# STS (Spring Tool Suite) +.springBeans + +# Code Recommenders +.recommenders/ + + +coverage.txt + +#vendor +vendor/ + +.envrc \ No newline at end of file diff --git a/vendor/github.com/grpc-ecosystem/go-grpc-middleware/.travis.yml b/vendor/github.com/grpc-ecosystem/go-grpc-middleware/.travis.yml new file mode 100644 index 00000000000..e20b8f06d95 --- /dev/null +++ b/vendor/github.com/grpc-ecosystem/go-grpc-middleware/.travis.yml @@ -0,0 +1,18 @@ +sudo: false +language: go +go: + - 1.11.x + - 1.12.x + - 1.13.x + - 1.14.x + - 1.15.x + +env: + global: + - GO111MODULE=on + +script: + - make test + +after_success: + - bash <(curl -s https://codecov.io/bash) diff --git a/vendor/github.com/grpc-ecosystem/go-grpc-middleware/CHANGELOG.md b/vendor/github.com/grpc-ecosystem/go-grpc-middleware/CHANGELOG.md new file mode 100644 index 00000000000..6eeb7e2dc3f --- /dev/null +++ b/vendor/github.com/grpc-ecosystem/go-grpc-middleware/CHANGELOG.md @@ -0,0 +1,51 @@ +# Changelog +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) +and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). + +Types of changes: +- `Added` for new features. +- `Changed` for changes in existing functionality. +- `Deprecated` for soon-to-be removed features. +- `Removed` for now removed features. +- `Fixed` for any bug fixes. +- `Security` in case of vulnerabilities. + +## [Unreleased] + +### Added + +- [#223](https://github.com/grpc-ecosystem/go-grpc-middleware/pull/223) Add go-kit logging middleware - [adrien-f](https://github.com/adrien-f) + +## [v1.1.0] - 2019-09-12 +### Added +- [#226](https://github.com/grpc-ecosystem/go-grpc-middleware/pull/226) Support for go modules. +- [#221](https://github.com/grpc-ecosystem/go-grpc-middleware/pull/221) logging/zap add support for gRPC LoggerV2 - [kush-patel-hs](https://github.com/kush-patel-hs) +- [#181](https://github.com/grpc-ecosystem/go-grpc-middleware/pull/181) Rate Limit support - [ceshihao](https://github.com/ceshihao) +- [#161](https://github.com/grpc-ecosystem/go-grpc-middleware/pull/161) Retry on server stream call - [lonnblad](https://github.com/lonnblad) +- [#152](https://github.com/grpc-ecosystem/go-grpc-middleware/pull/152) Exponential backoff functions - [polyfloyd](https://github.com/polyfloyd) +- [#147](https://github.com/grpc-ecosystem/go-grpc-middleware/pull/147) Jaeger support for ctxtags extraction - [vporoshok](https://github.com/vporoshok) +- [#184](https://github.com/grpc-ecosystem/go-grpc-middleware/pull/184) ctxTags identifies if the call was sampled + +### Deprecated +- [#201](https://github.com/grpc-ecosystem/go-grpc-middleware/pull/201) `golang.org/x/net/context` - [houz42](https://github.com/houz42) +- [#183](https://github.com/grpc-ecosystem/go-grpc-middleware/pull/183) Documentation Generation in favour of . + +### Fixed +- [172](https://github.com/grpc-ecosystem/go-grpc-middleware/pull/172) Passing ctx into retry and recover - [johanbrandhorst](https://github.com/johanbrandhorst) +- Numerious documentation fixes. + +## v1.0.0 - 2018-05-08 +### Added +- grpc_auth +- grpc_ctxtags +- grpc_zap +- grpc_logrus +- grpc_opentracing +- grpc_retry +- grpc_validator +- grpc_recovery + +[Unreleased]: https://github.com/grpc-ecosystem/go-grpc-middleware/compare/v1.1.0...HEAD +[v1.1.0]: https://github.com/grpc-ecosystem/go-grpc-middleware/compare/v1.0.0...v1.1.0 diff --git a/vendor/github.com/grpc-ecosystem/go-grpc-middleware/CONTRIBUTING.md b/vendor/github.com/grpc-ecosystem/go-grpc-middleware/CONTRIBUTING.md new file mode 100644 index 00000000000..dd52ab8938e --- /dev/null +++ b/vendor/github.com/grpc-ecosystem/go-grpc-middleware/CONTRIBUTING.md @@ -0,0 +1,20 @@ +# Contributing + +We would love to have people submit pull requests and help make `grpc-ecosystem/go-grpc-middleware` even better 👍. + +Fork, then clone the repo: + +```bash +git clone git@github.com:your-username/go-grpc-middleware.git +``` + +Before checking in please run the following: + +```bash +make all +``` + +This will `vet`, `fmt`, regenerate documentation and run all tests. + + +Push to your fork and open a pull request. \ No newline at end of file diff --git a/vendor/github.com/grpc-ecosystem/go-grpc-middleware/LICENSE b/vendor/github.com/grpc-ecosystem/go-grpc-middleware/LICENSE new file mode 100644 index 00000000000..b2b065037fc --- /dev/null +++ b/vendor/github.com/grpc-ecosystem/go-grpc-middleware/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. \ No newline at end of file diff --git a/vendor/github.com/grpc-ecosystem/go-grpc-middleware/README.md b/vendor/github.com/grpc-ecosystem/go-grpc-middleware/README.md new file mode 100644 index 00000000000..625e24dc1be --- /dev/null +++ b/vendor/github.com/grpc-ecosystem/go-grpc-middleware/README.md @@ -0,0 +1,85 @@ +# Go gRPC Middleware + +[![Travis Build](https://travis-ci.org/grpc-ecosystem/go-grpc-middleware.svg?branch=master)](https://travis-ci.org/grpc-ecosystem/go-grpc-middleware) +[![Go Report Card](https://goreportcard.com/badge/github.com/grpc-ecosystem/go-grpc-middleware)](https://goreportcard.com/report/github.com/grpc-ecosystem/go-grpc-middleware) +[![GoDoc](http://img.shields.io/badge/GoDoc-Reference-blue.svg)](https://godoc.org/github.com/grpc-ecosystem/go-grpc-middleware) +[![SourceGraph](https://sourcegraph.com/github.com/grpc-ecosystem/go-grpc-middleware/-/badge.svg)](https://sourcegraph.com/github.com/grpc-ecosystem/go-grpc-middleware/?badge) +[![codecov](https://codecov.io/gh/grpc-ecosystem/go-grpc-middleware/branch/master/graph/badge.svg)](https://codecov.io/gh/grpc-ecosystem/go-grpc-middleware) +[![Apache 2.0 License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](LICENSE) +[![quality: production](https://img.shields.io/badge/quality-production-orange.svg)](#status) +[![Slack](https://img.shields.io/badge/slack-%23grpc--middleware-brightgreen)](https://slack.com/share/IRUQCFC23/9Tm7hxRFVKKNoajQfMOcUiIk/enQtODc4ODI4NTIyMDcxLWM5NDA0ZTE4Njg5YjRjYWZkMTI5MzQwNDY3YzBjMzE1YzdjOGM5ZjI1NDNiM2JmNzI2YjM5ODE5OTRiNTEyOWE) + +[gRPC Go](https://github.com/grpc/grpc-go) Middleware: interceptors, helpers, utilities. + +## Middleware + +[gRPC Go](https://github.com/grpc/grpc-go) recently acquired support for +Interceptors, i.e. [middleware](https://medium.com/@matryer/writing-middleware-in-golang-and-how-go-makes-it-so-much-fun-4375c1246e81#.gv7tdlghs) +that is executed either on the gRPC Server before the request is passed onto the user's application logic, or on the gRPC client around the user call. It is a perfect way to implement +common patterns: auth, logging, message, validation, retries or monitoring. + +These are generic building blocks that make it easy to build multiple microservices easily. +The purpose of this repository is to act as a go-to point for such reusable functionality. It contains +some of them itself, but also will link to useful external repos. + +`grpc_middleware` itself provides support for chaining interceptors, here's an example: + +```go +import "github.com/grpc-ecosystem/go-grpc-middleware" + +myServer := grpc.NewServer( + grpc.StreamInterceptor(grpc_middleware.ChainStreamServer( + grpc_ctxtags.StreamServerInterceptor(), + grpc_opentracing.StreamServerInterceptor(), + grpc_prometheus.StreamServerInterceptor, + grpc_zap.StreamServerInterceptor(zapLogger), + grpc_auth.StreamServerInterceptor(myAuthFunction), + grpc_recovery.StreamServerInterceptor(), + )), + grpc.UnaryInterceptor(grpc_middleware.ChainUnaryServer( + grpc_ctxtags.UnaryServerInterceptor(), + grpc_opentracing.UnaryServerInterceptor(), + grpc_prometheus.UnaryServerInterceptor, + grpc_zap.UnaryServerInterceptor(zapLogger), + grpc_auth.UnaryServerInterceptor(myAuthFunction), + grpc_recovery.UnaryServerInterceptor(), + )), +) +``` + +## Interceptors + +*Please send a PR to add new interceptors or middleware to this list* + +#### Auth + * [`grpc_auth`](auth) - a customizable (via `AuthFunc`) piece of auth middleware + +#### Logging + * [`grpc_ctxtags`](tags/) - a library that adds a `Tag` map to context, with data populated from request body + * [`grpc_zap`](logging/zap/) - integration of [zap](https://github.com/uber-go/zap) logging library into gRPC handlers. + * [`grpc_logrus`](logging/logrus/) - integration of [logrus](https://github.com/sirupsen/logrus) logging library into gRPC handlers. + * [`grpc_kit`](logging/kit/) - integration of [go-kit](https://github.com/go-kit/kit/tree/master/log) logging library into gRPC handlers. + +#### Monitoring + * [`grpc_prometheus`⚡](https://github.com/grpc-ecosystem/go-grpc-prometheus) - Prometheus client-side and server-side monitoring middleware + * [`otgrpc`⚡](https://github.com/grpc-ecosystem/grpc-opentracing/tree/master/go/otgrpc) - [OpenTracing](http://opentracing.io/) client-side and server-side interceptors + * [`grpc_opentracing`](tracing/opentracing) - [OpenTracing](http://opentracing.io/) client-side and server-side interceptors with support for streaming and handler-returned tags + +#### Client + * [`grpc_retry`](retry/) - a generic gRPC response code retry mechanism, client-side middleware + +#### Server + * [`grpc_validator`](validator/) - codegen inbound message validation from `.proto` options + * [`grpc_recovery`](recovery/) - turn panics into gRPC errors + * [`ratelimit`](ratelimit/) - grpc rate limiting by your own limiter + + +## Status + +This code has been running in *production* since May 2016 as the basis of the gRPC micro services stack at [Improbable](https://improbable.io). + +Additional tooling will be added, and contributions are welcome. + +## License + +`go-grpc-middleware` is released under the Apache 2.0 license. See the [LICENSE](LICENSE) file for details. diff --git a/vendor/github.com/grpc-ecosystem/go-grpc-middleware/chain.go b/vendor/github.com/grpc-ecosystem/go-grpc-middleware/chain.go new file mode 100644 index 00000000000..ea3738b896c --- /dev/null +++ b/vendor/github.com/grpc-ecosystem/go-grpc-middleware/chain.go @@ -0,0 +1,120 @@ +// Copyright 2016 Michal Witkowski. All Rights Reserved. +// See LICENSE for licensing terms. + +// gRPC Server Interceptor chaining middleware. + +package grpc_middleware + +import ( + "context" + + "google.golang.org/grpc" +) + +// ChainUnaryServer creates a single interceptor out of a chain of many interceptors. +// +// Execution is done in left-to-right order, including passing of context. +// For example ChainUnaryServer(one, two, three) will execute one before two before three, and three +// will see context changes of one and two. +func ChainUnaryServer(interceptors ...grpc.UnaryServerInterceptor) grpc.UnaryServerInterceptor { + n := len(interceptors) + + return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) { + chainer := func(currentInter grpc.UnaryServerInterceptor, currentHandler grpc.UnaryHandler) grpc.UnaryHandler { + return func(currentCtx context.Context, currentReq interface{}) (interface{}, error) { + return currentInter(currentCtx, currentReq, info, currentHandler) + } + } + + chainedHandler := handler + for i := n - 1; i >= 0; i-- { + chainedHandler = chainer(interceptors[i], chainedHandler) + } + + return chainedHandler(ctx, req) + } +} + +// ChainStreamServer creates a single interceptor out of a chain of many interceptors. +// +// Execution is done in left-to-right order, including passing of context. +// For example ChainUnaryServer(one, two, three) will execute one before two before three. +// If you want to pass context between interceptors, use WrapServerStream. +func ChainStreamServer(interceptors ...grpc.StreamServerInterceptor) grpc.StreamServerInterceptor { + n := len(interceptors) + + return func(srv interface{}, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error { + chainer := func(currentInter grpc.StreamServerInterceptor, currentHandler grpc.StreamHandler) grpc.StreamHandler { + return func(currentSrv interface{}, currentStream grpc.ServerStream) error { + return currentInter(currentSrv, currentStream, info, currentHandler) + } + } + + chainedHandler := handler + for i := n - 1; i >= 0; i-- { + chainedHandler = chainer(interceptors[i], chainedHandler) + } + + return chainedHandler(srv, ss) + } +} + +// ChainUnaryClient creates a single interceptor out of a chain of many interceptors. +// +// Execution is done in left-to-right order, including passing of context. +// For example ChainUnaryClient(one, two, three) will execute one before two before three. +func ChainUnaryClient(interceptors ...grpc.UnaryClientInterceptor) grpc.UnaryClientInterceptor { + n := len(interceptors) + + return func(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error { + chainer := func(currentInter grpc.UnaryClientInterceptor, currentInvoker grpc.UnaryInvoker) grpc.UnaryInvoker { + return func(currentCtx context.Context, currentMethod string, currentReq, currentRepl interface{}, currentConn *grpc.ClientConn, currentOpts ...grpc.CallOption) error { + return currentInter(currentCtx, currentMethod, currentReq, currentRepl, currentConn, currentInvoker, currentOpts...) + } + } + + chainedInvoker := invoker + for i := n - 1; i >= 0; i-- { + chainedInvoker = chainer(interceptors[i], chainedInvoker) + } + + return chainedInvoker(ctx, method, req, reply, cc, opts...) + } +} + +// ChainStreamClient creates a single interceptor out of a chain of many interceptors. +// +// Execution is done in left-to-right order, including passing of context. +// For example ChainStreamClient(one, two, three) will execute one before two before three. +func ChainStreamClient(interceptors ...grpc.StreamClientInterceptor) grpc.StreamClientInterceptor { + n := len(interceptors) + + return func(ctx context.Context, desc *grpc.StreamDesc, cc *grpc.ClientConn, method string, streamer grpc.Streamer, opts ...grpc.CallOption) (grpc.ClientStream, error) { + chainer := func(currentInter grpc.StreamClientInterceptor, currentStreamer grpc.Streamer) grpc.Streamer { + return func(currentCtx context.Context, currentDesc *grpc.StreamDesc, currentConn *grpc.ClientConn, currentMethod string, currentOpts ...grpc.CallOption) (grpc.ClientStream, error) { + return currentInter(currentCtx, currentDesc, currentConn, currentMethod, currentStreamer, currentOpts...) + } + } + + chainedStreamer := streamer + for i := n - 1; i >= 0; i-- { + chainedStreamer = chainer(interceptors[i], chainedStreamer) + } + + return chainedStreamer(ctx, desc, cc, method, opts...) + } +} + +// Chain creates a single interceptor out of a chain of many interceptors. +// +// WithUnaryServerChain is a grpc.Server config option that accepts multiple unary interceptors. +// Basically syntactic sugar. +func WithUnaryServerChain(interceptors ...grpc.UnaryServerInterceptor) grpc.ServerOption { + return grpc.UnaryInterceptor(ChainUnaryServer(interceptors...)) +} + +// WithStreamServerChain is a grpc.Server config option that accepts multiple stream interceptors. +// Basically syntactic sugar. +func WithStreamServerChain(interceptors ...grpc.StreamServerInterceptor) grpc.ServerOption { + return grpc.StreamInterceptor(ChainStreamServer(interceptors...)) +} diff --git a/vendor/github.com/grpc-ecosystem/go-grpc-middleware/doc.go b/vendor/github.com/grpc-ecosystem/go-grpc-middleware/doc.go new file mode 100644 index 00000000000..718e10046a2 --- /dev/null +++ b/vendor/github.com/grpc-ecosystem/go-grpc-middleware/doc.go @@ -0,0 +1,69 @@ +// Copyright 2016 Michal Witkowski. All Rights Reserved. +// See LICENSE for licensing terms. + +/* +`grpc_middleware` is a collection of gRPC middleware packages: interceptors, helpers and tools. + +Middleware + +gRPC is a fantastic RPC middleware, which sees a lot of adoption in the Golang world. However, the +upstream gRPC codebase is relatively bare bones. + +This package, and most of its child packages provides commonly needed middleware for gRPC: +client-side interceptors for retires, server-side interceptors for input validation and auth, +functions for chaining said interceptors, metadata convenience methods and more. + +Chaining + +By default, gRPC doesn't allow one to have more than one interceptor either on the client nor on +the server side. `grpc_middleware` provides convenient chaining methods + +Simple way of turning a multiple interceptors into a single interceptor. Here's an example for +server chaining: + + myServer := grpc.NewServer( + grpc.StreamInterceptor(grpc_middleware.ChainStreamServer(loggingStream, monitoringStream, authStream)), + grpc.UnaryInterceptor(grpc_middleware.ChainUnaryServer(loggingUnary, monitoringUnary, authUnary)), + ) + +These interceptors will be executed from left to right: logging, monitoring and auth. + +Here's an example for client side chaining: + + clientConn, err = grpc.Dial( + address, + grpc.WithUnaryInterceptor(grpc_middleware.ChainUnaryClient(monitoringClientUnary, retryUnary)), + grpc.WithStreamInterceptor(grpc_middleware.ChainStreamClient(monitoringClientStream, retryStream)), + ) + client = pb_testproto.NewTestServiceClient(clientConn) + resp, err := client.PingEmpty(s.ctx, &myservice.Request{Msg: "hello"}) + +These interceptors will be executed from left to right: monitoring and then retry logic. + +The retry interceptor will call every interceptor that follows it whenever when a retry happens. + +Writing Your Own + +Implementing your own interceptor is pretty trivial: there are interfaces for that. But the interesting +bit exposing common data to handlers (and other middleware), similarly to HTTP Middleware design. +For example, you may want to pass the identity of the caller from the auth interceptor all the way +to the handling function. + +For example, a client side interceptor example for auth looks like: + + func FakeAuthUnaryInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) { + newCtx := context.WithValue(ctx, "user_id", "john@example.com") + return handler(newCtx, req) + } + +Unfortunately, it's not as easy for streaming RPCs. These have the `context.Context` embedded within +the `grpc.ServerStream` object. To pass values through context, a wrapper (`WrappedServerStream`) is +needed. For example: + + func FakeAuthStreamingInterceptor(srv interface{}, stream grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error { + newStream := grpc_middleware.WrapServerStream(stream) + newStream.WrappedContext = context.WithValue(ctx, "user_id", "john@example.com") + return handler(srv, newStream) + } +*/ +package grpc_middleware diff --git a/vendor/github.com/grpc-ecosystem/go-grpc-middleware/go.mod b/vendor/github.com/grpc-ecosystem/go-grpc-middleware/go.mod new file mode 100644 index 00000000000..5a822d028db --- /dev/null +++ b/vendor/github.com/grpc-ecosystem/go-grpc-middleware/go.mod @@ -0,0 +1,25 @@ +module github.com/grpc-ecosystem/go-grpc-middleware + +require ( + github.com/go-kit/kit v0.9.0 + github.com/go-logfmt/logfmt v0.4.0 // indirect + github.com/go-stack/stack v1.8.0 // indirect + github.com/gogo/protobuf v1.2.1 + github.com/golang/protobuf v1.3.3 + github.com/opentracing/opentracing-go v1.1.0 + github.com/pkg/errors v0.8.1 // indirect + github.com/sirupsen/logrus v1.4.2 + github.com/stretchr/testify v1.4.0 + go.uber.org/atomic v1.4.0 // indirect + go.uber.org/multierr v1.1.0 // indirect + go.uber.org/zap v1.10.0 + golang.org/x/exp v0.0.0-20200331195152-e8c3332aa8e5 // indirect + golang.org/x/net v0.0.0-20200421231249-e086a090c8fd + golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be + golang.org/x/sys v0.0.0-20200420163511-1957bb5e6d1f // indirect + golang.org/x/text v0.3.2 // indirect + google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215 // indirect + google.golang.org/grpc v1.29.1 +) + +go 1.14 diff --git a/vendor/github.com/grpc-ecosystem/go-grpc-middleware/go.sum b/vendor/github.com/grpc-ecosystem/go-grpc-middleware/go.sum new file mode 100644 index 00000000000..44b52906a08 --- /dev/null +++ b/vendor/github.com/grpc-ecosystem/go-grpc-middleware/go.sum @@ -0,0 +1,140 @@ +cloud.google.com/go v0.26.0 h1:e0WKqKTd5BnrG8aKH3J3h+QvEIQtSUcf2n5UZ5ZgLtQ= +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/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/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-kit/kit v0.9.0 h1:wDJmvq38kDhkVxi50ni9ykkdUr1PKgqKOoi01fa0Mdk= +github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-logfmt/logfmt v0.4.0 h1:MP4Eh7ZCb31lleYCFuwm0oe4/YGak+5l1vA2NOE80nA= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/gogo/protobuf v1.2.1 h1:/s5zKNz0uPFCZ5hddgPdo2TK2TVrUNMn0OOX8/aZMTE= +github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= +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/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/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3 h1:gyjaxf+svBWX08ZjK86iN9geUJF0H6gp2IRKX6Nf6/I= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +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/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +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/kr/logfmt v0.0.0-20140226030751-b84e30acd515 h1:T+h1c/A9Gawja4Y9mFVWj2vyii2bbUNDw3kt9VxK2EY= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/opentracing/opentracing-go v1.1.0 h1:pWlfV3Bxv7k65HYwkikxat0+s3pV4bsqf19k25Ur8rU= +github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +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.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +go.uber.org/atomic v1.4.0 h1:cxzIVoETapQEqDhQu3QfnvXAV4AlzcvUCxkVUFw3+EU= +go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/multierr v1.1.0 h1:HoEmRHQPVSqub6w2z2d2EOVs2fjyFRGyofhKuyDq0QI= +go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/zap v1.10.0 h1:ORx85nbTijNz8ljznvCMR1ZBIPKFn3jQrag10X2AsuM= +go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4 h1:c2HOrn5iMezYjSlGPncknSEr/8x5LELb/ilJbXi9DEA= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20200331195152-e8c3332aa8e5 h1:FR+oGxGfbQu1d+jglI3rCkjAjUnhRSZcUxr+DqlDLNo= +golang.org/x/exp v0.0.0-20200331195152-e8c3332aa8e5/go.mod h1:4M0jN8W1tt0AVLNr8HDosyJCDCDuyL9N9+3m7wDWgKw= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b h1:GgiSbuUyC0BlbUmHQBgFqu32eiRR/CEYdjOjOd4zE6Y= +golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +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-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-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200421231249-e086a090c8fd h1:QPwSajcTUrFriMF1nJ3XzgoqakqQEsnZf9LdXdi2nkI= +golang.org/x/net v0.0.0-20200421231249-e086a090c8fd/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be h1:vEDujvNQGv4jgYKudGeI/+DAX4Jffq6hpD55MmoEvKs= +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-20190423024810-112230192c58/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-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894 h1:Cz4ceDQGXuKRnVBDTS23GTn/pU5OE2C0WrNTOYK1Uuc= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200420163511-1957bb5e6d1f h1:gWF768j/LaZugp8dyS4UwsslYCYz9XgFxvlgsn0n9H8= +golang.org/x/sys v0.0.0-20200420163511-1957bb5e6d1f/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.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa h1:5E4dL8+NgFOgjwbTKz+OOEGGhP+ectTmF842l6KjupQ= +golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898 h1:/atklqdjdhuosWIl6AIbOeHJjicWYPqR9bpxqxYG2pA= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/appengine v1.1.0 h1:igQkv0AAhEIvTEpD5LIpAfav2eeVO9HBTjvKHVJPRSs= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8 h1:Nw54tB0rB7hY/N0NQvRW8DG4Yk3Q6T9cu9RcFQDu1tc= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55 h1:gSJIx1SDwno+2ElGhA4+qG2zF97qiUzTM+rQ0klBOcE= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215 h1:0Uz5jLJQioKgVozXa1gzGbzYxbb/rhQEVvSWxzw5oUs= +google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/grpc v1.19.0 h1:cfg4PD8YEdSFnm7qLV4++93WcmhH2nIUhMjhdCvl3j8= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.29.1 h1:EC2SB8S04d2r73uptxphDSUG+kTKVgjRPF+N3xpxRB4= +google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= +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= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/vendor/github.com/grpc-ecosystem/go-grpc-middleware/makefile b/vendor/github.com/grpc-ecosystem/go-grpc-middleware/makefile new file mode 100644 index 00000000000..b18d2d2bb11 --- /dev/null +++ b/vendor/github.com/grpc-ecosystem/go-grpc-middleware/makefile @@ -0,0 +1,17 @@ +SHELL=/bin/bash + +GOFILES_NOVENDOR = $(shell go list ./... | grep -v /vendor/) + +all: vet fmt test + +fmt: + go fmt $(GOFILES_NOVENDOR) + +vet: + # do not check lostcancel, they are intentional. + go vet -lostcancel=false $(GOFILES_NOVENDOR) + +test: vet + ./scripts/test_all.sh + +.PHONY: all test diff --git a/vendor/github.com/grpc-ecosystem/go-grpc-middleware/slack.png b/vendor/github.com/grpc-ecosystem/go-grpc-middleware/slack.png new file mode 100644 index 00000000000..cc8f9a68a93 Binary files /dev/null and b/vendor/github.com/grpc-ecosystem/go-grpc-middleware/slack.png differ diff --git a/vendor/github.com/grpc-ecosystem/go-grpc-middleware/wrappers.go b/vendor/github.com/grpc-ecosystem/go-grpc-middleware/wrappers.go new file mode 100644 index 00000000000..05ccfb3f24a --- /dev/null +++ b/vendor/github.com/grpc-ecosystem/go-grpc-middleware/wrappers.go @@ -0,0 +1,30 @@ +// Copyright 2016 Michal Witkowski. All Rights Reserved. +// See LICENSE for licensing terms. + +package grpc_middleware + +import ( + "context" + + "google.golang.org/grpc" +) + +// WrappedServerStream is a thin wrapper around grpc.ServerStream that allows modifying context. +type WrappedServerStream struct { + grpc.ServerStream + // WrappedContext is the wrapper's own Context. You can assign it. + WrappedContext context.Context +} + +// Context returns the wrapper's WrappedContext, overwriting the nested grpc.ServerStream.Context() +func (w *WrappedServerStream) Context() context.Context { + return w.WrappedContext +} + +// WrapServerStream returns a ServerStream that has the ability to overwrite context. +func WrapServerStream(stream grpc.ServerStream) *WrappedServerStream { + if existing, ok := stream.(*WrappedServerStream); ok { + return existing + } + return &WrappedServerStream{ServerStream: stream, WrappedContext: stream.Context()} +} diff --git a/vendor/github.com/grpc-ecosystem/go-grpc-prometheus/.gitignore b/vendor/github.com/grpc-ecosystem/go-grpc-prometheus/.gitignore new file mode 100644 index 00000000000..2233cff9d1f --- /dev/null +++ b/vendor/github.com/grpc-ecosystem/go-grpc-prometheus/.gitignore @@ -0,0 +1,201 @@ +#vendor +vendor/ + +# Created by .ignore support plugin (hsz.mobi) +coverage.txt +### Go template +# Compiled Object files, Static and Dynamic libs (Shared Objects) +*.o +*.a +*.so + +# Folders +_obj +_test + +# Architecture specific extensions/prefixes +*.[568vq] +[568vq].out + +*.cgo1.go +*.cgo2.c +_cgo_defun.c +_cgo_gotypes.go +_cgo_export.* + +_testmain.go + +*.exe +*.test +*.prof +### Windows template +# Windows image file caches +Thumbs.db +ehthumbs.db + +# Folder config file +Desktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msm +*.msp + +# Windows shortcuts +*.lnk +### Kate template +# Swap Files # +.*.kate-swp +.swp.* +### SublimeText template +# cache files for sublime text +*.tmlanguage.cache +*.tmPreferences.cache +*.stTheme.cache + +# workspace files are user-specific +*.sublime-workspace + +# project files should be checked into the repository, unless a significant +# proportion of contributors will probably not be using SublimeText +# *.sublime-project + +# sftp configuration file +sftp-config.json +### Linux template +*~ + +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* +### JetBrains template +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff: +.idea +.idea/tasks.xml +.idea/dictionaries +.idea/vcs.xml +.idea/jsLibraryMappings.xml + +# Sensitive or high-churn files: +.idea/dataSources.ids +.idea/dataSources.xml +.idea/dataSources.local.xml +.idea/sqlDataSources.xml +.idea/dynamic.xml +.idea/uiDesigner.xml + +# Gradle: +.idea/gradle.xml +.idea/libraries + +# Mongo Explorer plugin: +.idea/mongoSettings.xml + +## File-based project format: +*.iws + +## Plugin-specific files: + +# IntelliJ +/out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties +### Xcode template +# Xcode +# +# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore + +## Build generated +build/ +DerivedData/ + +## Various settings +*.pbxuser +!default.pbxuser +*.mode1v3 +!default.mode1v3 +*.mode2v3 +!default.mode2v3 +*.perspectivev3 +!default.perspectivev3 +xcuserdata/ + +## Other +*.moved-aside +*.xccheckout +*.xcscmblueprint +### Eclipse template + +.metadata +bin/ +tmp/ +*.tmp +*.bak +*.swp +*~.nib +local.properties +.settings/ +.loadpath +.recommenders + +# Eclipse Core +.project + +# External tool builders +.externalToolBuilders/ + +# Locally stored "Eclipse launch configurations" +*.launch + +# PyDev specific (Python IDE for Eclipse) +*.pydevproject + +# CDT-specific (C/C++ Development Tooling) +.cproject + +# JDT-specific (Eclipse Java Development Tools) +.classpath + +# Java annotation processor (APT) +.factorypath + +# PDT-specific (PHP Development Tools) +.buildpath + +# sbteclipse plugin +.target + +# Tern plugin +.tern-project + +# TeXlipse plugin +.texlipse + +# STS (Spring Tool Suite) +.springBeans + +# Code Recommenders +.recommenders/ + diff --git a/vendor/github.com/grpc-ecosystem/go-grpc-prometheus/.travis.yml b/vendor/github.com/grpc-ecosystem/go-grpc-prometheus/.travis.yml new file mode 100644 index 00000000000..ac037f55736 --- /dev/null +++ b/vendor/github.com/grpc-ecosystem/go-grpc-prometheus/.travis.yml @@ -0,0 +1,23 @@ +sudo: false +language: go +go: + - 1.9.x + - 1.10.x + - 1.11.x + - 1.12.x + - master + +env: +- GO111MODULE=on + +install: +# With Go modules (eg Go >= 1.11), it isn't necessary anymore to 'go get' the dependencies before running the tests. +- if [[ $TRAVIS_GO_VERSION =~ ^1\.(7|8|9|10)\. ]]; then go get github.com/prometheus/client_golang/prometheus; fi +- if [[ $TRAVIS_GO_VERSION =~ ^1\.(7|8|9|10)\. ]]; then go get google.golang.org/grpc; fi +- if [[ $TRAVIS_GO_VERSION =~ ^1\.(7|8|9|10)\. ]]; then go get github.com/stretchr/testify; fi + +script: +- make test + +after_success: +- bash <(curl -s https://codecov.io/bash) diff --git a/vendor/github.com/grpc-ecosystem/go-grpc-prometheus/CHANGELOG.md b/vendor/github.com/grpc-ecosystem/go-grpc-prometheus/CHANGELOG.md new file mode 100644 index 00000000000..14e8869cb93 --- /dev/null +++ b/vendor/github.com/grpc-ecosystem/go-grpc-prometheus/CHANGELOG.md @@ -0,0 +1,28 @@ +# Changelog +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) +and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +### Added +* Support for error unwrapping. (Supported for `github.com/pkg/errors` and native wrapping added in go1.13) + +## [1.2.0](https://github.com/grpc-ecosystem/go-grpc-prometheus/releases/tag/v1.2.0) - 2018-06-04 + +### Added + +* Require go 1.9 or later and test against these versions in CI. +* Provide metrics object as `prometheus.Collector`, for conventional metric registration. +* Support non-default/global Prometheus registry. +* Allow configuring counters with `prometheus.CounterOpts`. + +### Changed + +* Remove usage of deprecated `grpc.Code()`. +* Remove usage of deprecated `grpc.Errorf` and replace with `status.Errorf`. + +--- + +This changelog was started with version `v1.2.0`, for earlier versions refer to the respective [GitHub releases](https://github.com/grpc-ecosystem/go-grpc-prometheus/releases). diff --git a/vendor/github.com/grpc-ecosystem/go-grpc-prometheus/LICENSE b/vendor/github.com/grpc-ecosystem/go-grpc-prometheus/LICENSE new file mode 100644 index 00000000000..b2b065037fc --- /dev/null +++ b/vendor/github.com/grpc-ecosystem/go-grpc-prometheus/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. \ No newline at end of file diff --git a/vendor/github.com/grpc-ecosystem/go-grpc-prometheus/README.md b/vendor/github.com/grpc-ecosystem/go-grpc-prometheus/README.md new file mode 100644 index 00000000000..b101dffce68 --- /dev/null +++ b/vendor/github.com/grpc-ecosystem/go-grpc-prometheus/README.md @@ -0,0 +1,250 @@ +# Go gRPC Interceptors for Prometheus monitoring + +[![Travis Build](https://travis-ci.org/grpc-ecosystem/go-grpc-prometheus.svg)](https://travis-ci.org/grpc-ecosystem/go-grpc-prometheus) +[![Go Report Card](https://goreportcard.com/badge/github.com/grpc-ecosystem/go-grpc-prometheus)](http://goreportcard.com/report/grpc-ecosystem/go-grpc-prometheus) +[![GoDoc](http://img.shields.io/badge/GoDoc-Reference-blue.svg)](https://godoc.org/github.com/grpc-ecosystem/go-grpc-prometheus) +[![SourceGraph](https://sourcegraph.com/github.com/grpc-ecosystem/go-grpc-prometheus/-/badge.svg)](https://sourcegraph.com/github.com/grpc-ecosystem/go-grpc-prometheus/?badge) +[![codecov](https://codecov.io/gh/grpc-ecosystem/go-grpc-prometheus/branch/master/graph/badge.svg)](https://codecov.io/gh/grpc-ecosystem/go-grpc-prometheus) +[![Slack](https://img.shields.io/badge/join%20slack-%23go--grpc--prometheus-brightgreen.svg)](https://join.slack.com/t/improbable-eng/shared_invite/enQtMzQ1ODcyMzQ5MjM4LWY5ZWZmNGM2ODc5MmViNmQ3ZTA3ZTY3NzQwOTBlMTkzZmIxZTIxODk0OWU3YjZhNWVlNDU3MDlkZGViZjhkMjc) +[![Apache 2.0 License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](LICENSE) + +[Prometheus](https://prometheus.io/) monitoring for your [gRPC Go](https://github.com/grpc/grpc-go) servers and clients. + +A sister implementation for [gRPC Java](https://github.com/grpc/grpc-java) (same metrics, same semantics) is in [grpc-ecosystem/java-grpc-prometheus](https://github.com/grpc-ecosystem/java-grpc-prometheus). + +## Interceptors + +[gRPC Go](https://github.com/grpc/grpc-go) recently acquired support for Interceptors, i.e. middleware that is executed +by a gRPC Server before the request is passed onto the user's application logic. It is a perfect way to implement +common patterns: auth, logging and... monitoring. + +To use Interceptors in chains, please see [`go-grpc-middleware`](https://github.com/mwitkow/go-grpc-middleware). + +This library requires Go 1.9 or later. + +## Usage + +There are two types of interceptors: client-side and server-side. This package provides monitoring Interceptors for both. + +### Server-side + +```go +import "github.com/grpc-ecosystem/go-grpc-prometheus" +... + // Initialize your gRPC server's interceptor. + myServer := grpc.NewServer( + grpc.StreamInterceptor(grpc_prometheus.StreamServerInterceptor), + grpc.UnaryInterceptor(grpc_prometheus.UnaryServerInterceptor), + ) + // Register your gRPC service implementations. + myservice.RegisterMyServiceServer(s.server, &myServiceImpl{}) + // After all your registrations, make sure all of the Prometheus metrics are initialized. + grpc_prometheus.Register(myServer) + // Register Prometheus metrics handler. + http.Handle("/metrics", promhttp.Handler()) +... +``` + +### Client-side + +```go +import "github.com/grpc-ecosystem/go-grpc-prometheus" +... + clientConn, err = grpc.Dial( + address, + grpc.WithUnaryInterceptor(grpc_prometheus.UnaryClientInterceptor), + grpc.WithStreamInterceptor(grpc_prometheus.StreamClientInterceptor) + ) + client = pb_testproto.NewTestServiceClient(clientConn) + resp, err := client.PingEmpty(s.ctx, &myservice.Request{Msg: "hello"}) +... +``` + +# Metrics + +## Labels + +All server-side metrics start with `grpc_server` as Prometheus subsystem name. All client-side metrics start with `grpc_client`. Both of them have mirror-concepts. Similarly all methods +contain the same rich labels: + + * `grpc_service` - the [gRPC service](http://www.grpc.io/docs/#defining-a-service) name, which is the combination of protobuf `package` and + the `grpc_service` section name. E.g. for `package = mwitkow.testproto` and + `service TestService` the label will be `grpc_service="mwitkow.testproto.TestService"` + * `grpc_method` - the name of the method called on the gRPC service. E.g. + `grpc_method="Ping"` + * `grpc_type` - the gRPC [type of request](http://www.grpc.io/docs/guides/concepts.html#rpc-life-cycle). + Differentiating between the two is important especially for latency measurements. + + - `unary` is single request, single response RPC + - `client_stream` is a multi-request, single response RPC + - `server_stream` is a single request, multi-response RPC + - `bidi_stream` is a multi-request, multi-response RPC + + +Additionally for completed RPCs, the following labels are used: + + * `grpc_code` - the human-readable [gRPC status code](https://github.com/grpc/grpc-go/blob/master/codes/codes.go). + The list of all statuses is to long, but here are some common ones: + + - `OK` - means the RPC was successful + - `IllegalArgument` - RPC contained bad values + - `Internal` - server-side error not disclosed to the clients + +## Counters + +The counters and their up to date documentation is in [server_reporter.go](server_reporter.go) and [client_reporter.go](client_reporter.go) +the respective Prometheus handler (usually `/metrics`). + +For the purpose of this documentation we will only discuss `grpc_server` metrics. The `grpc_client` ones contain mirror concepts. + +For simplicity, let's assume we're tracking a single server-side RPC call of [`mwitkow.testproto.TestService`](examples/testproto/test.proto), +calling the method `PingList`. The call succeeds and returns 20 messages in the stream. + +First, immediately after the server receives the call it will increment the +`grpc_server_started_total` and start the handling time clock (if histograms are enabled). + +```jsoniq +grpc_server_started_total{grpc_method="PingList",grpc_service="mwitkow.testproto.TestService",grpc_type="server_stream"} 1 +``` + +Then the user logic gets invoked. It receives one message from the client containing the request +(it's a `server_stream`): + +```jsoniq +grpc_server_msg_received_total{grpc_method="PingList",grpc_service="mwitkow.testproto.TestService",grpc_type="server_stream"} 1 +``` + +The user logic may return an error, or send multiple messages back to the client. In this case, on +each of the 20 messages sent back, a counter will be incremented: + +```jsoniq +grpc_server_msg_sent_total{grpc_method="PingList",grpc_service="mwitkow.testproto.TestService",grpc_type="server_stream"} 20 +``` + +After the call completes, its status (`OK` or other [gRPC status code](https://github.com/grpc/grpc-go/blob/master/codes/codes.go)) +and the relevant call labels increment the `grpc_server_handled_total` counter. + +```jsoniq +grpc_server_handled_total{grpc_code="OK",grpc_method="PingList",grpc_service="mwitkow.testproto.TestService",grpc_type="server_stream"} 1 +``` + +## Histograms + +[Prometheus histograms](https://prometheus.io/docs/concepts/metric_types/#histogram) are a great way +to measure latency distributions of your RPCs. However, since it is bad practice to have metrics +of [high cardinality](https://prometheus.io/docs/practices/instrumentation/#do-not-overuse-labels) +the latency monitoring metrics are disabled by default. To enable them please call the following +in your server initialization code: + +```jsoniq +grpc_prometheus.EnableHandlingTimeHistogram() +``` + +After the call completes, its handling time will be recorded in a [Prometheus histogram](https://prometheus.io/docs/concepts/metric_types/#histogram) +variable `grpc_server_handling_seconds`. The histogram variable contains three sub-metrics: + + * `grpc_server_handling_seconds_count` - the count of all completed RPCs by status and method + * `grpc_server_handling_seconds_sum` - cumulative time of RPCs by status and method, useful for + calculating average handling times + * `grpc_server_handling_seconds_bucket` - contains the counts of RPCs by status and method in respective + handling-time buckets. These buckets can be used by Prometheus to estimate SLAs (see [here](https://prometheus.io/docs/practices/histograms/)) + +The counter values will look as follows: + +```jsoniq +grpc_server_handling_seconds_bucket{grpc_code="OK",grpc_method="PingList",grpc_service="mwitkow.testproto.TestService",grpc_type="server_stream",le="0.005"} 1 +grpc_server_handling_seconds_bucket{grpc_code="OK",grpc_method="PingList",grpc_service="mwitkow.testproto.TestService",grpc_type="server_stream",le="0.01"} 1 +grpc_server_handling_seconds_bucket{grpc_code="OK",grpc_method="PingList",grpc_service="mwitkow.testproto.TestService",grpc_type="server_stream",le="0.025"} 1 +grpc_server_handling_seconds_bucket{grpc_code="OK",grpc_method="PingList",grpc_service="mwitkow.testproto.TestService",grpc_type="server_stream",le="0.05"} 1 +grpc_server_handling_seconds_bucket{grpc_code="OK",grpc_method="PingList",grpc_service="mwitkow.testproto.TestService",grpc_type="server_stream",le="0.1"} 1 +grpc_server_handling_seconds_bucket{grpc_code="OK",grpc_method="PingList",grpc_service="mwitkow.testproto.TestService",grpc_type="server_stream",le="0.25"} 1 +grpc_server_handling_seconds_bucket{grpc_code="OK",grpc_method="PingList",grpc_service="mwitkow.testproto.TestService",grpc_type="server_stream",le="0.5"} 1 +grpc_server_handling_seconds_bucket{grpc_code="OK",grpc_method="PingList",grpc_service="mwitkow.testproto.TestService",grpc_type="server_stream",le="1"} 1 +grpc_server_handling_seconds_bucket{grpc_code="OK",grpc_method="PingList",grpc_service="mwitkow.testproto.TestService",grpc_type="server_stream",le="2.5"} 1 +grpc_server_handling_seconds_bucket{grpc_code="OK",grpc_method="PingList",grpc_service="mwitkow.testproto.TestService",grpc_type="server_stream",le="5"} 1 +grpc_server_handling_seconds_bucket{grpc_code="OK",grpc_method="PingList",grpc_service="mwitkow.testproto.TestService",grpc_type="server_stream",le="10"} 1 +grpc_server_handling_seconds_bucket{grpc_code="OK",grpc_method="PingList",grpc_service="mwitkow.testproto.TestService",grpc_type="server_stream",le="+Inf"} 1 +grpc_server_handling_seconds_sum{grpc_code="OK",grpc_method="PingList",grpc_service="mwitkow.testproto.TestService",grpc_type="server_stream"} 0.0003866430000000001 +grpc_server_handling_seconds_count{grpc_code="OK",grpc_method="PingList",grpc_service="mwitkow.testproto.TestService",grpc_type="server_stream"} 1 +``` + + +## Useful query examples + +Prometheus philosophy is to provide raw metrics to the monitoring system, and +let the aggregations be handled there. The verbosity of above metrics make it possible to have that +flexibility. Here's a couple of useful monitoring queries: + + +### request inbound rate +```jsoniq +sum(rate(grpc_server_started_total{job="foo"}[1m])) by (grpc_service) +``` +For `job="foo"` (common label to differentiate between Prometheus monitoring targets), calculate the +rate of requests per second (1 minute window) for each gRPC `grpc_service` that the job has. Please note +how the `grpc_method` is being omitted here: all methods of a given gRPC service will be summed together. + +### unary request error rate +```jsoniq +sum(rate(grpc_server_handled_total{job="foo",grpc_type="unary",grpc_code!="OK"}[1m])) by (grpc_service) +``` +For `job="foo"`, calculate the per-`grpc_service` rate of `unary` (1:1) RPCs that failed, i.e. the +ones that didn't finish with `OK` code. + +### unary request error percentage +```jsoniq +sum(rate(grpc_server_handled_total{job="foo",grpc_type="unary",grpc_code!="OK"}[1m])) by (grpc_service) + / +sum(rate(grpc_server_started_total{job="foo",grpc_type="unary"}[1m])) by (grpc_service) + * 100.0 +``` +For `job="foo"`, calculate the percentage of failed requests by service. It's easy to notice that +this is a combination of the two above examples. This is an example of a query you would like to +[alert on](https://prometheus.io/docs/alerting/rules/) in your system for SLA violations, e.g. +"no more than 1% requests should fail". + +### average response stream size +```jsoniq +sum(rate(grpc_server_msg_sent_total{job="foo",grpc_type="server_stream"}[10m])) by (grpc_service) + / +sum(rate(grpc_server_started_total{job="foo",grpc_type="server_stream"}[10m])) by (grpc_service) +``` +For `job="foo"` what is the `grpc_service`-wide `10m` average of messages returned for all ` +server_stream` RPCs. This allows you to track the stream sizes returned by your system, e.g. allows +you to track when clients started to send "wide" queries that ret +Note the divisor is the number of started RPCs, in order to account for in-flight requests. + +### 99%-tile latency of unary requests +```jsoniq +histogram_quantile(0.99, + sum(rate(grpc_server_handling_seconds_bucket{job="foo",grpc_type="unary"}[5m])) by (grpc_service,le) +) +``` +For `job="foo"`, returns an 99%-tile [quantile estimation](https://prometheus.io/docs/practices/histograms/#quantiles) +of the handling time of RPCs per service. Please note the `5m` rate, this means that the quantile +estimation will take samples in a rolling `5m` window. When combined with other quantiles +(e.g. 50%, 90%), this query gives you tremendous insight into the responsiveness of your system +(e.g. impact of caching). + +### percentage of slow unary queries (>250ms) +```jsoniq +100.0 - ( +sum(rate(grpc_server_handling_seconds_bucket{job="foo",grpc_type="unary",le="0.25"}[5m])) by (grpc_service) + / +sum(rate(grpc_server_handling_seconds_count{job="foo",grpc_type="unary"}[5m])) by (grpc_service) +) * 100.0 +``` +For `job="foo"` calculate the by-`grpc_service` fraction of slow requests that took longer than `0.25` +seconds. This query is relatively complex, since the Prometheus aggregations use `le` (less or equal) +buckets, meaning that counting "fast" requests fractions is easier. However, simple maths helps. +This is an example of a query you would like to alert on in your system for SLA violations, +e.g. "less than 1% of requests are slower than 250ms". + + +## Status + +This code has been used since August 2015 as the basis for monitoring of *production* gRPC micro services at [Improbable](https://improbable.io). + +## License + +`go-grpc-prometheus` is released under the Apache 2.0 license. See the [LICENSE](LICENSE) file for details. diff --git a/vendor/github.com/grpc-ecosystem/go-grpc-prometheus/client.go b/vendor/github.com/grpc-ecosystem/go-grpc-prometheus/client.go new file mode 100644 index 00000000000..5b861b726e0 --- /dev/null +++ b/vendor/github.com/grpc-ecosystem/go-grpc-prometheus/client.go @@ -0,0 +1,57 @@ +// Copyright 2016 Michal Witkowski. All Rights Reserved. +// See LICENSE for licensing terms. + +// gRPC Prometheus monitoring interceptors for client-side gRPC. + +package grpc_prometheus + +import ( + prom "github.com/prometheus/client_golang/prometheus" +) + +var ( + // DefaultClientMetrics is the default instance of ClientMetrics. It is + // intended to be used in conjunction the default Prometheus metrics + // registry. + DefaultClientMetrics = NewClientMetrics() + + // UnaryClientInterceptor is a gRPC client-side interceptor that provides Prometheus monitoring for Unary RPCs. + UnaryClientInterceptor = DefaultClientMetrics.UnaryClientInterceptor() + + // StreamClientInterceptor is a gRPC client-side interceptor that provides Prometheus monitoring for Streaming RPCs. + StreamClientInterceptor = DefaultClientMetrics.StreamClientInterceptor() +) + +func init() { + prom.MustRegister(DefaultClientMetrics.clientStartedCounter) + prom.MustRegister(DefaultClientMetrics.clientHandledCounter) + prom.MustRegister(DefaultClientMetrics.clientStreamMsgReceived) + prom.MustRegister(DefaultClientMetrics.clientStreamMsgSent) +} + +// EnableClientHandlingTimeHistogram turns on recording of handling time of +// RPCs. Histogram metrics can be very expensive for Prometheus to retain and +// query. This function acts on the DefaultClientMetrics variable and the +// default Prometheus metrics registry. +func EnableClientHandlingTimeHistogram(opts ...HistogramOption) { + DefaultClientMetrics.EnableClientHandlingTimeHistogram(opts...) + prom.Register(DefaultClientMetrics.clientHandledHistogram) +} + +// EnableClientStreamReceiveTimeHistogram turns on recording of +// single message receive time of streaming RPCs. +// This function acts on the DefaultClientMetrics variable and the +// default Prometheus metrics registry. +func EnableClientStreamReceiveTimeHistogram(opts ...HistogramOption) { + DefaultClientMetrics.EnableClientStreamReceiveTimeHistogram(opts...) + prom.Register(DefaultClientMetrics.clientStreamRecvHistogram) +} + +// EnableClientStreamReceiveTimeHistogram turns on recording of +// single message send time of streaming RPCs. +// This function acts on the DefaultClientMetrics variable and the +// default Prometheus metrics registry. +func EnableClientStreamSendTimeHistogram(opts ...HistogramOption) { + DefaultClientMetrics.EnableClientStreamSendTimeHistogram(opts...) + prom.Register(DefaultClientMetrics.clientStreamSendHistogram) +} diff --git a/vendor/github.com/grpc-ecosystem/go-grpc-prometheus/client_metrics.go b/vendor/github.com/grpc-ecosystem/go-grpc-prometheus/client_metrics.go new file mode 100644 index 00000000000..2d29cde1d25 --- /dev/null +++ b/vendor/github.com/grpc-ecosystem/go-grpc-prometheus/client_metrics.go @@ -0,0 +1,244 @@ +package grpc_prometheus + +import ( + "context" + "io" + + prom "github.com/prometheus/client_golang/prometheus" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +// ClientMetrics represents a collection of metrics to be registered on a +// Prometheus metrics registry for a gRPC client. +type ClientMetrics struct { + clientStartedCounter *prom.CounterVec + clientHandledCounter *prom.CounterVec + clientStreamMsgReceived *prom.CounterVec + clientStreamMsgSent *prom.CounterVec + + clientHandledHistogramEnabled bool + clientHandledHistogramOpts prom.HistogramOpts + clientHandledHistogram *prom.HistogramVec + + clientStreamRecvHistogramEnabled bool + clientStreamRecvHistogramOpts prom.HistogramOpts + clientStreamRecvHistogram *prom.HistogramVec + + clientStreamSendHistogramEnabled bool + clientStreamSendHistogramOpts prom.HistogramOpts + clientStreamSendHistogram *prom.HistogramVec +} + +// NewClientMetrics returns a ClientMetrics object. Use a new instance of +// ClientMetrics when not using the default Prometheus metrics registry, for +// example when wanting to control which metrics are added to a registry as +// opposed to automatically adding metrics via init functions. +func NewClientMetrics(counterOpts ...CounterOption) *ClientMetrics { + opts := counterOptions(counterOpts) + return &ClientMetrics{ + clientStartedCounter: prom.NewCounterVec( + opts.apply(prom.CounterOpts{ + Name: "grpc_client_started_total", + Help: "Total number of RPCs started on the client.", + }), []string{"grpc_type", "grpc_service", "grpc_method"}), + + clientHandledCounter: prom.NewCounterVec( + opts.apply(prom.CounterOpts{ + Name: "grpc_client_handled_total", + Help: "Total number of RPCs completed by the client, regardless of success or failure.", + }), []string{"grpc_type", "grpc_service", "grpc_method", "grpc_code"}), + + clientStreamMsgReceived: prom.NewCounterVec( + opts.apply(prom.CounterOpts{ + Name: "grpc_client_msg_received_total", + Help: "Total number of RPC stream messages received by the client.", + }), []string{"grpc_type", "grpc_service", "grpc_method"}), + + clientStreamMsgSent: prom.NewCounterVec( + opts.apply(prom.CounterOpts{ + Name: "grpc_client_msg_sent_total", + Help: "Total number of gRPC stream messages sent by the client.", + }), []string{"grpc_type", "grpc_service", "grpc_method"}), + + clientHandledHistogramEnabled: false, + clientHandledHistogramOpts: prom.HistogramOpts{ + Name: "grpc_client_handling_seconds", + Help: "Histogram of response latency (seconds) of the gRPC until it is finished by the application.", + Buckets: prom.DefBuckets, + }, + clientHandledHistogram: nil, + clientStreamRecvHistogramEnabled: false, + clientStreamRecvHistogramOpts: prom.HistogramOpts{ + Name: "grpc_client_msg_recv_handling_seconds", + Help: "Histogram of response latency (seconds) of the gRPC single message receive.", + Buckets: prom.DefBuckets, + }, + clientStreamRecvHistogram: nil, + clientStreamSendHistogramEnabled: false, + clientStreamSendHistogramOpts: prom.HistogramOpts{ + Name: "grpc_client_msg_send_handling_seconds", + Help: "Histogram of response latency (seconds) of the gRPC single message send.", + Buckets: prom.DefBuckets, + }, + clientStreamSendHistogram: nil, + } +} + +// Describe sends the super-set of all possible descriptors of metrics +// collected by this Collector to the provided channel and returns once +// the last descriptor has been sent. +func (m *ClientMetrics) Describe(ch chan<- *prom.Desc) { + m.clientStartedCounter.Describe(ch) + m.clientHandledCounter.Describe(ch) + m.clientStreamMsgReceived.Describe(ch) + m.clientStreamMsgSent.Describe(ch) + if m.clientHandledHistogramEnabled { + m.clientHandledHistogram.Describe(ch) + } + if m.clientStreamRecvHistogramEnabled { + m.clientStreamRecvHistogram.Describe(ch) + } + if m.clientStreamSendHistogramEnabled { + m.clientStreamSendHistogram.Describe(ch) + } +} + +// Collect is called by the Prometheus registry when collecting +// metrics. The implementation sends each collected metric via the +// provided channel and returns once the last metric has been sent. +func (m *ClientMetrics) Collect(ch chan<- prom.Metric) { + m.clientStartedCounter.Collect(ch) + m.clientHandledCounter.Collect(ch) + m.clientStreamMsgReceived.Collect(ch) + m.clientStreamMsgSent.Collect(ch) + if m.clientHandledHistogramEnabled { + m.clientHandledHistogram.Collect(ch) + } + if m.clientStreamRecvHistogramEnabled { + m.clientStreamRecvHistogram.Collect(ch) + } + if m.clientStreamSendHistogramEnabled { + m.clientStreamSendHistogram.Collect(ch) + } +} + +// EnableClientHandlingTimeHistogram turns on recording of handling time of RPCs. +// Histogram metrics can be very expensive for Prometheus to retain and query. +func (m *ClientMetrics) EnableClientHandlingTimeHistogram(opts ...HistogramOption) { + for _, o := range opts { + o(&m.clientHandledHistogramOpts) + } + if !m.clientHandledHistogramEnabled { + m.clientHandledHistogram = prom.NewHistogramVec( + m.clientHandledHistogramOpts, + []string{"grpc_type", "grpc_service", "grpc_method"}, + ) + } + m.clientHandledHistogramEnabled = true +} + +// EnableClientStreamReceiveTimeHistogram turns on recording of single message receive time of streaming RPCs. +// Histogram metrics can be very expensive for Prometheus to retain and query. +func (m *ClientMetrics) EnableClientStreamReceiveTimeHistogram(opts ...HistogramOption) { + for _, o := range opts { + o(&m.clientStreamRecvHistogramOpts) + } + + if !m.clientStreamRecvHistogramEnabled { + m.clientStreamRecvHistogram = prom.NewHistogramVec( + m.clientStreamRecvHistogramOpts, + []string{"grpc_type", "grpc_service", "grpc_method"}, + ) + } + + m.clientStreamRecvHistogramEnabled = true +} + +// EnableClientStreamSendTimeHistogram turns on recording of single message send time of streaming RPCs. +// Histogram metrics can be very expensive for Prometheus to retain and query. +func (m *ClientMetrics) EnableClientStreamSendTimeHistogram(opts ...HistogramOption) { + for _, o := range opts { + o(&m.clientStreamSendHistogramOpts) + } + + if !m.clientStreamSendHistogramEnabled { + m.clientStreamSendHistogram = prom.NewHistogramVec( + m.clientStreamSendHistogramOpts, + []string{"grpc_type", "grpc_service", "grpc_method"}, + ) + } + + m.clientStreamSendHistogramEnabled = true +} + +// UnaryClientInterceptor is a gRPC client-side interceptor that provides Prometheus monitoring for Unary RPCs. +func (m *ClientMetrics) UnaryClientInterceptor() func(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error { + return func(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error { + monitor := newClientReporter(m, Unary, method) + monitor.SentMessage() + err := invoker(ctx, method, req, reply, cc, opts...) + if err != nil { + monitor.ReceivedMessage() + } + st, _ := status.FromError(err) + monitor.Handled(st.Code()) + return err + } +} + +// StreamClientInterceptor is a gRPC client-side interceptor that provides Prometheus monitoring for Streaming RPCs. +func (m *ClientMetrics) StreamClientInterceptor() func(ctx context.Context, desc *grpc.StreamDesc, cc *grpc.ClientConn, method string, streamer grpc.Streamer, opts ...grpc.CallOption) (grpc.ClientStream, error) { + return func(ctx context.Context, desc *grpc.StreamDesc, cc *grpc.ClientConn, method string, streamer grpc.Streamer, opts ...grpc.CallOption) (grpc.ClientStream, error) { + monitor := newClientReporter(m, clientStreamType(desc), method) + clientStream, err := streamer(ctx, desc, cc, method, opts...) + if err != nil { + st, _ := status.FromError(err) + monitor.Handled(st.Code()) + return nil, err + } + return &monitoredClientStream{clientStream, monitor}, nil + } +} + +func clientStreamType(desc *grpc.StreamDesc) grpcType { + if desc.ClientStreams && !desc.ServerStreams { + return ClientStream + } else if !desc.ClientStreams && desc.ServerStreams { + return ServerStream + } + return BidiStream +} + +// monitoredClientStream wraps grpc.ClientStream allowing each Sent/Recv of message to increment counters. +type monitoredClientStream struct { + grpc.ClientStream + monitor *clientReporter +} + +func (s *monitoredClientStream) SendMsg(m interface{}) error { + timer := s.monitor.SendMessageTimer() + err := s.ClientStream.SendMsg(m) + timer.ObserveDuration() + if err == nil { + s.monitor.SentMessage() + } + return err +} + +func (s *monitoredClientStream) RecvMsg(m interface{}) error { + timer := s.monitor.ReceiveMessageTimer() + err := s.ClientStream.RecvMsg(m) + timer.ObserveDuration() + + if err == nil { + s.monitor.ReceivedMessage() + } else if err == io.EOF { + s.monitor.Handled(codes.OK) + } else { + st, _ := status.FromError(err) + s.monitor.Handled(st.Code()) + } + return err +} diff --git a/vendor/github.com/grpc-ecosystem/go-grpc-prometheus/client_reporter.go b/vendor/github.com/grpc-ecosystem/go-grpc-prometheus/client_reporter.go new file mode 100644 index 00000000000..286d6571cae --- /dev/null +++ b/vendor/github.com/grpc-ecosystem/go-grpc-prometheus/client_reporter.go @@ -0,0 +1,79 @@ +// Copyright 2016 Michal Witkowski. All Rights Reserved. +// See LICENSE for licensing terms. + +package grpc_prometheus + +import ( + "time" + + "github.com/prometheus/client_golang/prometheus" + "google.golang.org/grpc/codes" +) + +type clientReporter struct { + metrics *ClientMetrics + rpcType grpcType + serviceName string + methodName string + startTime time.Time +} + +func newClientReporter(m *ClientMetrics, rpcType grpcType, fullMethod string) *clientReporter { + r := &clientReporter{ + metrics: m, + rpcType: rpcType, + } + if r.metrics.clientHandledHistogramEnabled { + r.startTime = time.Now() + } + r.serviceName, r.methodName = splitMethodName(fullMethod) + r.metrics.clientStartedCounter.WithLabelValues(string(r.rpcType), r.serviceName, r.methodName).Inc() + return r +} + +// timer is a helper interface to time functions. +type timer interface { + ObserveDuration() time.Duration +} + +type noOpTimer struct { +} + +func (noOpTimer) ObserveDuration() time.Duration { + return 0 +} + +var emptyTimer = noOpTimer{} + +func (r *clientReporter) ReceiveMessageTimer() timer { + if r.metrics.clientStreamRecvHistogramEnabled { + hist := r.metrics.clientStreamRecvHistogram.WithLabelValues(string(r.rpcType), r.serviceName, r.methodName) + return prometheus.NewTimer(hist) + } + + return emptyTimer +} + +func (r *clientReporter) ReceivedMessage() { + r.metrics.clientStreamMsgReceived.WithLabelValues(string(r.rpcType), r.serviceName, r.methodName).Inc() +} + +func (r *clientReporter) SendMessageTimer() timer { + if r.metrics.clientStreamSendHistogramEnabled { + hist := r.metrics.clientStreamSendHistogram.WithLabelValues(string(r.rpcType), r.serviceName, r.methodName) + return prometheus.NewTimer(hist) + } + + return emptyTimer +} + +func (r *clientReporter) SentMessage() { + r.metrics.clientStreamMsgSent.WithLabelValues(string(r.rpcType), r.serviceName, r.methodName).Inc() +} + +func (r *clientReporter) Handled(code codes.Code) { + r.metrics.clientHandledCounter.WithLabelValues(string(r.rpcType), r.serviceName, r.methodName, code.String()).Inc() + if r.metrics.clientHandledHistogramEnabled { + r.metrics.clientHandledHistogram.WithLabelValues(string(r.rpcType), r.serviceName, r.methodName).Observe(time.Since(r.startTime).Seconds()) + } +} diff --git a/vendor/github.com/grpc-ecosystem/go-grpc-prometheus/go.mod b/vendor/github.com/grpc-ecosystem/go-grpc-prometheus/go.mod new file mode 100644 index 00000000000..c49d1108c9b --- /dev/null +++ b/vendor/github.com/grpc-ecosystem/go-grpc-prometheus/go.mod @@ -0,0 +1,10 @@ +module github.com/grpc-ecosystem/go-grpc-prometheus + +require ( + github.com/golang/protobuf v1.2.0 + github.com/prometheus/client_golang v0.9.2 + github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90 + github.com/stretchr/testify v1.3.0 + golang.org/x/net v0.0.0-20190213061140-3a22650c66bd + google.golang.org/grpc v1.18.0 +) diff --git a/vendor/github.com/grpc-ecosystem/go-grpc-prometheus/go.sum b/vendor/github.com/grpc-ecosystem/go-grpc-prometheus/go.sum new file mode 100644 index 00000000000..485e90ad41f --- /dev/null +++ b/vendor/github.com/grpc-ecosystem/go-grpc-prometheus/go.sum @@ -0,0 +1,48 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +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/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/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/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/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +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/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_golang v0.9.2 h1:awm861/B8OKDd2I/6o1dy3ra4BamzKhYOiGItCeZ740= +github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +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/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= +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= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +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-20190213061140-3a22650c66bd h1:HuTn7WObtcDo9uEEU7rEqL0jYthdXAmZ6PP+meazmaU= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +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 h1:Bl/8QSvNqXvPGPGXa2z5xUTmV7VDcZyvRZ+QQXkXTZQ= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522 h1:Ve1ORMCxvRmSXBwJK+t3Oy+V2vRW2OetUQBq4rJIkZE= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +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/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8 h1:Nw54tB0rB7hY/N0NQvRW8DG4Yk3Q6T9cu9RcFQDu1tc= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/grpc v1.18.0 h1:IZl7mfBGfbhYx2p2rKRtYgDFw6SBz+kclmxYrCksPPA= +google.golang.org/grpc v1.18.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= +honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/vendor/github.com/grpc-ecosystem/go-grpc-prometheus/makefile b/vendor/github.com/grpc-ecosystem/go-grpc-prometheus/makefile new file mode 100644 index 00000000000..6cfc8d27fef --- /dev/null +++ b/vendor/github.com/grpc-ecosystem/go-grpc-prometheus/makefile @@ -0,0 +1,14 @@ +GOFILES_NOVENDOR = $(shell go list ./... | grep -v /vendor/) + +all: vet fmt test + +fmt: + go fmt $(GOFILES_NOVENDOR) + +vet: + go vet $(GOFILES_NOVENDOR) + +test: vet + ./scripts/test_all.sh + +.PHONY: all vet test diff --git a/vendor/github.com/grpc-ecosystem/go-grpc-prometheus/metric_options.go b/vendor/github.com/grpc-ecosystem/go-grpc-prometheus/metric_options.go new file mode 100644 index 00000000000..9d51aec9816 --- /dev/null +++ b/vendor/github.com/grpc-ecosystem/go-grpc-prometheus/metric_options.go @@ -0,0 +1,41 @@ +package grpc_prometheus + +import ( + prom "github.com/prometheus/client_golang/prometheus" +) + +// A CounterOption lets you add options to Counter metrics using With* funcs. +type CounterOption func(*prom.CounterOpts) + +type counterOptions []CounterOption + +func (co counterOptions) apply(o prom.CounterOpts) prom.CounterOpts { + for _, f := range co { + f(&o) + } + return o +} + +// WithConstLabels allows you to add ConstLabels to Counter metrics. +func WithConstLabels(labels prom.Labels) CounterOption { + return func(o *prom.CounterOpts) { + o.ConstLabels = labels + } +} + +// A HistogramOption lets you add options to Histogram metrics using With* +// funcs. +type HistogramOption func(*prom.HistogramOpts) + +// WithHistogramBuckets allows you to specify custom bucket ranges for histograms if EnableHandlingTimeHistogram is on. +func WithHistogramBuckets(buckets []float64) HistogramOption { + return func(o *prom.HistogramOpts) { o.Buckets = buckets } +} + +// WithHistogramConstLabels allows you to add custom ConstLabels to +// histograms metrics. +func WithHistogramConstLabels(labels prom.Labels) HistogramOption { + return func(o *prom.HistogramOpts) { + o.ConstLabels = labels + } +} diff --git a/vendor/github.com/grpc-ecosystem/go-grpc-prometheus/packages/grpcstatus/grpcstatus.go b/vendor/github.com/grpc-ecosystem/go-grpc-prometheus/packages/grpcstatus/grpcstatus.go new file mode 100644 index 00000000000..6c0f285f159 --- /dev/null +++ b/vendor/github.com/grpc-ecosystem/go-grpc-prometheus/packages/grpcstatus/grpcstatus.go @@ -0,0 +1,57 @@ +package grpcstatus + +import ( + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +type gRPCStatus interface { + GRPCStatus() *status.Status +} + +func unwrapPkgErrorsGRPCStatus(err error) (*status.Status, bool) { + type causer interface { + Cause() error + } + + // Unwrapping the github.com/pkg/errors causer interface, using `Cause` directly could miss some error implementing + // the `GRPCStatus` function so we have to check it on our selves. + unwrappedCauser := err + for unwrappedCauser != nil { + if s, ok := unwrappedCauser.(gRPCStatus); ok { + return s.GRPCStatus(), true + } + cause, ok := unwrappedCauser.(causer) + if !ok { + break + } + unwrappedCauser = cause.Cause() + } + return nil, false +} + +// Since error can be wrapped and the `FromError` function only checks for `GRPCStatus` function +// and as a fallback uses the `Unknown` gRPC status we need to unwrap the error if possible to get the original status. +// pkg/errors and Go native errors packages have two different approaches so we try to unwrap both types. +// Eventually should be implemented in the go-grpc status function `FromError`. See https://github.com/grpc/grpc-go/issues/2934 +func FromError(err error) (s *status.Status, ok bool) { + s, ok = status.FromError(err) + if ok { + return s, true + } + + // Try to unwrap `github.com/pkg/errors` wrapped error + s, ok = unwrapPkgErrorsGRPCStatus(err) + if ok { + return s, true + } + + // Try to unwrap native wrapped errors using `fmt.Errorf` and `%w` + s, ok = unwrapNativeWrappedGRPCStatus(err) + if ok { + return s, true + } + + // We failed to unwrap any GRPSStatus so return default `Unknown` + return status.New(codes.Unknown, err.Error()), false +} diff --git a/vendor/github.com/grpc-ecosystem/go-grpc-prometheus/packages/grpcstatus/native_unwrap1.12-.go b/vendor/github.com/grpc-ecosystem/go-grpc-prometheus/packages/grpcstatus/native_unwrap1.12-.go new file mode 100644 index 00000000000..0f575fe2943 --- /dev/null +++ b/vendor/github.com/grpc-ecosystem/go-grpc-prometheus/packages/grpcstatus/native_unwrap1.12-.go @@ -0,0 +1,11 @@ +// +build !go1.13 + +package grpcstatus + +import ( + "google.golang.org/grpc/status" +) + +func unwrapNativeWrappedGRPCStatus(err error) (*status.Status, bool) { + return nil, false +} diff --git a/vendor/github.com/grpc-ecosystem/go-grpc-prometheus/packages/grpcstatus/native_unwrap1.13+.go b/vendor/github.com/grpc-ecosystem/go-grpc-prometheus/packages/grpcstatus/native_unwrap1.13+.go new file mode 100644 index 00000000000..216d057e892 --- /dev/null +++ b/vendor/github.com/grpc-ecosystem/go-grpc-prometheus/packages/grpcstatus/native_unwrap1.13+.go @@ -0,0 +1,17 @@ +// +build go1.13 + +package grpcstatus + +import ( + "errors" + "google.golang.org/grpc/status" +) + +func unwrapNativeWrappedGRPCStatus(err error) (*status.Status, bool) { + // Unwrapping the native Go unwrap interface + var unwrappedStatus gRPCStatus + if ok := errors.As(err, &unwrappedStatus); ok { + return unwrappedStatus.GRPCStatus(), true + } + return nil, false +} diff --git a/vendor/github.com/grpc-ecosystem/go-grpc-prometheus/server.go b/vendor/github.com/grpc-ecosystem/go-grpc-prometheus/server.go new file mode 100644 index 00000000000..322f99046fd --- /dev/null +++ b/vendor/github.com/grpc-ecosystem/go-grpc-prometheus/server.go @@ -0,0 +1,48 @@ +// Copyright 2016 Michal Witkowski. All Rights Reserved. +// See LICENSE for licensing terms. + +// gRPC Prometheus monitoring interceptors for server-side gRPC. + +package grpc_prometheus + +import ( + prom "github.com/prometheus/client_golang/prometheus" + "google.golang.org/grpc" +) + +var ( + // DefaultServerMetrics is the default instance of ServerMetrics. It is + // intended to be used in conjunction the default Prometheus metrics + // registry. + DefaultServerMetrics = NewServerMetrics() + + // UnaryServerInterceptor is a gRPC server-side interceptor that provides Prometheus monitoring for Unary RPCs. + UnaryServerInterceptor = DefaultServerMetrics.UnaryServerInterceptor() + + // StreamServerInterceptor is a gRPC server-side interceptor that provides Prometheus monitoring for Streaming RPCs. + StreamServerInterceptor = DefaultServerMetrics.StreamServerInterceptor() +) + +func init() { + prom.MustRegister(DefaultServerMetrics.serverStartedCounter) + prom.MustRegister(DefaultServerMetrics.serverHandledCounter) + prom.MustRegister(DefaultServerMetrics.serverStreamMsgReceived) + prom.MustRegister(DefaultServerMetrics.serverStreamMsgSent) +} + +// Register takes a gRPC server and pre-initializes all counters to 0. This +// allows for easier monitoring in Prometheus (no missing metrics), and should +// be called *after* all services have been registered with the server. This +// function acts on the DefaultServerMetrics variable. +func Register(server *grpc.Server) { + DefaultServerMetrics.InitializeMetrics(server) +} + +// EnableHandlingTimeHistogram turns on recording of handling time +// of RPCs. Histogram metrics can be very expensive for Prometheus +// to retain and query. This function acts on the DefaultServerMetrics +// variable and the default Prometheus metrics registry. +func EnableHandlingTimeHistogram(opts ...HistogramOption) { + DefaultServerMetrics.EnableHandlingTimeHistogram(opts...) + prom.Register(DefaultServerMetrics.serverHandledHistogram) +} diff --git a/vendor/github.com/grpc-ecosystem/go-grpc-prometheus/server_metrics.go b/vendor/github.com/grpc-ecosystem/go-grpc-prometheus/server_metrics.go new file mode 100644 index 00000000000..d28a46e89b5 --- /dev/null +++ b/vendor/github.com/grpc-ecosystem/go-grpc-prometheus/server_metrics.go @@ -0,0 +1,186 @@ +package grpc_prometheus + +import ( + "context" + "github.com/grpc-ecosystem/go-grpc-prometheus/packages/grpcstatus" + prom "github.com/prometheus/client_golang/prometheus" + + "google.golang.org/grpc" +) + +// ServerMetrics represents a collection of metrics to be registered on a +// Prometheus metrics registry for a gRPC server. +type ServerMetrics struct { + serverStartedCounter *prom.CounterVec + serverHandledCounter *prom.CounterVec + serverStreamMsgReceived *prom.CounterVec + serverStreamMsgSent *prom.CounterVec + serverHandledHistogramEnabled bool + serverHandledHistogramOpts prom.HistogramOpts + serverHandledHistogram *prom.HistogramVec +} + +// NewServerMetrics returns a ServerMetrics object. Use a new instance of +// ServerMetrics when not using the default Prometheus metrics registry, for +// example when wanting to control which metrics are added to a registry as +// opposed to automatically adding metrics via init functions. +func NewServerMetrics(counterOpts ...CounterOption) *ServerMetrics { + opts := counterOptions(counterOpts) + return &ServerMetrics{ + serverStartedCounter: prom.NewCounterVec( + opts.apply(prom.CounterOpts{ + Name: "grpc_server_started_total", + Help: "Total number of RPCs started on the server.", + }), []string{"grpc_type", "grpc_service", "grpc_method"}), + serverHandledCounter: prom.NewCounterVec( + opts.apply(prom.CounterOpts{ + Name: "grpc_server_handled_total", + Help: "Total number of RPCs completed on the server, regardless of success or failure.", + }), []string{"grpc_type", "grpc_service", "grpc_method", "grpc_code"}), + serverStreamMsgReceived: prom.NewCounterVec( + opts.apply(prom.CounterOpts{ + Name: "grpc_server_msg_received_total", + Help: "Total number of RPC stream messages received on the server.", + }), []string{"grpc_type", "grpc_service", "grpc_method"}), + serverStreamMsgSent: prom.NewCounterVec( + opts.apply(prom.CounterOpts{ + Name: "grpc_server_msg_sent_total", + Help: "Total number of gRPC stream messages sent by the server.", + }), []string{"grpc_type", "grpc_service", "grpc_method"}), + serverHandledHistogramEnabled: false, + serverHandledHistogramOpts: prom.HistogramOpts{ + Name: "grpc_server_handling_seconds", + Help: "Histogram of response latency (seconds) of gRPC that had been application-level handled by the server.", + Buckets: prom.DefBuckets, + }, + serverHandledHistogram: nil, + } +} + +// EnableHandlingTimeHistogram enables histograms being registered when +// registering the ServerMetrics on a Prometheus registry. Histograms can be +// expensive on Prometheus servers. It takes options to configure histogram +// options such as the defined buckets. +func (m *ServerMetrics) EnableHandlingTimeHistogram(opts ...HistogramOption) { + for _, o := range opts { + o(&m.serverHandledHistogramOpts) + } + if !m.serverHandledHistogramEnabled { + m.serverHandledHistogram = prom.NewHistogramVec( + m.serverHandledHistogramOpts, + []string{"grpc_type", "grpc_service", "grpc_method"}, + ) + } + m.serverHandledHistogramEnabled = true +} + +// Describe sends the super-set of all possible descriptors of metrics +// collected by this Collector to the provided channel and returns once +// the last descriptor has been sent. +func (m *ServerMetrics) Describe(ch chan<- *prom.Desc) { + m.serverStartedCounter.Describe(ch) + m.serverHandledCounter.Describe(ch) + m.serverStreamMsgReceived.Describe(ch) + m.serverStreamMsgSent.Describe(ch) + if m.serverHandledHistogramEnabled { + m.serverHandledHistogram.Describe(ch) + } +} + +// Collect is called by the Prometheus registry when collecting +// metrics. The implementation sends each collected metric via the +// provided channel and returns once the last metric has been sent. +func (m *ServerMetrics) Collect(ch chan<- prom.Metric) { + m.serverStartedCounter.Collect(ch) + m.serverHandledCounter.Collect(ch) + m.serverStreamMsgReceived.Collect(ch) + m.serverStreamMsgSent.Collect(ch) + if m.serverHandledHistogramEnabled { + m.serverHandledHistogram.Collect(ch) + } +} + +// UnaryServerInterceptor is a gRPC server-side interceptor that provides Prometheus monitoring for Unary RPCs. +func (m *ServerMetrics) UnaryServerInterceptor() func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) { + return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) { + monitor := newServerReporter(m, Unary, info.FullMethod) + monitor.ReceivedMessage() + resp, err := handler(ctx, req) + st, _ := grpcstatus.FromError(err) + monitor.Handled(st.Code()) + if err == nil { + monitor.SentMessage() + } + return resp, err + } +} + +// StreamServerInterceptor is a gRPC server-side interceptor that provides Prometheus monitoring for Streaming RPCs. +func (m *ServerMetrics) StreamServerInterceptor() func(srv interface{}, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error { + return func(srv interface{}, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error { + monitor := newServerReporter(m, streamRPCType(info), info.FullMethod) + err := handler(srv, &monitoredServerStream{ss, monitor}) + st, _ := grpcstatus.FromError(err) + monitor.Handled(st.Code()) + return err + } +} + +// InitializeMetrics initializes all metrics, with their appropriate null +// value, for all gRPC methods registered on a gRPC server. This is useful, to +// ensure that all metrics exist when collecting and querying. +func (m *ServerMetrics) InitializeMetrics(server *grpc.Server) { + serviceInfo := server.GetServiceInfo() + for serviceName, info := range serviceInfo { + for _, mInfo := range info.Methods { + preRegisterMethod(m, serviceName, &mInfo) + } + } +} + +func streamRPCType(info *grpc.StreamServerInfo) grpcType { + if info.IsClientStream && !info.IsServerStream { + return ClientStream + } else if !info.IsClientStream && info.IsServerStream { + return ServerStream + } + return BidiStream +} + +// monitoredStream wraps grpc.ServerStream allowing each Sent/Recv of message to increment counters. +type monitoredServerStream struct { + grpc.ServerStream + monitor *serverReporter +} + +func (s *monitoredServerStream) SendMsg(m interface{}) error { + err := s.ServerStream.SendMsg(m) + if err == nil { + s.monitor.SentMessage() + } + return err +} + +func (s *monitoredServerStream) RecvMsg(m interface{}) error { + err := s.ServerStream.RecvMsg(m) + if err == nil { + s.monitor.ReceivedMessage() + } + return err +} + +// preRegisterMethod is invoked on Register of a Server, allowing all gRPC services labels to be pre-populated. +func preRegisterMethod(metrics *ServerMetrics, serviceName string, mInfo *grpc.MethodInfo) { + methodName := mInfo.Name + methodType := string(typeFromMethodInfo(mInfo)) + // These are just references (no increments), as just referencing will create the labels but not set values. + metrics.serverStartedCounter.GetMetricWithLabelValues(methodType, serviceName, methodName) + metrics.serverStreamMsgReceived.GetMetricWithLabelValues(methodType, serviceName, methodName) + metrics.serverStreamMsgSent.GetMetricWithLabelValues(methodType, serviceName, methodName) + if metrics.serverHandledHistogramEnabled { + metrics.serverHandledHistogram.GetMetricWithLabelValues(methodType, serviceName, methodName) + } + for _, code := range allCodes { + metrics.serverHandledCounter.GetMetricWithLabelValues(methodType, serviceName, methodName, code.String()) + } +} diff --git a/vendor/github.com/grpc-ecosystem/go-grpc-prometheus/server_reporter.go b/vendor/github.com/grpc-ecosystem/go-grpc-prometheus/server_reporter.go new file mode 100644 index 00000000000..aa9db5401a4 --- /dev/null +++ b/vendor/github.com/grpc-ecosystem/go-grpc-prometheus/server_reporter.go @@ -0,0 +1,46 @@ +// Copyright 2016 Michal Witkowski. All Rights Reserved. +// See LICENSE for licensing terms. + +package grpc_prometheus + +import ( + "time" + + "google.golang.org/grpc/codes" +) + +type serverReporter struct { + metrics *ServerMetrics + rpcType grpcType + serviceName string + methodName string + startTime time.Time +} + +func newServerReporter(m *ServerMetrics, rpcType grpcType, fullMethod string) *serverReporter { + r := &serverReporter{ + metrics: m, + rpcType: rpcType, + } + if r.metrics.serverHandledHistogramEnabled { + r.startTime = time.Now() + } + r.serviceName, r.methodName = splitMethodName(fullMethod) + r.metrics.serverStartedCounter.WithLabelValues(string(r.rpcType), r.serviceName, r.methodName).Inc() + return r +} + +func (r *serverReporter) ReceivedMessage() { + r.metrics.serverStreamMsgReceived.WithLabelValues(string(r.rpcType), r.serviceName, r.methodName).Inc() +} + +func (r *serverReporter) SentMessage() { + r.metrics.serverStreamMsgSent.WithLabelValues(string(r.rpcType), r.serviceName, r.methodName).Inc() +} + +func (r *serverReporter) Handled(code codes.Code) { + r.metrics.serverHandledCounter.WithLabelValues(string(r.rpcType), r.serviceName, r.methodName, code.String()).Inc() + if r.metrics.serverHandledHistogramEnabled { + r.metrics.serverHandledHistogram.WithLabelValues(string(r.rpcType), r.serviceName, r.methodName).Observe(time.Since(r.startTime).Seconds()) + } +} diff --git a/vendor/github.com/grpc-ecosystem/go-grpc-prometheus/util.go b/vendor/github.com/grpc-ecosystem/go-grpc-prometheus/util.go new file mode 100644 index 00000000000..7987de35f42 --- /dev/null +++ b/vendor/github.com/grpc-ecosystem/go-grpc-prometheus/util.go @@ -0,0 +1,50 @@ +// Copyright 2016 Michal Witkowski. All Rights Reserved. +// See LICENSE for licensing terms. + +package grpc_prometheus + +import ( + "strings" + + "google.golang.org/grpc" + "google.golang.org/grpc/codes" +) + +type grpcType string + +const ( + Unary grpcType = "unary" + ClientStream grpcType = "client_stream" + ServerStream grpcType = "server_stream" + BidiStream grpcType = "bidi_stream" +) + +var ( + allCodes = []codes.Code{ + codes.OK, codes.Canceled, codes.Unknown, codes.InvalidArgument, codes.DeadlineExceeded, codes.NotFound, + codes.AlreadyExists, codes.PermissionDenied, codes.Unauthenticated, codes.ResourceExhausted, + codes.FailedPrecondition, codes.Aborted, codes.OutOfRange, codes.Unimplemented, codes.Internal, + codes.Unavailable, codes.DataLoss, + } +) + +func splitMethodName(fullMethodName string) (string, string) { + fullMethodName = strings.TrimPrefix(fullMethodName, "/") // remove leading slash + if i := strings.Index(fullMethodName, "/"); i >= 0 { + return fullMethodName[:i], fullMethodName[i+1:] + } + return "unknown", "unknown" +} + +func typeFromMethodInfo(mInfo *grpc.MethodInfo) grpcType { + if !mInfo.IsClientStream && !mInfo.IsServerStream { + return Unary + } + if mInfo.IsClientStream && !mInfo.IsServerStream { + return ClientStream + } + if !mInfo.IsClientStream && mInfo.IsServerStream { + return ServerStream + } + return BidiStream +} diff --git a/vendor/github.com/hashicorp/go-hclog/.gitignore b/vendor/github.com/hashicorp/go-hclog/.gitignore new file mode 100644 index 00000000000..42cc4105ff4 --- /dev/null +++ b/vendor/github.com/hashicorp/go-hclog/.gitignore @@ -0,0 +1 @@ +.idea* \ No newline at end of file diff --git a/vendor/github.com/hashicorp/go-hclog/LICENSE b/vendor/github.com/hashicorp/go-hclog/LICENSE new file mode 100644 index 00000000000..abaf1e45f2a --- /dev/null +++ b/vendor/github.com/hashicorp/go-hclog/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2017 HashiCorp + +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/hashicorp/go-hclog/README.md b/vendor/github.com/hashicorp/go-hclog/README.md new file mode 100644 index 00000000000..5d56f4b59c3 --- /dev/null +++ b/vendor/github.com/hashicorp/go-hclog/README.md @@ -0,0 +1,148 @@ +# go-hclog + +[![Go Documentation](http://img.shields.io/badge/go-documentation-blue.svg?style=flat-square)][godocs] + +[godocs]: https://godoc.org/github.com/hashicorp/go-hclog + +`go-hclog` is a package for Go that provides a simple key/value logging +interface for use in development and production environments. + +It provides logging levels that provide decreased output based upon the +desired amount of output, unlike the standard library `log` package. + +It provides `Printf` style logging of values via `hclog.Fmt()`. + +It provides a human readable output mode for use in development as well as +JSON output mode for production. + +## Stability Note + +While this library is fully open source and HashiCorp will be maintaining it +(since we are and will be making extensive use of it), the API and output +format is subject to minor changes as we fully bake and vet it in our projects. +This notice will be removed once it's fully integrated into our major projects +and no further changes are anticipated. + +## Installation and Docs + +Install using `go get github.com/hashicorp/go-hclog`. + +Full documentation is available at +http://godoc.org/github.com/hashicorp/go-hclog + +## Usage + +### Use the global logger + +```go +hclog.Default().Info("hello world") +``` + +```text +2017-07-05T16:15:55.167-0700 [INFO ] hello world +``` + +(Note timestamps are removed in future examples for brevity.) + +### Create a new logger + +```go +appLogger := hclog.New(&hclog.LoggerOptions{ + Name: "my-app", + Level: hclog.LevelFromString("DEBUG"), +}) +``` + +### Emit an Info level message with 2 key/value pairs + +```go +input := "5.5" +_, err := strconv.ParseInt(input, 10, 32) +if err != nil { + appLogger.Info("Invalid input for ParseInt", "input", input, "error", err) +} +``` + +```text +... [INFO ] my-app: Invalid input for ParseInt: input=5.5 error="strconv.ParseInt: parsing "5.5": invalid syntax" +``` + +### Create a new Logger for a major subsystem + +```go +subsystemLogger := appLogger.Named("transport") +subsystemLogger.Info("we are transporting something") +``` + +```text +... [INFO ] my-app.transport: we are transporting something +``` + +Notice that logs emitted by `subsystemLogger` contain `my-app.transport`, +reflecting both the application and subsystem names. + +### Create a new Logger with fixed key/value pairs + +Using `With()` will include a specific key-value pair in all messages emitted +by that logger. + +```go +requestID := "5fb446b6-6eba-821d-df1b-cd7501b6a363" +requestLogger := subsystemLogger.With("request", requestID) +requestLogger.Info("we are transporting a request") +``` + +```text +... [INFO ] my-app.transport: we are transporting a request: request=5fb446b6-6eba-821d-df1b-cd7501b6a363 +``` + +This allows sub Loggers to be context specific without having to thread that +into all the callers. + +### Using `hclog.Fmt()` + +```go +var int totalBandwidth = 200 +appLogger.Info("total bandwidth exceeded", "bandwidth", hclog.Fmt("%d GB/s", totalBandwidth)) +``` + +```text +... [INFO ] my-app: total bandwidth exceeded: bandwidth="200 GB/s" +``` + +### Use this with code that uses the standard library logger + +If you want to use the standard library's `log.Logger` interface you can wrap +`hclog.Logger` by calling the `StandardLogger()` method. This allows you to use +it with the familiar `Println()`, `Printf()`, etc. For example: + +```go +stdLogger := appLogger.StandardLogger(&hclog.StandardLoggerOptions{ + InferLevels: true, +}) +// Printf() is provided by stdlib log.Logger interface, not hclog.Logger +stdLogger.Printf("[DEBUG] %+v", stdLogger) +``` + +```text +... [DEBUG] my-app: &{mu:{state:0 sema:0} prefix: flag:0 out:0xc42000a0a0 buf:[]} +``` + +Alternatively, you may configure the system-wide logger: + +```go +// log the standard logger from 'import "log"' +log.SetOutput(appLogger.StandardWriter(&hclog.StandardLoggerOptions{InferLevels: true})) +log.SetPrefix("") +log.SetFlags(0) + +log.Printf("[DEBUG] %d", 42) +``` + +```text +... [DEBUG] my-app: 42 +``` + +Notice that if `appLogger` is initialized with the `INFO` log level _and_ you +specify `InferLevels: true`, you will not see any output here. You must change +`appLogger` to `DEBUG` to see output. See the docs for more information. diff --git a/vendor/github.com/hashicorp/go-hclog/colorize_unix.go b/vendor/github.com/hashicorp/go-hclog/colorize_unix.go new file mode 100644 index 00000000000..44aa9bf2c62 --- /dev/null +++ b/vendor/github.com/hashicorp/go-hclog/colorize_unix.go @@ -0,0 +1,27 @@ +// +build !windows + +package hclog + +import ( + "github.com/mattn/go-isatty" +) + +// setColorization will mutate the values of this logger +// to approperately configure colorization options. It provides +// a wrapper to the output stream on Windows systems. +func (l *intLogger) setColorization(opts *LoggerOptions) { + switch opts.Color { + case ColorOff: + fallthrough + case ForceColor: + return + case AutoColor: + fi := l.checkWriterIsFile() + isUnixTerm := isatty.IsTerminal(fi.Fd()) + isCygwinTerm := isatty.IsCygwinTerminal(fi.Fd()) + isTerm := isUnixTerm || isCygwinTerm + if !isTerm { + l.writer.color = ColorOff + } + } +} diff --git a/vendor/github.com/hashicorp/go-hclog/colorize_windows.go b/vendor/github.com/hashicorp/go-hclog/colorize_windows.go new file mode 100644 index 00000000000..23486b6d74f --- /dev/null +++ b/vendor/github.com/hashicorp/go-hclog/colorize_windows.go @@ -0,0 +1,33 @@ +// +build windows + +package hclog + +import ( + "os" + + colorable "github.com/mattn/go-colorable" + "github.com/mattn/go-isatty" +) + +// setColorization will mutate the values of this logger +// to approperately configure colorization options. It provides +// a wrapper to the output stream on Windows systems. +func (l *intLogger) setColorization(opts *LoggerOptions) { + switch opts.Color { + case ColorOff: + return + case ForceColor: + fi := l.checkWriterIsFile() + l.writer.w = colorable.NewColorable(fi) + case AutoColor: + fi := l.checkWriterIsFile() + isUnixTerm := isatty.IsTerminal(os.Stdout.Fd()) + isCygwinTerm := isatty.IsCygwinTerminal(os.Stdout.Fd()) + isTerm := isUnixTerm || isCygwinTerm + if !isTerm { + l.writer.color = ColorOff + return + } + l.writer.w = colorable.NewColorable(fi) + } +} diff --git a/vendor/github.com/hashicorp/go-hclog/context.go b/vendor/github.com/hashicorp/go-hclog/context.go new file mode 100644 index 00000000000..7815f501942 --- /dev/null +++ b/vendor/github.com/hashicorp/go-hclog/context.go @@ -0,0 +1,38 @@ +package hclog + +import ( + "context" +) + +// WithContext inserts a logger into the context and is retrievable +// with FromContext. The optional args can be set with the same syntax as +// Logger.With to set fields on the inserted logger. This will not modify +// the logger argument in-place. +func WithContext(ctx context.Context, logger Logger, args ...interface{}) context.Context { + // While we could call logger.With even with zero args, we have this + // check to avoid unnecessary allocations around creating a copy of a + // logger. + if len(args) > 0 { + logger = logger.With(args...) + } + + return context.WithValue(ctx, contextKey, logger) +} + +// FromContext returns a logger from the context. This will return L() +// (the default logger) if no logger is found in the context. Therefore, +// this will never return a nil value. +func FromContext(ctx context.Context) Logger { + logger, _ := ctx.Value(contextKey).(Logger) + if logger == nil { + return L() + } + + return logger +} + +// Unexported new type so that our context key never collides with another. +type contextKeyType struct{} + +// contextKey is the key used for the context to store the logger. +var contextKey = contextKeyType{} diff --git a/vendor/github.com/hashicorp/go-hclog/exclude.go b/vendor/github.com/hashicorp/go-hclog/exclude.go new file mode 100644 index 00000000000..cfd4307a803 --- /dev/null +++ b/vendor/github.com/hashicorp/go-hclog/exclude.go @@ -0,0 +1,71 @@ +package hclog + +import ( + "regexp" + "strings" +) + +// ExcludeByMessage provides a simple way to build a list of log messages that +// can be queried and matched. This is meant to be used with the Exclude +// option on Options to suppress log messages. This does not hold any mutexs +// within itself, so normal usage would be to Add entries at setup and none after +// Exclude is going to be called. Exclude is called with a mutex held within +// the Logger, so that doesn't need to use a mutex. Example usage: +// +// f := new(ExcludeByMessage) +// f.Add("Noisy log message text") +// appLogger.Exclude = f.Exclude +type ExcludeByMessage struct { + messages map[string]struct{} +} + +// Add a message to be filtered. Do not call this after Exclude is to be called +// due to concurrency issues. +func (f *ExcludeByMessage) Add(msg string) { + if f.messages == nil { + f.messages = make(map[string]struct{}) + } + + f.messages[msg] = struct{}{} +} + +// Return true if the given message should be included +func (f *ExcludeByMessage) Exclude(level Level, msg string, args ...interface{}) bool { + _, ok := f.messages[msg] + return ok +} + +// ExcludeByPrefix is a simple type to match a message string that has a common prefix. +type ExcludeByPrefix string + +// Matches an message that starts with the prefix. +func (p ExcludeByPrefix) Exclude(level Level, msg string, args ...interface{}) bool { + return strings.HasPrefix(msg, string(p)) +} + +// ExcludeByRegexp takes a regexp and uses it to match a log message string. If it matches +// the log entry is excluded. +type ExcludeByRegexp struct { + Regexp *regexp.Regexp +} + +// Exclude the log message if the message string matches the regexp +func (e ExcludeByRegexp) Exclude(level Level, msg string, args ...interface{}) bool { + return e.Regexp.MatchString(msg) +} + +// ExcludeFuncs is a slice of functions that will called to see if a log entry +// should be filtered or not. It stops calling functions once at least one returns +// true. +type ExcludeFuncs []func(level Level, msg string, args ...interface{}) bool + +// Calls each function until one of them returns true +func (ff ExcludeFuncs) Exclude(level Level, msg string, args ...interface{}) bool { + for _, f := range ff { + if f(level, msg, args...) { + return true + } + } + + return false +} diff --git a/vendor/github.com/hashicorp/go-hclog/global.go b/vendor/github.com/hashicorp/go-hclog/global.go new file mode 100644 index 00000000000..22ebc57d877 --- /dev/null +++ b/vendor/github.com/hashicorp/go-hclog/global.go @@ -0,0 +1,62 @@ +package hclog + +import ( + "sync" +) + +var ( + protect sync.Once + def Logger + + // DefaultOptions is used to create the Default logger. These are read + // only when the Default logger is created, so set them as soon as the + // process starts. + DefaultOptions = &LoggerOptions{ + Level: DefaultLevel, + Output: DefaultOutput, + } +) + +// Default returns a globally held logger. This can be a good starting +// place, and then you can use .With() and .Name() to create sub-loggers +// to be used in more specific contexts. +// The value of the Default logger can be set via SetDefault() or by +// changing the options in DefaultOptions. +// +// This method is goroutine safe, returning a global from memory, but +// cause should be used if SetDefault() is called it random times +// in the program as that may result in race conditions and an unexpected +// Logger being returned. +func Default() Logger { + protect.Do(func() { + // If SetDefault was used before Default() was called, we need to + // detect that here. + if def == nil { + def = New(DefaultOptions) + } + }) + + return def +} + +// L is a short alias for Default(). +func L() Logger { + return Default() +} + +// SetDefault changes the logger to be returned by Default()and L() +// to the one given. This allows packages to use the default logger +// and have higher level packages change it to match the execution +// environment. It returns any old default if there is one. +// +// NOTE: This is expected to be called early in the program to setup +// a default logger. As such, it does not attempt to make itself +// not racy with regard to the value of the default logger. Ergo +// if it is called in goroutines, you may experience race conditions +// with other goroutines retrieving the default logger. Basically, +// don't do that. +func SetDefault(log Logger) Logger { + old := def + def = log + return old +} diff --git a/vendor/github.com/hashicorp/go-hclog/go.mod b/vendor/github.com/hashicorp/go-hclog/go.mod new file mode 100644 index 00000000000..b6698c0836f --- /dev/null +++ b/vendor/github.com/hashicorp/go-hclog/go.mod @@ -0,0 +1,12 @@ +module github.com/hashicorp/go-hclog + +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/fatih/color v1.7.0 + github.com/mattn/go-colorable v0.1.4 + github.com/mattn/go-isatty v0.0.10 + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/stretchr/testify v1.2.2 +) + +go 1.13 diff --git a/vendor/github.com/hashicorp/go-hclog/go.sum b/vendor/github.com/hashicorp/go-hclog/go.sum new file mode 100644 index 00000000000..3a656dfd9c9 --- /dev/null +++ b/vendor/github.com/hashicorp/go-hclog/go.sum @@ -0,0 +1,18 @@ +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/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA= +github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-isatty v0.0.8 h1:HLtExJ+uU2HOZ+wI0Tt5DtUDrx8yhUqDcp7fYERX4CE= +github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.10 h1:qxFzApOv4WsAL965uUPIsXzAKCZxN2p9UqdhFS4ZW10= +github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= +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-20190222072716-a9d3bda3a223 h1:DH4skfRX4EBpamg7iV4ZlCpblAHI6s6TDM39bFZumv8= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20191008105621-543471e840be h1:QAcqgptGM8IQBC9K/RC4o+O9YmqEm0diQn9QmZw/0mU= +golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= diff --git a/vendor/github.com/hashicorp/go-hclog/interceptlogger.go b/vendor/github.com/hashicorp/go-hclog/interceptlogger.go new file mode 100644 index 00000000000..d8e2e76fc38 --- /dev/null +++ b/vendor/github.com/hashicorp/go-hclog/interceptlogger.go @@ -0,0 +1,235 @@ +package hclog + +import ( + "io" + "log" + "sync" + "sync/atomic" +) + +var _ Logger = &interceptLogger{} + +type interceptLogger struct { + Logger + + mu *sync.Mutex + sinkCount *int32 + Sinks map[SinkAdapter]struct{} +} + +func NewInterceptLogger(opts *LoggerOptions) InterceptLogger { + intercept := &interceptLogger{ + Logger: New(opts), + mu: new(sync.Mutex), + sinkCount: new(int32), + Sinks: make(map[SinkAdapter]struct{}), + } + + atomic.StoreInt32(intercept.sinkCount, 0) + + return intercept +} + +func (i *interceptLogger) Log(level Level, msg string, args ...interface{}) { + i.Logger.Log(level, msg, args...) + if atomic.LoadInt32(i.sinkCount) == 0 { + return + } + + i.mu.Lock() + defer i.mu.Unlock() + for s := range i.Sinks { + s.Accept(i.Name(), level, msg, i.retrieveImplied(args...)...) + } +} + +// Emit the message and args at TRACE level to log and sinks +func (i *interceptLogger) Trace(msg string, args ...interface{}) { + i.Logger.Trace(msg, args...) + if atomic.LoadInt32(i.sinkCount) == 0 { + return + } + + i.mu.Lock() + defer i.mu.Unlock() + for s := range i.Sinks { + s.Accept(i.Name(), Trace, msg, i.retrieveImplied(args...)...) + } +} + +// Emit the message and args at DEBUG level to log and sinks +func (i *interceptLogger) Debug(msg string, args ...interface{}) { + i.Logger.Debug(msg, args...) + if atomic.LoadInt32(i.sinkCount) == 0 { + return + } + + i.mu.Lock() + defer i.mu.Unlock() + for s := range i.Sinks { + s.Accept(i.Name(), Debug, msg, i.retrieveImplied(args...)...) + } +} + +// Emit the message and args at INFO level to log and sinks +func (i *interceptLogger) Info(msg string, args ...interface{}) { + i.Logger.Info(msg, args...) + if atomic.LoadInt32(i.sinkCount) == 0 { + return + } + + i.mu.Lock() + defer i.mu.Unlock() + for s := range i.Sinks { + s.Accept(i.Name(), Info, msg, i.retrieveImplied(args...)...) + } +} + +// Emit the message and args at WARN level to log and sinks +func (i *interceptLogger) Warn(msg string, args ...interface{}) { + i.Logger.Warn(msg, args...) + if atomic.LoadInt32(i.sinkCount) == 0 { + return + } + + i.mu.Lock() + defer i.mu.Unlock() + for s := range i.Sinks { + s.Accept(i.Name(), Warn, msg, i.retrieveImplied(args...)...) + } +} + +// Emit the message and args at ERROR level to log and sinks +func (i *interceptLogger) Error(msg string, args ...interface{}) { + i.Logger.Error(msg, args...) + if atomic.LoadInt32(i.sinkCount) == 0 { + return + } + + i.mu.Lock() + defer i.mu.Unlock() + for s := range i.Sinks { + s.Accept(i.Name(), Error, msg, i.retrieveImplied(args...)...) + } +} + +func (i *interceptLogger) retrieveImplied(args ...interface{}) []interface{} { + top := i.Logger.ImpliedArgs() + + cp := make([]interface{}, len(top)+len(args)) + copy(cp, top) + copy(cp[len(top):], args) + + return cp +} + +// Create a new sub-Logger that a name decending from the current name. +// This is used to create a subsystem specific Logger. +// Registered sinks will subscribe to these messages as well. +func (i *interceptLogger) Named(name string) Logger { + return i.NamedIntercept(name) +} + +// Create a new sub-Logger with an explicit name. This ignores the current +// name. This is used to create a standalone logger that doesn't fall +// within the normal hierarchy. Registered sinks will subscribe +// to these messages as well. +func (i *interceptLogger) ResetNamed(name string) Logger { + return i.ResetNamedIntercept(name) +} + +// Create a new sub-Logger that a name decending from the current name. +// This is used to create a subsystem specific Logger. +// Registered sinks will subscribe to these messages as well. +func (i *interceptLogger) NamedIntercept(name string) InterceptLogger { + var sub interceptLogger + + sub = *i + sub.Logger = i.Logger.Named(name) + return &sub +} + +// Create a new sub-Logger with an explicit name. This ignores the current +// name. This is used to create a standalone logger that doesn't fall +// within the normal hierarchy. Registered sinks will subscribe +// to these messages as well. +func (i *interceptLogger) ResetNamedIntercept(name string) InterceptLogger { + var sub interceptLogger + + sub = *i + sub.Logger = i.Logger.ResetNamed(name) + return &sub +} + +// Return a sub-Logger for which every emitted log message will contain +// the given key/value pairs. This is used to create a context specific +// Logger. +func (i *interceptLogger) With(args ...interface{}) Logger { + var sub interceptLogger + + sub = *i + + sub.Logger = i.Logger.With(args...) + + return &sub +} + +// RegisterSink attaches a SinkAdapter to interceptLoggers sinks. +func (i *interceptLogger) RegisterSink(sink SinkAdapter) { + i.mu.Lock() + defer i.mu.Unlock() + + i.Sinks[sink] = struct{}{} + + atomic.AddInt32(i.sinkCount, 1) +} + +// DeregisterSink removes a SinkAdapter from interceptLoggers sinks. +func (i *interceptLogger) DeregisterSink(sink SinkAdapter) { + i.mu.Lock() + defer i.mu.Unlock() + + delete(i.Sinks, sink) + + atomic.AddInt32(i.sinkCount, -1) +} + +func (i *interceptLogger) StandardLoggerIntercept(opts *StandardLoggerOptions) *log.Logger { + return i.StandardLogger(opts) +} + +func (i *interceptLogger) StandardLogger(opts *StandardLoggerOptions) *log.Logger { + if opts == nil { + opts = &StandardLoggerOptions{} + } + + return log.New(i.StandardWriter(opts), "", 0) +} + +func (i *interceptLogger) StandardWriterIntercept(opts *StandardLoggerOptions) io.Writer { + return i.StandardWriter(opts) +} + +func (i *interceptLogger) StandardWriter(opts *StandardLoggerOptions) io.Writer { + return &stdlogAdapter{ + log: i, + inferLevels: opts.InferLevels, + forceLevel: opts.ForceLevel, + } +} + +func (i *interceptLogger) ResetOutput(opts *LoggerOptions) error { + if or, ok := i.Logger.(OutputResettable); ok { + return or.ResetOutput(opts) + } else { + return nil + } +} + +func (i *interceptLogger) ResetOutputWithFlush(opts *LoggerOptions, flushable Flushable) error { + if or, ok := i.Logger.(OutputResettable); ok { + return or.ResetOutputWithFlush(opts, flushable) + } else { + return nil + } +} diff --git a/vendor/github.com/hashicorp/go-hclog/intlogger.go b/vendor/github.com/hashicorp/go-hclog/intlogger.go new file mode 100644 index 00000000000..f961ed91913 --- /dev/null +++ b/vendor/github.com/hashicorp/go-hclog/intlogger.go @@ -0,0 +1,679 @@ +package hclog + +import ( + "bytes" + "encoding" + "encoding/json" + "errors" + "fmt" + "io" + "log" + "os" + "reflect" + "runtime" + "sort" + "strconv" + "strings" + "sync" + "sync/atomic" + "time" + + "github.com/fatih/color" +) + +// TimeFormat to use for logging. This is a version of RFC3339 that contains +// contains millisecond precision +const TimeFormat = "2006-01-02T15:04:05.000Z0700" + +// errJsonUnsupportedTypeMsg is included in log json entries, if an arg cannot be serialized to json +const errJsonUnsupportedTypeMsg = "logging contained values that don't serialize to json" + +var ( + _levelToBracket = map[Level]string{ + Debug: "[DEBUG]", + Trace: "[TRACE]", + Info: "[INFO] ", + Warn: "[WARN] ", + Error: "[ERROR]", + } + + _levelToColor = map[Level]*color.Color{ + Debug: color.New(color.FgHiWhite), + Trace: color.New(color.FgHiGreen), + Info: color.New(color.FgHiBlue), + Warn: color.New(color.FgHiYellow), + Error: color.New(color.FgHiRed), + } +) + +// Make sure that intLogger is a Logger +var _ Logger = &intLogger{} + +// intLogger is an internal logger implementation. Internal in that it is +// defined entirely by this package. +type intLogger struct { + json bool + caller bool + name string + timeFormat string + + // This is an interface so that it's shared by any derived loggers, since + // those derived loggers share the bufio.Writer as well. + mutex Locker + writer *writer + level *int32 + + implied []interface{} + + exclude func(level Level, msg string, args ...interface{}) bool + + // create subloggers with their own level setting + independentLevels bool +} + +// New returns a configured logger. +func New(opts *LoggerOptions) Logger { + return newLogger(opts) +} + +// NewSinkAdapter returns a SinkAdapter with configured settings +// defined by LoggerOptions +func NewSinkAdapter(opts *LoggerOptions) SinkAdapter { + return newLogger(opts) +} + +func newLogger(opts *LoggerOptions) *intLogger { + if opts == nil { + opts = &LoggerOptions{} + } + + output := opts.Output + if output == nil { + output = DefaultOutput + } + + level := opts.Level + if level == NoLevel { + level = DefaultLevel + } + + mutex := opts.Mutex + if mutex == nil { + mutex = new(sync.Mutex) + } + + l := &intLogger{ + json: opts.JSONFormat, + caller: opts.IncludeLocation, + name: opts.Name, + timeFormat: TimeFormat, + mutex: mutex, + writer: newWriter(output, opts.Color), + level: new(int32), + exclude: opts.Exclude, + independentLevels: opts.IndependentLevels, + } + + l.setColorization(opts) + + if opts.DisableTime { + l.timeFormat = "" + } else if opts.TimeFormat != "" { + l.timeFormat = opts.TimeFormat + } + + atomic.StoreInt32(l.level, int32(level)) + + return l +} + +// Log a message and a set of key/value pairs if the given level is at +// or more severe that the threshold configured in the Logger. +func (l *intLogger) log(name string, level Level, msg string, args ...interface{}) { + if level < Level(atomic.LoadInt32(l.level)) { + return + } + + t := time.Now() + + l.mutex.Lock() + defer l.mutex.Unlock() + + if l.exclude != nil && l.exclude(level, msg, args...) { + return + } + + if l.json { + l.logJSON(t, name, level, msg, args...) + } else { + l.logPlain(t, name, level, msg, args...) + } + + l.writer.Flush(level) +} + +// Cleanup a path by returning the last 2 segments of the path only. +func trimCallerPath(path string) string { + // lovely borrowed from zap + // nb. To make sure we trim the path correctly on Windows too, we + // counter-intuitively need to use '/' and *not* os.PathSeparator here, + // because the path given originates from Go stdlib, specifically + // runtime.Caller() which (as of Mar/17) returns forward slashes even on + // Windows. + // + // See https://github.com/golang/go/issues/3335 + // and https://github.com/golang/go/issues/18151 + // + // for discussion on the issue on Go side. + + // Find the last separator. + idx := strings.LastIndexByte(path, '/') + if idx == -1 { + return path + } + + // Find the penultimate separator. + idx = strings.LastIndexByte(path[:idx], '/') + if idx == -1 { + return path + } + + return path[idx+1:] +} + +// Non-JSON logging format function +func (l *intLogger) logPlain(t time.Time, name string, level Level, msg string, args ...interface{}) { + if len(l.timeFormat) > 0 { + l.writer.WriteString(t.Format(l.timeFormat)) + l.writer.WriteByte(' ') + } + + s, ok := _levelToBracket[level] + if ok { + l.writer.WriteString(s) + } else { + l.writer.WriteString("[?????]") + } + + offset := 3 + if l.caller { + // Check if the caller is inside our package and inside + // a logger implementation file + if _, file, _, ok := runtime.Caller(3); ok { + if strings.HasSuffix(file, "intlogger.go") || strings.HasSuffix(file, "interceptlogger.go") { + offset = 4 + } + } + + if _, file, line, ok := runtime.Caller(offset); ok { + l.writer.WriteByte(' ') + l.writer.WriteString(trimCallerPath(file)) + l.writer.WriteByte(':') + l.writer.WriteString(strconv.Itoa(line)) + l.writer.WriteByte(':') + } + } + + l.writer.WriteByte(' ') + + if name != "" { + l.writer.WriteString(name) + l.writer.WriteString(": ") + } + + l.writer.WriteString(msg) + + args = append(l.implied, args...) + + var stacktrace CapturedStacktrace + + if args != nil && len(args) > 0 { + if len(args)%2 != 0 { + cs, ok := args[len(args)-1].(CapturedStacktrace) + if ok { + args = args[:len(args)-1] + stacktrace = cs + } else { + extra := args[len(args)-1] + args = append(args[:len(args)-1], MissingKey, extra) + } + } + + l.writer.WriteByte(':') + + FOR: + for i := 0; i < len(args); i = i + 2 { + var ( + val string + raw bool + ) + + switch st := args[i+1].(type) { + case string: + val = st + case int: + val = strconv.FormatInt(int64(st), 10) + case int64: + val = strconv.FormatInt(int64(st), 10) + case int32: + val = strconv.FormatInt(int64(st), 10) + case int16: + val = strconv.FormatInt(int64(st), 10) + case int8: + val = strconv.FormatInt(int64(st), 10) + case uint: + val = strconv.FormatUint(uint64(st), 10) + case uint64: + val = strconv.FormatUint(uint64(st), 10) + case uint32: + val = strconv.FormatUint(uint64(st), 10) + case uint16: + val = strconv.FormatUint(uint64(st), 10) + case uint8: + val = strconv.FormatUint(uint64(st), 10) + case Hex: + val = "0x" + strconv.FormatUint(uint64(st), 16) + case Octal: + val = "0" + strconv.FormatUint(uint64(st), 8) + case Binary: + val = "0b" + strconv.FormatUint(uint64(st), 2) + case CapturedStacktrace: + stacktrace = st + continue FOR + case Format: + val = fmt.Sprintf(st[0].(string), st[1:]...) + default: + v := reflect.ValueOf(st) + if v.Kind() == reflect.Slice { + val = l.renderSlice(v) + raw = true + } else { + val = fmt.Sprintf("%v", st) + } + } + + l.writer.WriteByte(' ') + switch st := args[i].(type) { + case string: + l.writer.WriteString(st) + default: + l.writer.WriteString(fmt.Sprintf("%s", st)) + } + l.writer.WriteByte('=') + + if !raw && strings.ContainsAny(val, " \t\n\r") { + l.writer.WriteByte('"') + l.writer.WriteString(val) + l.writer.WriteByte('"') + } else { + l.writer.WriteString(val) + } + } + } + + l.writer.WriteString("\n") + + if stacktrace != "" { + l.writer.WriteString(string(stacktrace)) + l.writer.WriteString("\n") + } +} + +func (l *intLogger) renderSlice(v reflect.Value) string { + var buf bytes.Buffer + + buf.WriteRune('[') + + for i := 0; i < v.Len(); i++ { + if i > 0 { + buf.WriteString(", ") + } + + sv := v.Index(i) + + var val string + + switch sv.Kind() { + case reflect.String: + val = sv.String() + case reflect.Int, reflect.Int16, reflect.Int32, reflect.Int64: + val = strconv.FormatInt(sv.Int(), 10) + case reflect.Uint, reflect.Uint16, reflect.Uint32, reflect.Uint64: + val = strconv.FormatUint(sv.Uint(), 10) + default: + val = fmt.Sprintf("%v", sv.Interface()) + } + + if strings.ContainsAny(val, " \t\n\r") { + buf.WriteByte('"') + buf.WriteString(val) + buf.WriteByte('"') + } else { + buf.WriteString(val) + } + } + + buf.WriteRune(']') + + return buf.String() +} + +// JSON logging function +func (l *intLogger) logJSON(t time.Time, name string, level Level, msg string, args ...interface{}) { + vals := l.jsonMapEntry(t, name, level, msg) + args = append(l.implied, args...) + + if args != nil && len(args) > 0 { + if len(args)%2 != 0 { + cs, ok := args[len(args)-1].(CapturedStacktrace) + if ok { + args = args[:len(args)-1] + vals["stacktrace"] = cs + } else { + extra := args[len(args)-1] + args = append(args[:len(args)-1], MissingKey, extra) + } + } + + for i := 0; i < len(args); i = i + 2 { + val := args[i+1] + switch sv := val.(type) { + case error: + // Check if val is of type error. If error type doesn't + // implement json.Marshaler or encoding.TextMarshaler + // then set val to err.Error() so that it gets marshaled + switch sv.(type) { + case json.Marshaler, encoding.TextMarshaler: + default: + val = sv.Error() + } + case Format: + val = fmt.Sprintf(sv[0].(string), sv[1:]...) + } + + var key string + + switch st := args[i].(type) { + case string: + key = st + default: + key = fmt.Sprintf("%s", st) + } + vals[key] = val + } + } + + err := json.NewEncoder(l.writer).Encode(vals) + if err != nil { + if _, ok := err.(*json.UnsupportedTypeError); ok { + plainVal := l.jsonMapEntry(t, name, level, msg) + plainVal["@warn"] = errJsonUnsupportedTypeMsg + + json.NewEncoder(l.writer).Encode(plainVal) + } + } +} + +func (l intLogger) jsonMapEntry(t time.Time, name string, level Level, msg string) map[string]interface{} { + vals := map[string]interface{}{ + "@message": msg, + "@timestamp": t.Format("2006-01-02T15:04:05.000000Z07:00"), + } + + var levelStr string + switch level { + case Error: + levelStr = "error" + case Warn: + levelStr = "warn" + case Info: + levelStr = "info" + case Debug: + levelStr = "debug" + case Trace: + levelStr = "trace" + default: + levelStr = "all" + } + + vals["@level"] = levelStr + + if name != "" { + vals["@module"] = name + } + + if l.caller { + if _, file, line, ok := runtime.Caller(4); ok { + vals["@caller"] = fmt.Sprintf("%s:%d", file, line) + } + } + return vals +} + +// Emit the message and args at the provided level +func (l *intLogger) Log(level Level, msg string, args ...interface{}) { + l.log(l.Name(), level, msg, args...) +} + +// Emit the message and args at DEBUG level +func (l *intLogger) Debug(msg string, args ...interface{}) { + l.log(l.Name(), Debug, msg, args...) +} + +// Emit the message and args at TRACE level +func (l *intLogger) Trace(msg string, args ...interface{}) { + l.log(l.Name(), Trace, msg, args...) +} + +// Emit the message and args at INFO level +func (l *intLogger) Info(msg string, args ...interface{}) { + l.log(l.Name(), Info, msg, args...) +} + +// Emit the message and args at WARN level +func (l *intLogger) Warn(msg string, args ...interface{}) { + l.log(l.Name(), Warn, msg, args...) +} + +// Emit the message and args at ERROR level +func (l *intLogger) Error(msg string, args ...interface{}) { + l.log(l.Name(), Error, msg, args...) +} + +// Indicate that the logger would emit TRACE level logs +func (l *intLogger) IsTrace() bool { + return Level(atomic.LoadInt32(l.level)) == Trace +} + +// Indicate that the logger would emit DEBUG level logs +func (l *intLogger) IsDebug() bool { + return Level(atomic.LoadInt32(l.level)) <= Debug +} + +// Indicate that the logger would emit INFO level logs +func (l *intLogger) IsInfo() bool { + return Level(atomic.LoadInt32(l.level)) <= Info +} + +// Indicate that the logger would emit WARN level logs +func (l *intLogger) IsWarn() bool { + return Level(atomic.LoadInt32(l.level)) <= Warn +} + +// Indicate that the logger would emit ERROR level logs +func (l *intLogger) IsError() bool { + return Level(atomic.LoadInt32(l.level)) <= Error +} + +const MissingKey = "EXTRA_VALUE_AT_END" + +// Return a sub-Logger for which every emitted log message will contain +// the given key/value pairs. This is used to create a context specific +// Logger. +func (l *intLogger) With(args ...interface{}) Logger { + var extra interface{} + + if len(args)%2 != 0 { + extra = args[len(args)-1] + args = args[:len(args)-1] + } + + sl := l.copy() + + result := make(map[string]interface{}, len(l.implied)+len(args)) + keys := make([]string, 0, len(l.implied)+len(args)) + + // Read existing args, store map and key for consistent sorting + for i := 0; i < len(l.implied); i += 2 { + key := l.implied[i].(string) + keys = append(keys, key) + result[key] = l.implied[i+1] + } + // Read new args, store map and key for consistent sorting + for i := 0; i < len(args); i += 2 { + key := args[i].(string) + _, exists := result[key] + if !exists { + keys = append(keys, key) + } + result[key] = args[i+1] + } + + // Sort keys to be consistent + sort.Strings(keys) + + sl.implied = make([]interface{}, 0, len(l.implied)+len(args)) + for _, k := range keys { + sl.implied = append(sl.implied, k) + sl.implied = append(sl.implied, result[k]) + } + + if extra != nil { + sl.implied = append(sl.implied, MissingKey, extra) + } + + return sl +} + +// Create a new sub-Logger that a name decending from the current name. +// This is used to create a subsystem specific Logger. +func (l *intLogger) Named(name string) Logger { + sl := l.copy() + + if sl.name != "" { + sl.name = sl.name + "." + name + } else { + sl.name = name + } + + return sl +} + +// Create a new sub-Logger with an explicit name. This ignores the current +// name. This is used to create a standalone logger that doesn't fall +// within the normal hierarchy. +func (l *intLogger) ResetNamed(name string) Logger { + sl := l.copy() + + sl.name = name + + return sl +} + +func (l *intLogger) ResetOutput(opts *LoggerOptions) error { + if opts.Output == nil { + return errors.New("given output is nil") + } + + l.mutex.Lock() + defer l.mutex.Unlock() + + return l.resetOutput(opts) +} + +func (l *intLogger) ResetOutputWithFlush(opts *LoggerOptions, flushable Flushable) error { + if opts.Output == nil { + return errors.New("given output is nil") + } + if flushable == nil { + return errors.New("flushable is nil") + } + + l.mutex.Lock() + defer l.mutex.Unlock() + + if err := flushable.Flush(); err != nil { + return err + } + + return l.resetOutput(opts) +} + +func (l *intLogger) resetOutput(opts *LoggerOptions) error { + l.writer = newWriter(opts.Output, opts.Color) + l.setColorization(opts) + return nil +} + +// Update the logging level on-the-fly. This will affect all subloggers as +// well. +func (l *intLogger) SetLevel(level Level) { + atomic.StoreInt32(l.level, int32(level)) +} + +// Create a *log.Logger that will send it's data through this Logger. This +// allows packages that expect to be using the standard library log to actually +// use this logger. +func (l *intLogger) StandardLogger(opts *StandardLoggerOptions) *log.Logger { + if opts == nil { + opts = &StandardLoggerOptions{} + } + + return log.New(l.StandardWriter(opts), "", 0) +} + +func (l *intLogger) StandardWriter(opts *StandardLoggerOptions) io.Writer { + return &stdlogAdapter{ + log: l, + inferLevels: opts.InferLevels, + forceLevel: opts.ForceLevel, + } +} + +// checks if the underlying io.Writer is a file, and +// panics if not. For use by colorization. +func (l *intLogger) checkWriterIsFile() *os.File { + fi, ok := l.writer.w.(*os.File) + if !ok { + panic("Cannot enable coloring of non-file Writers") + } + return fi +} + +// Accept implements the SinkAdapter interface +func (i *intLogger) Accept(name string, level Level, msg string, args ...interface{}) { + i.log(name, level, msg, args...) +} + +// ImpliedArgs returns the loggers implied args +func (i *intLogger) ImpliedArgs() []interface{} { + return i.implied +} + +// Name returns the loggers name +func (i *intLogger) Name() string { + return i.name +} + +// copy returns a shallow copy of the intLogger, replacing the level pointer +// when necessary +func (l *intLogger) copy() *intLogger { + sl := *l + + if l.independentLevels { + sl.level = new(int32) + *sl.level = *l.level + } + + return &sl +} diff --git a/vendor/github.com/hashicorp/go-hclog/logger.go b/vendor/github.com/hashicorp/go-hclog/logger.go new file mode 100644 index 00000000000..83eafc152c6 --- /dev/null +++ b/vendor/github.com/hashicorp/go-hclog/logger.go @@ -0,0 +1,341 @@ +package hclog + +import ( + "io" + "log" + "os" + "strings" +) + +var ( + //DefaultOutput is used as the default log output. + DefaultOutput io.Writer = os.Stderr + + // DefaultLevel is used as the default log level. + DefaultLevel = Info +) + +// Level represents a log level. +type Level int32 + +const ( + // NoLevel is a special level used to indicate that no level has been + // set and allow for a default to be used. + NoLevel Level = 0 + + // Trace is the most verbose level. Intended to be used for the tracing + // of actions in code, such as function enters/exits, etc. + Trace Level = 1 + + // Debug information for programmer lowlevel analysis. + Debug Level = 2 + + // Info information about steady state operations. + Info Level = 3 + + // Warn information about rare but handled events. + Warn Level = 4 + + // Error information about unrecoverable events. + Error Level = 5 + + // Off disables all logging output. + Off Level = 6 +) + +// Format is a simple convience type for when formatting is required. When +// processing a value of this type, the logger automatically treats the first +// argument as a Printf formatting string and passes the rest as the values +// to be formatted. For example: L.Info(Fmt{"%d beans/day", beans}). +type Format []interface{} + +// Fmt returns a Format type. This is a convience function for creating a Format +// type. +func Fmt(str string, args ...interface{}) Format { + return append(Format{str}, args...) +} + +// A simple shortcut to format numbers in hex when displayed with the normal +// text output. For example: L.Info("header value", Hex(17)) +type Hex int + +// A simple shortcut to format numbers in octal when displayed with the normal +// text output. For example: L.Info("perms", Octal(17)) +type Octal int + +// A simple shortcut to format numbers in binary when displayed with the normal +// text output. For example: L.Info("bits", Binary(17)) +type Binary int + +// ColorOption expresses how the output should be colored, if at all. +type ColorOption uint8 + +const ( + // ColorOff is the default coloration, and does not + // inject color codes into the io.Writer. + ColorOff ColorOption = iota + // AutoColor checks if the io.Writer is a tty, + // and if so enables coloring. + AutoColor + // ForceColor will enable coloring, regardless of whether + // the io.Writer is a tty or not. + ForceColor +) + +// LevelFromString returns a Level type for the named log level, or "NoLevel" if +// the level string is invalid. This facilitates setting the log level via +// config or environment variable by name in a predictable way. +func LevelFromString(levelStr string) Level { + // We don't care about case. Accept both "INFO" and "info". + levelStr = strings.ToLower(strings.TrimSpace(levelStr)) + switch levelStr { + case "trace": + return Trace + case "debug": + return Debug + case "info": + return Info + case "warn": + return Warn + case "error": + return Error + case "off": + return Off + default: + return NoLevel + } +} + +func (l Level) String() string { + switch l { + case Trace: + return "trace" + case Debug: + return "debug" + case Info: + return "info" + case Warn: + return "warn" + case Error: + return "error" + case NoLevel: + return "none" + case Off: + return "off" + default: + return "unknown" + } +} + +// Logger describes the interface that must be implemeted by all loggers. +type Logger interface { + // Args are alternating key, val pairs + // keys must be strings + // vals can be any type, but display is implementation specific + // Emit a message and key/value pairs at a provided log level + Log(level Level, msg string, args ...interface{}) + + // Emit a message and key/value pairs at the TRACE level + Trace(msg string, args ...interface{}) + + // Emit a message and key/value pairs at the DEBUG level + Debug(msg string, args ...interface{}) + + // Emit a message and key/value pairs at the INFO level + Info(msg string, args ...interface{}) + + // Emit a message and key/value pairs at the WARN level + Warn(msg string, args ...interface{}) + + // Emit a message and key/value pairs at the ERROR level + Error(msg string, args ...interface{}) + + // Indicate if TRACE logs would be emitted. This and the other Is* guards + // are used to elide expensive logging code based on the current level. + IsTrace() bool + + // Indicate if DEBUG logs would be emitted. This and the other Is* guards + IsDebug() bool + + // Indicate if INFO logs would be emitted. This and the other Is* guards + IsInfo() bool + + // Indicate if WARN logs would be emitted. This and the other Is* guards + IsWarn() bool + + // Indicate if ERROR logs would be emitted. This and the other Is* guards + IsError() bool + + // ImpliedArgs returns With key/value pairs + ImpliedArgs() []interface{} + + // Creates a sublogger that will always have the given key/value pairs + With(args ...interface{}) Logger + + // Returns the Name of the logger + Name() string + + // Create a logger that will prepend the name string on the front of all messages. + // If the logger already has a name, the new value will be appended to the current + // name. That way, a major subsystem can use this to decorate all it's own logs + // without losing context. + Named(name string) Logger + + // Create a logger that will prepend the name string on the front of all messages. + // This sets the name of the logger to the value directly, unlike Named which honor + // the current name as well. + ResetNamed(name string) Logger + + // Updates the level. This should affect all related loggers as well, + // unless they were created with IndependentLevels. If an + // implementation cannot update the level on the fly, it should no-op. + SetLevel(level Level) + + // Return a value that conforms to the stdlib log.Logger interface + StandardLogger(opts *StandardLoggerOptions) *log.Logger + + // Return a value that conforms to io.Writer, which can be passed into log.SetOutput() + StandardWriter(opts *StandardLoggerOptions) io.Writer +} + +// StandardLoggerOptions can be used to configure a new standard logger. +type StandardLoggerOptions struct { + // Indicate that some minimal parsing should be done on strings to try + // and detect their level and re-emit them. + // This supports the strings like [ERROR], [ERR] [TRACE], [WARN], [INFO], + // [DEBUG] and strip it off before reapplying it. + InferLevels bool + + // ForceLevel is used to force all output from the standard logger to be at + // the specified level. Similar to InferLevels, this will strip any level + // prefix contained in the logged string before applying the forced level. + // If set, this override InferLevels. + ForceLevel Level +} + +// LoggerOptions can be used to configure a new logger. +type LoggerOptions struct { + // Name of the subsystem to prefix logs with + Name string + + // The threshold for the logger. Anything less severe is supressed + Level Level + + // Where to write the logs to. Defaults to os.Stderr if nil + Output io.Writer + + // An optional Locker in case Output is shared. This can be a sync.Mutex or + // a NoopLocker if the caller wants control over output, e.g. for batching + // log lines. + Mutex Locker + + // Control if the output should be in JSON. + JSONFormat bool + + // Include file and line information in each log line + IncludeLocation bool + + // The time format to use instead of the default + TimeFormat string + + // Control whether or not to display the time at all. This is required + // because setting TimeFormat to empty assumes the default format. + DisableTime bool + + // Color the output. On Windows, colored logs are only avaiable for io.Writers that + // are concretely instances of *os.File. + Color ColorOption + + // A function which is called with the log information and if it returns true the value + // should not be logged. + // This is useful when interacting with a system that you wish to suppress the log + // message for (because it's too noisy, etc) + Exclude func(level Level, msg string, args ...interface{}) bool + + // IndependentLevels causes subloggers to be created with an independent + // copy of this logger's level. This means that using SetLevel on this + // logger will not effect any subloggers, and SetLevel on any subloggers + // will not effect the parent or sibling loggers. + IndependentLevels bool +} + +// InterceptLogger describes the interface for using a logger +// that can register different output sinks. +// This is useful for sending lower level log messages +// to a different output while keeping the root logger +// at a higher one. +type InterceptLogger interface { + // Logger is the root logger for an InterceptLogger + Logger + + // RegisterSink adds a SinkAdapter to the InterceptLogger + RegisterSink(sink SinkAdapter) + + // DeregisterSink removes a SinkAdapter from the InterceptLogger + DeregisterSink(sink SinkAdapter) + + // Create a interceptlogger that will prepend the name string on the front of all messages. + // If the logger already has a name, the new value will be appended to the current + // name. That way, a major subsystem can use this to decorate all it's own logs + // without losing context. + NamedIntercept(name string) InterceptLogger + + // Create a interceptlogger that will prepend the name string on the front of all messages. + // This sets the name of the logger to the value directly, unlike Named which honor + // the current name as well. + ResetNamedIntercept(name string) InterceptLogger + + // Deprecated: use StandardLogger + StandardLoggerIntercept(opts *StandardLoggerOptions) *log.Logger + + // Deprecated: use StandardWriter + StandardWriterIntercept(opts *StandardLoggerOptions) io.Writer +} + +// SinkAdapter describes the interface that must be implemented +// in order to Register a new sink to an InterceptLogger +type SinkAdapter interface { + Accept(name string, level Level, msg string, args ...interface{}) +} + +// Flushable represents a method for flushing an output buffer. It can be used +// if Resetting the log to use a new output, in order to flush the writes to +// the existing output beforehand. +type Flushable interface { + Flush() error +} + +// OutputResettable provides ways to swap the output in use at runtime +type OutputResettable interface { + // ResetOutput swaps the current output writer with the one given in the + // opts. Color options given in opts will be used for the new output. + ResetOutput(opts *LoggerOptions) error + + // ResetOutputWithFlush swaps the current output writer with the one given + // in the opts, first calling Flush on the given Flushable. Color options + // given in opts will be used for the new output. + ResetOutputWithFlush(opts *LoggerOptions, flushable Flushable) error +} + +// Locker is used for locking output. If not set when creating a logger, a +// sync.Mutex will be used internally. +type Locker interface { + // Lock is called when the output is going to be changed or written to + Lock() + + // Unlock is called when the operation that called Lock() completes + Unlock() +} + +// NoopLocker implements locker but does nothing. This is useful if the client +// wants tight control over locking, in order to provide grouping of log +// entries or other functionality. +type NoopLocker struct{} + +// Lock does nothing +func (n NoopLocker) Lock() {} + +// Unlock does nothing +func (n NoopLocker) Unlock() {} + +var _ Locker = (*NoopLocker)(nil) diff --git a/vendor/github.com/hashicorp/go-hclog/nulllogger.go b/vendor/github.com/hashicorp/go-hclog/nulllogger.go new file mode 100644 index 00000000000..bc14f770807 --- /dev/null +++ b/vendor/github.com/hashicorp/go-hclog/nulllogger.go @@ -0,0 +1,58 @@ +package hclog + +import ( + "io" + "io/ioutil" + "log" +) + +// NewNullLogger instantiates a Logger for which all calls +// will succeed without doing anything. +// Useful for testing purposes. +func NewNullLogger() Logger { + return &nullLogger{} +} + +type nullLogger struct{} + +func (l *nullLogger) Log(level Level, msg string, args ...interface{}) {} + +func (l *nullLogger) Trace(msg string, args ...interface{}) {} + +func (l *nullLogger) Debug(msg string, args ...interface{}) {} + +func (l *nullLogger) Info(msg string, args ...interface{}) {} + +func (l *nullLogger) Warn(msg string, args ...interface{}) {} + +func (l *nullLogger) Error(msg string, args ...interface{}) {} + +func (l *nullLogger) IsTrace() bool { return false } + +func (l *nullLogger) IsDebug() bool { return false } + +func (l *nullLogger) IsInfo() bool { return false } + +func (l *nullLogger) IsWarn() bool { return false } + +func (l *nullLogger) IsError() bool { return false } + +func (l *nullLogger) ImpliedArgs() []interface{} { return []interface{}{} } + +func (l *nullLogger) With(args ...interface{}) Logger { return l } + +func (l *nullLogger) Name() string { return "" } + +func (l *nullLogger) Named(name string) Logger { return l } + +func (l *nullLogger) ResetNamed(name string) Logger { return l } + +func (l *nullLogger) SetLevel(level Level) {} + +func (l *nullLogger) StandardLogger(opts *StandardLoggerOptions) *log.Logger { + return log.New(l.StandardWriter(opts), "", log.LstdFlags) +} + +func (l *nullLogger) StandardWriter(opts *StandardLoggerOptions) io.Writer { + return ioutil.Discard +} diff --git a/vendor/github.com/hashicorp/go-hclog/stacktrace.go b/vendor/github.com/hashicorp/go-hclog/stacktrace.go new file mode 100644 index 00000000000..9b27bd3d3d9 --- /dev/null +++ b/vendor/github.com/hashicorp/go-hclog/stacktrace.go @@ -0,0 +1,109 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// 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. + +package hclog + +import ( + "bytes" + "runtime" + "strconv" + "strings" + "sync" +) + +var ( + _stacktraceIgnorePrefixes = []string{ + "runtime.goexit", + "runtime.main", + } + _stacktracePool = sync.Pool{ + New: func() interface{} { + return newProgramCounters(64) + }, + } +) + +// CapturedStacktrace represents a stacktrace captured by a previous call +// to log.Stacktrace. If passed to a logging function, the stacktrace +// will be appended. +type CapturedStacktrace string + +// Stacktrace captures a stacktrace of the current goroutine and returns +// it to be passed to a logging function. +func Stacktrace() CapturedStacktrace { + return CapturedStacktrace(takeStacktrace()) +} + +func takeStacktrace() string { + programCounters := _stacktracePool.Get().(*programCounters) + defer _stacktracePool.Put(programCounters) + + var buffer bytes.Buffer + + for { + // Skip the call to runtime.Counters and takeStacktrace so that the + // program counters start at the caller of takeStacktrace. + n := runtime.Callers(2, programCounters.pcs) + if n < cap(programCounters.pcs) { + programCounters.pcs = programCounters.pcs[:n] + break + } + // Don't put the too-short counter slice back into the pool; this lets + // the pool adjust if we consistently take deep stacktraces. + programCounters = newProgramCounters(len(programCounters.pcs) * 2) + } + + i := 0 + frames := runtime.CallersFrames(programCounters.pcs) + for frame, more := frames.Next(); more; frame, more = frames.Next() { + if shouldIgnoreStacktraceFunction(frame.Function) { + continue + } + if i != 0 { + buffer.WriteByte('\n') + } + i++ + buffer.WriteString(frame.Function) + buffer.WriteByte('\n') + buffer.WriteByte('\t') + buffer.WriteString(frame.File) + buffer.WriteByte(':') + buffer.WriteString(strconv.Itoa(int(frame.Line))) + } + + return buffer.String() +} + +func shouldIgnoreStacktraceFunction(function string) bool { + for _, prefix := range _stacktraceIgnorePrefixes { + if strings.HasPrefix(function, prefix) { + return true + } + } + return false +} + +type programCounters struct { + pcs []uintptr +} + +func newProgramCounters(size int) *programCounters { + return &programCounters{make([]uintptr, size)} +} diff --git a/vendor/github.com/hashicorp/go-hclog/stdlog.go b/vendor/github.com/hashicorp/go-hclog/stdlog.go new file mode 100644 index 00000000000..f35d875d327 --- /dev/null +++ b/vendor/github.com/hashicorp/go-hclog/stdlog.go @@ -0,0 +1,95 @@ +package hclog + +import ( + "bytes" + "log" + "strings" +) + +// Provides a io.Writer to shim the data out of *log.Logger +// and back into our Logger. This is basically the only way to +// build upon *log.Logger. +type stdlogAdapter struct { + log Logger + inferLevels bool + forceLevel Level +} + +// Take the data, infer the levels if configured, and send it through +// a regular Logger. +func (s *stdlogAdapter) Write(data []byte) (int, error) { + str := string(bytes.TrimRight(data, " \t\n")) + + if s.forceLevel != NoLevel { + // Use pickLevel to strip log levels included in the line since we are + // forcing the level + _, str := s.pickLevel(str) + + // Log at the forced level + s.dispatch(str, s.forceLevel) + } else if s.inferLevels { + level, str := s.pickLevel(str) + s.dispatch(str, level) + } else { + s.log.Info(str) + } + + return len(data), nil +} + +func (s *stdlogAdapter) dispatch(str string, level Level) { + switch level { + case Trace: + s.log.Trace(str) + case Debug: + s.log.Debug(str) + case Info: + s.log.Info(str) + case Warn: + s.log.Warn(str) + case Error: + s.log.Error(str) + default: + s.log.Info(str) + } +} + +// Detect, based on conventions, what log level this is. +func (s *stdlogAdapter) pickLevel(str string) (Level, string) { + switch { + case strings.HasPrefix(str, "[DEBUG]"): + return Debug, strings.TrimSpace(str[7:]) + case strings.HasPrefix(str, "[TRACE]"): + return Trace, strings.TrimSpace(str[7:]) + case strings.HasPrefix(str, "[INFO]"): + return Info, strings.TrimSpace(str[6:]) + case strings.HasPrefix(str, "[WARN]"): + return Warn, strings.TrimSpace(str[7:]) + case strings.HasPrefix(str, "[ERROR]"): + return Error, strings.TrimSpace(str[7:]) + case strings.HasPrefix(str, "[ERR]"): + return Error, strings.TrimSpace(str[5:]) + default: + return Info, str + } +} + +type logWriter struct { + l *log.Logger +} + +func (l *logWriter) Write(b []byte) (int, error) { + l.l.Println(string(bytes.TrimRight(b, " \n\t"))) + return len(b), nil +} + +// Takes a standard library logger and returns a Logger that will write to it +func FromStandardLogger(l *log.Logger, opts *LoggerOptions) Logger { + var dl LoggerOptions = *opts + + // Use the time format that log.Logger uses + dl.DisableTime = true + dl.Output = &logWriter{l} + + return New(&dl) +} diff --git a/vendor/github.com/hashicorp/go-hclog/writer.go b/vendor/github.com/hashicorp/go-hclog/writer.go new file mode 100644 index 00000000000..421a1f06c0b --- /dev/null +++ b/vendor/github.com/hashicorp/go-hclog/writer.go @@ -0,0 +1,82 @@ +package hclog + +import ( + "bytes" + "io" +) + +type writer struct { + b bytes.Buffer + w io.Writer + color ColorOption +} + +func newWriter(w io.Writer, color ColorOption) *writer { + return &writer{w: w, color: color} +} + +func (w *writer) Flush(level Level) (err error) { + var unwritten = w.b.Bytes() + + if w.color != ColorOff { + color := _levelToColor[level] + unwritten = []byte(color.Sprintf("%s", unwritten)) + } + + if lw, ok := w.w.(LevelWriter); ok { + _, err = lw.LevelWrite(level, unwritten) + } else { + _, err = w.w.Write(unwritten) + } + w.b.Reset() + return err +} + +func (w *writer) Write(p []byte) (int, error) { + return w.b.Write(p) +} + +func (w *writer) WriteByte(c byte) error { + return w.b.WriteByte(c) +} + +func (w *writer) WriteString(s string) (int, error) { + return w.b.WriteString(s) +} + +// LevelWriter is the interface that wraps the LevelWrite method. +type LevelWriter interface { + LevelWrite(level Level, p []byte) (n int, err error) +} + +// LeveledWriter writes all log messages to the standard writer, +// except for log levels that are defined in the overrides map. +type LeveledWriter struct { + standard io.Writer + overrides map[Level]io.Writer +} + +// NewLeveledWriter returns an initialized LeveledWriter. +// +// standard will be used as the default writer for all log levels, +// except for log levels that are defined in the overrides map. +func NewLeveledWriter(standard io.Writer, overrides map[Level]io.Writer) *LeveledWriter { + return &LeveledWriter{ + standard: standard, + overrides: overrides, + } +} + +// Write implements io.Writer. +func (lw *LeveledWriter) Write(p []byte) (int, error) { + return lw.standard.Write(p) +} + +// LevelWrite implements LevelWriter. +func (lw *LeveledWriter) LevelWrite(level Level, p []byte) (int, error) { + w, ok := lw.overrides[level] + if !ok { + w = lw.standard + } + return w.Write(p) +} diff --git a/vendor/github.com/hashicorp/go-plugin/.gitignore b/vendor/github.com/hashicorp/go-plugin/.gitignore new file mode 100644 index 00000000000..4befed30a1c --- /dev/null +++ b/vendor/github.com/hashicorp/go-plugin/.gitignore @@ -0,0 +1,2 @@ +.DS_Store +.idea diff --git a/vendor/github.com/hashicorp/go-plugin/LICENSE b/vendor/github.com/hashicorp/go-plugin/LICENSE new file mode 100644 index 00000000000..82b4de97c7e --- /dev/null +++ b/vendor/github.com/hashicorp/go-plugin/LICENSE @@ -0,0 +1,353 @@ +Mozilla Public License, version 2.0 + +1. Definitions + +1.1. “Contributor” + + means each individual or legal entity that creates, contributes to the + creation of, or owns Covered Software. + +1.2. “Contributor Version” + + means the combination of the Contributions of others (if any) used by a + Contributor and that particular Contributor’s Contribution. + +1.3. “Contribution” + + means Covered Software of a particular Contributor. + +1.4. “Covered Software” + + means Source Code Form to which the initial Contributor has attached the + notice in Exhibit A, the Executable Form of such Source Code Form, and + Modifications of such Source Code Form, in each case including portions + thereof. + +1.5. “Incompatible With Secondary Licenses” + means + + a. that the initial Contributor has attached the notice described in + Exhibit B to the Covered Software; or + + b. that the Covered Software was made available under the terms of version + 1.1 or earlier of the License, but not also under the terms of a + Secondary License. + +1.6. “Executable Form” + + means any form of the work other than Source Code Form. + +1.7. “Larger Work” + + means a work that combines Covered Software with other material, in a separate + file or files, that is not Covered Software. + +1.8. “License” + + means this document. + +1.9. “Licensable” + + means having the right to grant, to the maximum extent possible, whether at the + time of the initial grant or subsequently, any and all of the rights conveyed by + this License. + +1.10. “Modifications” + + means any of the following: + + a. any file in Source Code Form that results from an addition to, deletion + from, or modification of the contents of Covered Software; or + + b. any new file in Source Code Form that contains any Covered Software. + +1.11. “Patent Claims” of a Contributor + + means any patent claim(s), including without limitation, method, process, + and apparatus claims, in any patent Licensable by such Contributor that + would be infringed, but for the grant of the License, by the making, + using, selling, offering for sale, having made, import, or transfer of + either its Contributions or its Contributor Version. + +1.12. “Secondary License” + + means either the GNU General Public License, Version 2.0, the GNU Lesser + General Public License, Version 2.1, the GNU Affero General Public + License, Version 3.0, or any later versions of those licenses. + +1.13. “Source Code Form” + + means the form of the work preferred for making modifications. + +1.14. “You” (or “Your”) + + means an individual or a legal entity exercising rights under this + License. For legal entities, “You” includes any entity that controls, is + controlled by, or is under common control with You. For purposes of this + definition, “control” means (a) the power, direct or indirect, to cause + the direction or management of such entity, whether by contract or + otherwise, or (b) ownership of more than fifty percent (50%) of the + outstanding shares or beneficial ownership of such entity. + + +2. License Grants and Conditions + +2.1. Grants + + Each Contributor hereby grants You a world-wide, royalty-free, + non-exclusive license: + + a. under intellectual property rights (other than patent or trademark) + Licensable by such Contributor to use, reproduce, make available, + modify, display, perform, distribute, and otherwise exploit its + Contributions, either on an unmodified basis, with Modifications, or as + part of a Larger Work; and + + b. under Patent Claims of such Contributor to make, use, sell, offer for + sale, have made, import, and otherwise transfer either its Contributions + or its Contributor Version. + +2.2. Effective Date + + The licenses granted in Section 2.1 with respect to any Contribution become + effective for each Contribution on the date the Contributor first distributes + such Contribution. + +2.3. Limitations on Grant Scope + + The licenses granted in this Section 2 are the only rights granted under this + License. No additional rights or licenses will be implied from the distribution + or licensing of Covered Software under this License. Notwithstanding Section + 2.1(b) above, no patent license is granted by a Contributor: + + a. for any code that a Contributor has removed from Covered Software; or + + b. for infringements caused by: (i) Your and any other third party’s + modifications of Covered Software, or (ii) the combination of its + Contributions with other software (except as part of its Contributor + Version); or + + c. under Patent Claims infringed by Covered Software in the absence of its + Contributions. + + This License does not grant any rights in the trademarks, service marks, or + logos of any Contributor (except as may be necessary to comply with the + notice requirements in Section 3.4). + +2.4. Subsequent Licenses + + No Contributor makes additional grants as a result of Your choice to + distribute the Covered Software under a subsequent version of this License + (see Section 10.2) or under the terms of a Secondary License (if permitted + under the terms of Section 3.3). + +2.5. Representation + + Each Contributor represents that the Contributor believes its Contributions + are its original creation(s) or it has sufficient rights to grant the + rights to its Contributions conveyed by this License. + +2.6. Fair Use + + This License is not intended to limit any rights You have under applicable + copyright doctrines of fair use, fair dealing, or other equivalents. + +2.7. Conditions + + Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in + Section 2.1. + + +3. Responsibilities + +3.1. Distribution of Source Form + + All distribution of Covered Software in Source Code Form, including any + Modifications that You create or to which You contribute, must be under the + terms of this License. You must inform recipients that the Source Code Form + of the Covered Software is governed by the terms of this License, and how + they can obtain a copy of this License. You may not attempt to alter or + restrict the recipients’ rights in the Source Code Form. + +3.2. Distribution of Executable Form + + If You distribute Covered Software in Executable Form then: + + a. such Covered Software must also be made available in Source Code Form, + as described in Section 3.1, and You must inform recipients of the + Executable Form how they can obtain a copy of such Source Code Form by + reasonable means in a timely manner, at a charge no more than the cost + of distribution to the recipient; and + + b. You may distribute such Executable Form under the terms of this License, + or sublicense it under different terms, provided that the license for + the Executable Form does not attempt to limit or alter the recipients’ + rights in the Source Code Form under this License. + +3.3. Distribution of a Larger Work + + You may create and distribute a Larger Work under terms of Your choice, + provided that You also comply with the requirements of this License for the + Covered Software. If the Larger Work is a combination of Covered Software + with a work governed by one or more Secondary Licenses, and the Covered + Software is not Incompatible With Secondary Licenses, this License permits + You to additionally distribute such Covered Software under the terms of + such Secondary License(s), so that the recipient of the Larger Work may, at + their option, further distribute the Covered Software under the terms of + either this License or such Secondary License(s). + +3.4. Notices + + You may not remove or alter the substance of any license notices (including + copyright notices, patent notices, disclaimers of warranty, or limitations + of liability) contained within the Source Code Form of the Covered + Software, except that You may alter any license notices to the extent + required to remedy known factual inaccuracies. + +3.5. Application of Additional Terms + + You may choose to offer, and to charge a fee for, warranty, support, + indemnity or liability obligations to one or more recipients of Covered + Software. However, You may do so only on Your own behalf, and not on behalf + of any Contributor. You must make it absolutely clear that any such + warranty, support, indemnity, or liability obligation is offered by You + alone, and You hereby agree to indemnify every Contributor for any + liability incurred by such Contributor as a result of warranty, support, + indemnity or liability terms You offer. You may include additional + disclaimers of warranty and limitations of liability specific to any + jurisdiction. + +4. Inability to Comply Due to Statute or Regulation + + If it is impossible for You to comply with any of the terms of this License + with respect to some or all of the Covered Software due to statute, judicial + order, or regulation then You must: (a) comply with the terms of this License + to the maximum extent possible; and (b) describe the limitations and the code + they affect. Such description must be placed in a text file included with all + distributions of the Covered Software under this License. Except to the + extent prohibited by statute or regulation, such description must be + sufficiently detailed for a recipient of ordinary skill to be able to + understand it. + +5. Termination + +5.1. The rights granted under this License will terminate automatically if You + fail to comply with any of its terms. However, if You become compliant, + then the rights granted under this License from a particular Contributor + are reinstated (a) provisionally, unless and until such Contributor + explicitly and finally terminates Your grants, and (b) on an ongoing basis, + if such Contributor fails to notify You of the non-compliance by some + reasonable means prior to 60 days after You have come back into compliance. + Moreover, Your grants from a particular Contributor are reinstated on an + ongoing basis if such Contributor notifies You of the non-compliance by + some reasonable means, this is the first time You have received notice of + non-compliance with this License from such Contributor, and You become + compliant prior to 30 days after Your receipt of the notice. + +5.2. If You initiate litigation against any entity by asserting a patent + infringement claim (excluding declaratory judgment actions, counter-claims, + and cross-claims) alleging that a Contributor Version directly or + indirectly infringes any patent, then the rights granted to You by any and + all Contributors for the Covered Software under Section 2.1 of this License + shall terminate. + +5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user + license agreements (excluding distributors and resellers) which have been + validly granted by You or Your distributors under this License prior to + termination shall survive termination. + +6. Disclaimer of Warranty + + Covered Software is provided under this License on an “as is” basis, without + warranty of any kind, either expressed, implied, or statutory, including, + without limitation, warranties that the Covered Software is free of defects, + merchantable, fit for a particular purpose or non-infringing. The entire + risk as to the quality and performance of the Covered Software is with You. + Should any Covered Software prove defective in any respect, You (not any + Contributor) assume the cost of any necessary servicing, repair, or + correction. This disclaimer of warranty constitutes an essential part of this + License. No use of any Covered Software is authorized under this License + except under this disclaimer. + +7. Limitation of Liability + + Under no circumstances and under no legal theory, whether tort (including + negligence), contract, or otherwise, shall any Contributor, or anyone who + distributes Covered Software as permitted above, be liable to You for any + direct, indirect, special, incidental, or consequential damages of any + character including, without limitation, damages for lost profits, loss of + goodwill, work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses, even if such party shall have been + informed of the possibility of such damages. This limitation of liability + shall not apply to liability for death or personal injury resulting from such + party’s negligence to the extent applicable law prohibits such limitation. + Some jurisdictions do not allow the exclusion or limitation of incidental or + consequential damages, so this exclusion and limitation may not apply to You. + +8. Litigation + + Any litigation relating to this License may be brought only in the courts of + a jurisdiction where the defendant maintains its principal place of business + and such litigation shall be governed by laws of that jurisdiction, without + reference to its conflict-of-law provisions. Nothing in this Section shall + prevent a party’s ability to bring cross-claims or counter-claims. + +9. Miscellaneous + + This License represents the complete agreement concerning the subject matter + hereof. If any provision of this License is held to be unenforceable, such + provision shall be reformed only to the extent necessary to make it + enforceable. Any law or regulation which provides that the language of a + contract shall be construed against the drafter shall not be used to construe + this License against a Contributor. + + +10. Versions of the License + +10.1. New Versions + + Mozilla Foundation is the license steward. Except as provided in Section + 10.3, no one other than the license steward has the right to modify or + publish new versions of this License. Each version will be given a + distinguishing version number. + +10.2. Effect of New Versions + + You may distribute the Covered Software under the terms of the version of + the License under which You originally received the Covered Software, or + under the terms of any subsequent version published by the license + steward. + +10.3. Modified Versions + + If you create software not governed by this License, and you want to + create a new license for such software, you may create and use a modified + version of this License if you rename the license and remove any + references to the name of the license steward (except to note that such + modified license differs from this License). + +10.4. Distributing Source Code Form that is Incompatible With Secondary Licenses + If You choose to distribute Source Code Form that is Incompatible With + Secondary Licenses under the terms of this version of the License, the + notice described in Exhibit B of this License must be attached. + +Exhibit A - Source Code Form License Notice + + This Source Code Form is subject to the + terms of the Mozilla Public License, v. + 2.0. If a copy of the MPL was not + distributed with this file, You can + obtain one at + http://mozilla.org/MPL/2.0/. + +If it is not possible or desirable to put the notice in a particular file, then +You may include the notice in a location (such as a LICENSE file in a relevant +directory) where a recipient would be likely to look for such a notice. + +You may add additional accurate notices of copyright ownership. + +Exhibit B - “Incompatible With Secondary Licenses” Notice + + This Source Code Form is “Incompatible + With Secondary Licenses”, as defined by + the Mozilla Public License, v. 2.0. diff --git a/vendor/github.com/hashicorp/go-plugin/README.md b/vendor/github.com/hashicorp/go-plugin/README.md new file mode 100644 index 00000000000..46ee09fc0ca --- /dev/null +++ b/vendor/github.com/hashicorp/go-plugin/README.md @@ -0,0 +1,163 @@ +# Go Plugin System over RPC + +`go-plugin` is a Go (golang) plugin system over RPC. It is the plugin system +that has been in use by HashiCorp tooling for over 4 years. While initially +created for [Packer](https://www.packer.io), it is additionally in use by +[Terraform](https://www.terraform.io), [Nomad](https://www.nomadproject.io), and +[Vault](https://www.vaultproject.io). + +While the plugin system is over RPC, it is currently only designed to work +over a local [reliable] network. Plugins over a real network are not supported +and will lead to unexpected behavior. + +This plugin system has been used on millions of machines across many different +projects and has proven to be battle hardened and ready for production use. + +## Features + +The HashiCorp plugin system supports a number of features: + +**Plugins are Go interface implementations.** This makes writing and consuming +plugins feel very natural. To a plugin author: you just implement an +interface as if it were going to run in the same process. For a plugin user: +you just use and call functions on an interface as if it were in the same +process. This plugin system handles the communication in between. + +**Cross-language support.** Plugins can be written (and consumed) by +almost every major language. This library supports serving plugins via +[gRPC](http://www.grpc.io). gRPC-based plugins enable plugins to be written +in any language. + +**Complex arguments and return values are supported.** This library +provides APIs for handling complex arguments and return values such +as interfaces, `io.Reader/Writer`, etc. We do this by giving you a library +(`MuxBroker`) for creating new connections between the client/server to +serve additional interfaces or transfer raw data. + +**Bidirectional communication.** Because the plugin system supports +complex arguments, the host process can send it interface implementations +and the plugin can call back into the host process. + +**Built-in Logging.** Any plugins that use the `log` standard library +will have log data automatically sent to the host process. The host +process will mirror this output prefixed with the path to the plugin +binary. This makes debugging with plugins simple. If the host system +uses [hclog](https://github.com/hashicorp/go-hclog) then the log data +will be structured. If the plugin also uses hclog, logs from the plugin +will be sent to the host hclog and be structured. + +**Protocol Versioning.** A very basic "protocol version" is supported that +can be incremented to invalidate any previous plugins. This is useful when +interface signatures are changing, protocol level changes are necessary, +etc. When a protocol version is incompatible, a human friendly error +message is shown to the end user. + +**Stdout/Stderr Syncing.** While plugins are subprocesses, they can continue +to use stdout/stderr as usual and the output will get mirrored back to +the host process. The host process can control what `io.Writer` these +streams go to to prevent this from happening. + +**TTY Preservation.** Plugin subprocesses are connected to the identical +stdin file descriptor as the host process, allowing software that requires +a TTY to work. For example, a plugin can execute `ssh` and even though there +are multiple subprocesses and RPC happening, it will look and act perfectly +to the end user. + +**Host upgrade while a plugin is running.** Plugins can be "reattached" +so that the host process can be upgraded while the plugin is still running. +This requires the host/plugin to know this is possible and daemonize +properly. `NewClient` takes a `ReattachConfig` to determine if and how to +reattach. + +**Cryptographically Secure Plugins.** Plugins can be verified with an expected +checksum and RPC communications can be configured to use TLS. The host process +must be properly secured to protect this configuration. + +## Architecture + +The HashiCorp plugin system works by launching subprocesses and communicating +over RPC (using standard `net/rpc` or [gRPC](http://www.grpc.io)). A single +connection is made between any plugin and the host process. For net/rpc-based +plugins, we use a [connection multiplexing](https://github.com/hashicorp/yamux) +library to multiplex any other connections on top. For gRPC-based plugins, +the HTTP2 protocol handles multiplexing. + +This architecture has a number of benefits: + + * Plugins can't crash your host process: A panic in a plugin doesn't + panic the plugin user. + + * Plugins are very easy to write: just write a Go application and `go build`. + Or use any other language to write a gRPC server with a tiny amount of + boilerplate to support go-plugin. + + * Plugins are very easy to install: just put the binary in a location where + the host will find it (depends on the host but this library also provides + helpers), and the plugin host handles the rest. + + * Plugins can be relatively secure: The plugin only has access to the + interfaces and args given to it, not to the entire memory space of the + process. Additionally, go-plugin can communicate with the plugin over + TLS. + +## Usage + +To use the plugin system, you must take the following steps. These are +high-level steps that must be done. Examples are available in the +`examples/` directory. + + 1. Choose the interface(s) you want to expose for plugins. + + 2. For each interface, implement an implementation of that interface + that communicates over a `net/rpc` connection or over a + [gRPC](http://www.grpc.io) connection or both. You'll have to implement + both a client and server implementation. + + 3. Create a `Plugin` implementation that knows how to create the RPC + client/server for a given plugin type. + + 4. Plugin authors call `plugin.Serve` to serve a plugin from the + `main` function. + + 5. Plugin users use `plugin.Client` to launch a subprocess and request + an interface implementation over RPC. + +That's it! In practice, step 2 is the most tedious and time consuming step. +Even so, it isn't very difficult and you can see examples in the `examples/` +directory as well as throughout our various open source projects. + +For complete API documentation, see [GoDoc](https://godoc.org/github.com/hashicorp/go-plugin). + +## Roadmap + +Our plugin system is constantly evolving. As we use the plugin system for +new projects or for new features in existing projects, we constantly find +improvements we can make. + +At this point in time, the roadmap for the plugin system is: + +**Semantic Versioning.** Plugins will be able to implement a semantic version. +This plugin system will give host processes a system for constraining +versions. This is in addition to the protocol versioning already present +which is more for larger underlying changes. + +## What About Shared Libraries? + +When we started using plugins (late 2012, early 2013), plugins over RPC +were the only option since Go didn't support dynamic library loading. Today, +Go supports the [plugin](https://golang.org/pkg/plugin/) standard library with +a number of limitations. Since 2012, our plugin system has stabilized +from tens of millions of users using it, and has many benefits we've come to +value greatly. + +For example, we use this plugin system in +[Vault](https://www.vaultproject.io) where dynamic library loading is +not acceptable for security reasons. That is an extreme +example, but we believe our library system has more upsides than downsides +over dynamic library loading and since we've had it built and tested for years, +we'll continue to use it. + +Shared libraries have one major advantage over our system which is much +higher performance. In real world scenarios across our various tools, +we've never required any more performance out of our plugin system and it +has seen very high throughput, so this isn't a concern for us at the moment. diff --git a/vendor/github.com/hashicorp/go-plugin/client.go b/vendor/github.com/hashicorp/go-plugin/client.go new file mode 100644 index 00000000000..780a3121da3 --- /dev/null +++ b/vendor/github.com/hashicorp/go-plugin/client.go @@ -0,0 +1,1037 @@ +package plugin + +import ( + "bufio" + "context" + "crypto/subtle" + "crypto/tls" + "crypto/x509" + "encoding/base64" + "errors" + "fmt" + "hash" + "io" + "io/ioutil" + "net" + "os" + "os/exec" + "path/filepath" + "strconv" + "strings" + "sync" + "sync/atomic" + "time" + + hclog "github.com/hashicorp/go-hclog" +) + +// If this is 1, then we've called CleanupClients. This can be used +// by plugin RPC implementations to change error behavior since you +// can expected network connection errors at this point. This should be +// read by using sync/atomic. +var Killed uint32 = 0 + +// This is a slice of the "managed" clients which are cleaned up when +// calling Cleanup +var managedClients = make([]*Client, 0, 5) +var managedClientsLock sync.Mutex + +// Error types +var ( + // ErrProcessNotFound is returned when a client is instantiated to + // reattach to an existing process and it isn't found. + ErrProcessNotFound = errors.New("Reattachment process not found") + + // ErrChecksumsDoNotMatch is returned when binary's checksum doesn't match + // the one provided in the SecureConfig. + ErrChecksumsDoNotMatch = errors.New("checksums did not match") + + // ErrSecureNoChecksum is returned when an empty checksum is provided to the + // SecureConfig. + ErrSecureConfigNoChecksum = errors.New("no checksum provided") + + // ErrSecureNoHash is returned when a nil Hash object is provided to the + // SecureConfig. + ErrSecureConfigNoHash = errors.New("no hash implementation provided") + + // ErrSecureConfigAndReattach is returned when both Reattach and + // SecureConfig are set. + ErrSecureConfigAndReattach = errors.New("only one of Reattach or SecureConfig can be set") +) + +// Client handles the lifecycle of a plugin application. It launches +// plugins, connects to them, dispenses interface implementations, and handles +// killing the process. +// +// Plugin hosts should use one Client for each plugin executable. To +// dispense a plugin type, use the `Client.Client` function, and then +// cal `Dispense`. This awkward API is mostly historical but is used to split +// the client that deals with subprocess management and the client that +// does RPC management. +// +// See NewClient and ClientConfig for using a Client. +type Client struct { + config *ClientConfig + exited bool + l sync.Mutex + address net.Addr + process *os.Process + client ClientProtocol + protocol Protocol + logger hclog.Logger + doneCtx context.Context + ctxCancel context.CancelFunc + negotiatedVersion int + + // clientWaitGroup is used to manage the lifecycle of the plugin management + // goroutines. + clientWaitGroup sync.WaitGroup + + // stderrWaitGroup is used to prevent the command's Wait() function from + // being called before we've finished reading from the stderr pipe. + stderrWaitGroup sync.WaitGroup + + // processKilled is used for testing only, to flag when the process was + // forcefully killed. + processKilled bool +} + +// NegotiatedVersion returns the protocol version negotiated with the server. +// This is only valid after Start() is called. +func (c *Client) NegotiatedVersion() int { + return c.negotiatedVersion +} + +// ClientConfig is the configuration used to initialize a new +// plugin client. After being used to initialize a plugin client, +// that configuration must not be modified again. +type ClientConfig struct { + // HandshakeConfig is the configuration that must match servers. + HandshakeConfig + + // Plugins are the plugins that can be consumed. + // The implied version of this PluginSet is the Handshake.ProtocolVersion. + Plugins PluginSet + + // VersionedPlugins is a map of PluginSets for specific protocol versions. + // These can be used to negotiate a compatible version between client and + // server. If this is set, Handshake.ProtocolVersion is not required. + VersionedPlugins map[int]PluginSet + + // One of the following must be set, but not both. + // + // Cmd is the unstarted subprocess for starting the plugin. If this is + // set, then the Client starts the plugin process on its own and connects + // to it. + // + // Reattach is configuration for reattaching to an existing plugin process + // that is already running. This isn't common. + Cmd *exec.Cmd + Reattach *ReattachConfig + + // SecureConfig is configuration for verifying the integrity of the + // executable. It can not be used with Reattach. + SecureConfig *SecureConfig + + // TLSConfig is used to enable TLS on the RPC client. + TLSConfig *tls.Config + + // Managed represents if the client should be managed by the + // plugin package or not. If true, then by calling CleanupClients, + // it will automatically be cleaned up. Otherwise, the client + // user is fully responsible for making sure to Kill all plugin + // clients. By default the client is _not_ managed. + Managed bool + + // The minimum and maximum port to use for communicating with + // the subprocess. If not set, this defaults to 10,000 and 25,000 + // respectively. + MinPort, MaxPort uint + + // StartTimeout is the timeout to wait for the plugin to say it + // has started successfully. + StartTimeout time.Duration + + // If non-nil, then the stderr of the client will be written to here + // (as well as the log). This is the original os.Stderr of the subprocess. + // This isn't the output of synced stderr. + Stderr io.Writer + + // SyncStdout, SyncStderr can be set to override the + // respective os.Std* values in the plugin. Care should be taken to + // avoid races here. If these are nil, then this will be set to + // ioutil.Discard. + SyncStdout io.Writer + SyncStderr io.Writer + + // AllowedProtocols is a list of allowed protocols. If this isn't set, + // then only netrpc is allowed. This is so that older go-plugin systems + // can show friendly errors if they see a plugin with an unknown + // protocol. + // + // By setting this, you can cause an error immediately on plugin start + // if an unsupported protocol is used with a good error message. + // + // If this isn't set at all (nil value), then only net/rpc is accepted. + // This is done for legacy reasons. You must explicitly opt-in to + // new protocols. + AllowedProtocols []Protocol + + // Logger is the logger that the client will used. If none is provided, + // it will default to hclog's default logger. + Logger hclog.Logger + + // AutoMTLS has the client and server automatically negotiate mTLS for + // transport authentication. This ensures that only the original client will + // be allowed to connect to the server, and all other connections will be + // rejected. The client will also refuse to connect to any server that isn't + // the original instance started by the client. + // + // In this mode of operation, the client generates a one-time use tls + // certificate, sends the public x.509 certificate to the new server, and + // the server generates a one-time use tls certificate, and sends the public + // x.509 certificate back to the client. These are used to authenticate all + // rpc connections between the client and server. + // + // Setting AutoMTLS to true implies that the server must support the + // protocol, and correctly negotiate the tls certificates, or a connection + // failure will result. + // + // The client should not set TLSConfig, nor should the server set a + // TLSProvider, because AutoMTLS implies that a new certificate and tls + // configuration will be generated at startup. + // + // You cannot Reattach to a server with this option enabled. + AutoMTLS bool +} + +// ReattachConfig is used to configure a client to reattach to an +// already-running plugin process. You can retrieve this information by +// calling ReattachConfig on Client. +type ReattachConfig struct { + Protocol Protocol + Addr net.Addr + Pid int + + // Test is set to true if this is reattaching to to a plugin in "test mode" + // (see ServeConfig.Test). In this mode, client.Kill will NOT kill the + // process and instead will rely on the plugin to terminate itself. This + // should not be used in non-test environments. + Test bool +} + +// SecureConfig is used to configure a client to verify the integrity of an +// executable before running. It does this by verifying the checksum is +// expected. Hash is used to specify the hashing method to use when checksumming +// the file. The configuration is verified by the client by calling the +// SecureConfig.Check() function. +// +// The host process should ensure the checksum was provided by a trusted and +// authoritative source. The binary should be installed in such a way that it +// can not be modified by an unauthorized user between the time of this check +// and the time of execution. +type SecureConfig struct { + Checksum []byte + Hash hash.Hash +} + +// Check takes the filepath to an executable and returns true if the checksum of +// the file matches the checksum provided in the SecureConfig. +func (s *SecureConfig) Check(filePath string) (bool, error) { + if len(s.Checksum) == 0 { + return false, ErrSecureConfigNoChecksum + } + + if s.Hash == nil { + return false, ErrSecureConfigNoHash + } + + file, err := os.Open(filePath) + if err != nil { + return false, err + } + defer file.Close() + + _, err = io.Copy(s.Hash, file) + if err != nil { + return false, err + } + + sum := s.Hash.Sum(nil) + + return subtle.ConstantTimeCompare(sum, s.Checksum) == 1, nil +} + +// This makes sure all the managed subprocesses are killed and properly +// logged. This should be called before the parent process running the +// plugins exits. +// +// This must only be called _once_. +func CleanupClients() { + // Set the killed to true so that we don't get unexpected panics + atomic.StoreUint32(&Killed, 1) + + // Kill all the managed clients in parallel and use a WaitGroup + // to wait for them all to finish up. + var wg sync.WaitGroup + managedClientsLock.Lock() + for _, client := range managedClients { + wg.Add(1) + + go func(client *Client) { + client.Kill() + wg.Done() + }(client) + } + managedClientsLock.Unlock() + + wg.Wait() +} + +// Creates a new plugin client which manages the lifecycle of an external +// plugin and gets the address for the RPC connection. +// +// The client must be cleaned up at some point by calling Kill(). If +// the client is a managed client (created with NewManagedClient) you +// can just call CleanupClients at the end of your program and they will +// be properly cleaned. +func NewClient(config *ClientConfig) (c *Client) { + if config.MinPort == 0 && config.MaxPort == 0 { + config.MinPort = 10000 + config.MaxPort = 25000 + } + + if config.StartTimeout == 0 { + config.StartTimeout = 1 * time.Minute + } + + if config.Stderr == nil { + config.Stderr = ioutil.Discard + } + + if config.SyncStdout == nil { + config.SyncStdout = ioutil.Discard + } + if config.SyncStderr == nil { + config.SyncStderr = ioutil.Discard + } + + if config.AllowedProtocols == nil { + config.AllowedProtocols = []Protocol{ProtocolNetRPC} + } + + if config.Logger == nil { + config.Logger = hclog.New(&hclog.LoggerOptions{ + Output: hclog.DefaultOutput, + Level: hclog.Trace, + Name: "plugin", + }) + } + + c = &Client{ + config: config, + logger: config.Logger, + } + if config.Managed { + managedClientsLock.Lock() + managedClients = append(managedClients, c) + managedClientsLock.Unlock() + } + + return +} + +// Client returns the protocol client for this connection. +// +// Subsequent calls to this will return the same client. +func (c *Client) Client() (ClientProtocol, error) { + _, err := c.Start() + if err != nil { + return nil, err + } + + c.l.Lock() + defer c.l.Unlock() + + if c.client != nil { + return c.client, nil + } + + switch c.protocol { + case ProtocolNetRPC: + c.client, err = newRPCClient(c) + + case ProtocolGRPC: + c.client, err = newGRPCClient(c.doneCtx, c) + + default: + return nil, fmt.Errorf("unknown server protocol: %s", c.protocol) + } + + if err != nil { + c.client = nil + return nil, err + } + + return c.client, nil +} + +// Tells whether or not the underlying process has exited. +func (c *Client) Exited() bool { + c.l.Lock() + defer c.l.Unlock() + return c.exited +} + +// killed is used in tests to check if a process failed to exit gracefully, and +// needed to be killed. +func (c *Client) killed() bool { + c.l.Lock() + defer c.l.Unlock() + return c.processKilled +} + +// End the executing subprocess (if it is running) and perform any cleanup +// tasks necessary such as capturing any remaining logs and so on. +// +// This method blocks until the process successfully exits. +// +// This method can safely be called multiple times. +func (c *Client) Kill() { + // Grab a lock to read some private fields. + c.l.Lock() + process := c.process + addr := c.address + c.l.Unlock() + + // If there is no process, there is nothing to kill. + if process == nil { + return + } + + defer func() { + // Wait for the all client goroutines to finish. + c.clientWaitGroup.Wait() + + // Make sure there is no reference to the old process after it has been + // killed. + c.l.Lock() + c.process = nil + c.l.Unlock() + }() + + // We need to check for address here. It is possible that the plugin + // started (process != nil) but has no address (addr == nil) if the + // plugin failed at startup. If we do have an address, we need to close + // the plugin net connections. + graceful := false + if addr != nil { + // Close the client to cleanly exit the process. + client, err := c.Client() + if err == nil { + err = client.Close() + + // If there is no error, then we attempt to wait for a graceful + // exit. If there was an error, we assume that graceful cleanup + // won't happen and just force kill. + graceful = err == nil + if err != nil { + // If there was an error just log it. We're going to force + // kill in a moment anyways. + c.logger.Warn("error closing client during Kill", "err", err) + } + } else { + c.logger.Error("client", "error", err) + } + } + + // If we're attempting a graceful exit, then we wait for a short period + // of time to allow that to happen. To wait for this we just wait on the + // doneCh which would be closed if the process exits. + if graceful { + select { + case <-c.doneCtx.Done(): + c.logger.Debug("plugin exited") + return + case <-time.After(2 * time.Second): + } + } + + // If graceful exiting failed, just kill it + c.logger.Warn("plugin failed to exit gracefully") + process.Kill() + + c.l.Lock() + c.processKilled = true + c.l.Unlock() +} + +// Starts the underlying subprocess, communicating with it to negotiate +// a port for RPC connections, and returning the address to connect via RPC. +// +// This method is safe to call multiple times. Subsequent calls have no effect. +// Once a client has been started once, it cannot be started again, even if +// it was killed. +func (c *Client) Start() (addr net.Addr, err error) { + c.l.Lock() + defer c.l.Unlock() + + if c.address != nil { + return c.address, nil + } + + // If one of cmd or reattach isn't set, then it is an error. We wrap + // this in a {} for scoping reasons, and hopeful that the escape + // analysis will pop the stack here. + { + cmdSet := c.config.Cmd != nil + attachSet := c.config.Reattach != nil + secureSet := c.config.SecureConfig != nil + if cmdSet == attachSet { + return nil, fmt.Errorf("Only one of Cmd or Reattach must be set") + } + + if secureSet && attachSet { + return nil, ErrSecureConfigAndReattach + } + } + + if c.config.Reattach != nil { + return c.reattach() + } + + if c.config.VersionedPlugins == nil { + c.config.VersionedPlugins = make(map[int]PluginSet) + } + + // handle all plugins as versioned, using the handshake config as the default. + version := int(c.config.ProtocolVersion) + + // Make sure we're not overwriting a real version 0. If ProtocolVersion was + // non-zero, then we have to just assume the user made sure that + // VersionedPlugins doesn't conflict. + if _, ok := c.config.VersionedPlugins[version]; !ok && c.config.Plugins != nil { + c.config.VersionedPlugins[version] = c.config.Plugins + } + + var versionStrings []string + for v := range c.config.VersionedPlugins { + versionStrings = append(versionStrings, strconv.Itoa(v)) + } + + env := []string{ + fmt.Sprintf("%s=%s", c.config.MagicCookieKey, c.config.MagicCookieValue), + fmt.Sprintf("PLUGIN_MIN_PORT=%d", c.config.MinPort), + fmt.Sprintf("PLUGIN_MAX_PORT=%d", c.config.MaxPort), + fmt.Sprintf("PLUGIN_PROTOCOL_VERSIONS=%s", strings.Join(versionStrings, ",")), + } + + cmd := c.config.Cmd + cmd.Env = append(cmd.Env, os.Environ()...) + cmd.Env = append(cmd.Env, env...) + cmd.Stdin = os.Stdin + + cmdStdout, err := cmd.StdoutPipe() + if err != nil { + return nil, err + } + cmdStderr, err := cmd.StderrPipe() + if err != nil { + return nil, err + } + + if c.config.SecureConfig != nil { + if ok, err := c.config.SecureConfig.Check(cmd.Path); err != nil { + return nil, fmt.Errorf("error verifying checksum: %s", err) + } else if !ok { + return nil, ErrChecksumsDoNotMatch + } + } + + // Setup a temporary certificate for client/server mtls, and send the public + // certificate to the plugin. + if c.config.AutoMTLS { + c.logger.Info("configuring client automatic mTLS") + certPEM, keyPEM, err := generateCert() + if err != nil { + c.logger.Error("failed to generate client certificate", "error", err) + return nil, err + } + cert, err := tls.X509KeyPair(certPEM, keyPEM) + if err != nil { + c.logger.Error("failed to parse client certificate", "error", err) + return nil, err + } + + cmd.Env = append(cmd.Env, fmt.Sprintf("PLUGIN_CLIENT_CERT=%s", certPEM)) + + c.config.TLSConfig = &tls.Config{ + Certificates: []tls.Certificate{cert}, + ServerName: "localhost", + } + } + + c.logger.Debug("starting plugin", "path", cmd.Path, "args", cmd.Args) + err = cmd.Start() + if err != nil { + return + } + + // Set the process + c.process = cmd.Process + c.logger.Debug("plugin started", "path", cmd.Path, "pid", c.process.Pid) + + // Make sure the command is properly cleaned up if there is an error + defer func() { + r := recover() + + if err != nil || r != nil { + cmd.Process.Kill() + } + + if r != nil { + panic(r) + } + }() + + // Create a context for when we kill + c.doneCtx, c.ctxCancel = context.WithCancel(context.Background()) + + // Start goroutine that logs the stderr + c.clientWaitGroup.Add(1) + c.stderrWaitGroup.Add(1) + // logStderr calls Done() + go c.logStderr(cmdStderr) + + c.clientWaitGroup.Add(1) + go func() { + // ensure the context is cancelled when we're done + defer c.ctxCancel() + + defer c.clientWaitGroup.Done() + + // get the cmd info early, since the process information will be removed + // in Kill. + pid := c.process.Pid + path := cmd.Path + + // wait to finish reading from stderr since the stderr pipe reader + // will be closed by the subsequent call to cmd.Wait(). + c.stderrWaitGroup.Wait() + + // Wait for the command to end. + err := cmd.Wait() + + debugMsgArgs := []interface{}{ + "path", path, + "pid", pid, + } + if err != nil { + debugMsgArgs = append(debugMsgArgs, + []interface{}{"error", err.Error()}...) + } + + // Log and make sure to flush the logs write away + c.logger.Debug("plugin process exited", debugMsgArgs...) + os.Stderr.Sync() + + // Set that we exited, which takes a lock + c.l.Lock() + defer c.l.Unlock() + c.exited = true + }() + + // Start a goroutine that is going to be reading the lines + // out of stdout + linesCh := make(chan string) + c.clientWaitGroup.Add(1) + go func() { + defer c.clientWaitGroup.Done() + defer close(linesCh) + + scanner := bufio.NewScanner(cmdStdout) + for scanner.Scan() { + linesCh <- scanner.Text() + } + }() + + // Make sure after we exit we read the lines from stdout forever + // so they don't block since it is a pipe. + // The scanner goroutine above will close this, but track it with a wait + // group for completeness. + c.clientWaitGroup.Add(1) + defer func() { + go func() { + defer c.clientWaitGroup.Done() + for range linesCh { + } + }() + }() + + // Some channels for the next step + timeout := time.After(c.config.StartTimeout) + + // Start looking for the address + c.logger.Debug("waiting for RPC address", "path", cmd.Path) + select { + case <-timeout: + err = errors.New("timeout while waiting for plugin to start") + case <-c.doneCtx.Done(): + err = errors.New("plugin exited before we could connect") + case line := <-linesCh: + // Trim the line and split by "|" in order to get the parts of + // the output. + line = strings.TrimSpace(line) + parts := strings.SplitN(line, "|", 6) + if len(parts) < 4 { + err = fmt.Errorf( + "Unrecognized remote plugin message: %s\n\n"+ + "This usually means that the plugin is either invalid or simply\n"+ + "needs to be recompiled to support the latest protocol.", line) + return + } + + // Check the core protocol. Wrapped in a {} for scoping. + { + var coreProtocol int + coreProtocol, err = strconv.Atoi(parts[0]) + if err != nil { + err = fmt.Errorf("Error parsing core protocol version: %s", err) + return + } + + if coreProtocol != CoreProtocolVersion { + err = fmt.Errorf("Incompatible core API version with plugin. "+ + "Plugin version: %s, Core version: %d\n\n"+ + "To fix this, the plugin usually only needs to be recompiled.\n"+ + "Please report this to the plugin author.", parts[0], CoreProtocolVersion) + return + } + } + + // Test the API version + version, pluginSet, err := c.checkProtoVersion(parts[1]) + if err != nil { + return addr, err + } + + // set the Plugins value to the compatible set, so the version + // doesn't need to be passed through to the ClientProtocol + // implementation. + c.config.Plugins = pluginSet + c.negotiatedVersion = version + c.logger.Debug("using plugin", "version", version) + + switch parts[2] { + case "tcp": + addr, err = net.ResolveTCPAddr("tcp", parts[3]) + case "unix": + addr, err = net.ResolveUnixAddr("unix", parts[3]) + default: + err = fmt.Errorf("Unknown address type: %s", parts[3]) + } + + // If we have a server type, then record that. We default to net/rpc + // for backwards compatibility. + c.protocol = ProtocolNetRPC + if len(parts) >= 5 { + c.protocol = Protocol(parts[4]) + } + + found := false + for _, p := range c.config.AllowedProtocols { + if p == c.protocol { + found = true + break + } + } + if !found { + err = fmt.Errorf("Unsupported plugin protocol %q. Supported: %v", + c.protocol, c.config.AllowedProtocols) + return addr, err + } + + // See if we have a TLS certificate from the server. + // Checking if the length is > 50 rules out catching the unused "extra" + // data returned from some older implementations. + if len(parts) >= 6 && len(parts[5]) > 50 { + err := c.loadServerCert(parts[5]) + if err != nil { + return nil, fmt.Errorf("error parsing server cert: %s", err) + } + } + } + + c.address = addr + return +} + +// loadServerCert is used by AutoMTLS to read an x.509 cert returned by the +// server, and load it as the RootCA for the client TLSConfig. +func (c *Client) loadServerCert(cert string) error { + certPool := x509.NewCertPool() + + asn1, err := base64.RawStdEncoding.DecodeString(cert) + if err != nil { + return err + } + + x509Cert, err := x509.ParseCertificate([]byte(asn1)) + if err != nil { + return err + } + + certPool.AddCert(x509Cert) + + c.config.TLSConfig.RootCAs = certPool + return nil +} + +func (c *Client) reattach() (net.Addr, error) { + // Verify the process still exists. If not, then it is an error + p, err := os.FindProcess(c.config.Reattach.Pid) + if err != nil { + // On Unix systems, FindProcess never returns an error. + // On Windows, for non-existent pids it returns: + // os.SyscallError - 'OpenProcess: the paremter is incorrect' + return nil, ErrProcessNotFound + } + + // Attempt to connect to the addr since on Unix systems FindProcess + // doesn't actually return an error if it can't find the process. + conn, err := net.Dial( + c.config.Reattach.Addr.Network(), + c.config.Reattach.Addr.String()) + if err != nil { + p.Kill() + return nil, ErrProcessNotFound + } + conn.Close() + + // Create a context for when we kill + c.doneCtx, c.ctxCancel = context.WithCancel(context.Background()) + + c.clientWaitGroup.Add(1) + // Goroutine to mark exit status + go func(pid int) { + defer c.clientWaitGroup.Done() + + // ensure the context is cancelled when we're done + defer c.ctxCancel() + + // Wait for the process to die + pidWait(pid) + + // Log so we can see it + c.logger.Debug("reattached plugin process exited") + + // Mark it + c.l.Lock() + defer c.l.Unlock() + c.exited = true + }(p.Pid) + + // Set the address and protocol + c.address = c.config.Reattach.Addr + c.protocol = c.config.Reattach.Protocol + if c.protocol == "" { + // Default the protocol to net/rpc for backwards compatibility + c.protocol = ProtocolNetRPC + } + + // If we're in test mode, we do NOT set the process. This avoids the + // process being killed (the only purpose we have for c.process), since + // in test mode the process is responsible for exiting on its own. + if !c.config.Reattach.Test { + c.process = p + } + + return c.address, nil +} + +// checkProtoVersion returns the negotiated version and PluginSet. +// This returns an error if the server returned an incompatible protocol +// version, or an invalid handshake response. +func (c *Client) checkProtoVersion(protoVersion string) (int, PluginSet, error) { + serverVersion, err := strconv.Atoi(protoVersion) + if err != nil { + return 0, nil, fmt.Errorf("Error parsing protocol version %q: %s", protoVersion, err) + } + + // record these for the error message + var clientVersions []int + + // all versions, including the legacy ProtocolVersion have been added to + // the versions set + for version, plugins := range c.config.VersionedPlugins { + clientVersions = append(clientVersions, version) + + if serverVersion != version { + continue + } + return version, plugins, nil + } + + return 0, nil, fmt.Errorf("Incompatible API version with plugin. "+ + "Plugin version: %d, Client versions: %d", serverVersion, clientVersions) +} + +// ReattachConfig returns the information that must be provided to NewClient +// to reattach to the plugin process that this client started. This is +// useful for plugins that detach from their parent process. +// +// If this returns nil then the process hasn't been started yet. Please +// call Start or Client before calling this. +func (c *Client) ReattachConfig() *ReattachConfig { + c.l.Lock() + defer c.l.Unlock() + + if c.address == nil { + return nil + } + + if c.config.Cmd != nil && c.config.Cmd.Process == nil { + return nil + } + + // If we connected via reattach, just return the information as-is + if c.config.Reattach != nil { + return c.config.Reattach + } + + return &ReattachConfig{ + Protocol: c.protocol, + Addr: c.address, + Pid: c.config.Cmd.Process.Pid, + } +} + +// Protocol returns the protocol of server on the remote end. This will +// start the plugin process if it isn't already started. Errors from +// starting the plugin are surpressed and ProtocolInvalid is returned. It +// is recommended you call Start explicitly before calling Protocol to ensure +// no errors occur. +func (c *Client) Protocol() Protocol { + _, err := c.Start() + if err != nil { + return ProtocolInvalid + } + + return c.protocol +} + +func netAddrDialer(addr net.Addr) func(string, time.Duration) (net.Conn, error) { + return func(_ string, _ time.Duration) (net.Conn, error) { + // Connect to the client + conn, err := net.Dial(addr.Network(), addr.String()) + if err != nil { + return nil, err + } + if tcpConn, ok := conn.(*net.TCPConn); ok { + // Make sure to set keep alive so that the connection doesn't die + tcpConn.SetKeepAlive(true) + } + + return conn, nil + } +} + +// dialer is compatible with grpc.WithDialer and creates the connection +// to the plugin. +func (c *Client) dialer(_ string, timeout time.Duration) (net.Conn, error) { + conn, err := netAddrDialer(c.address)("", timeout) + if err != nil { + return nil, err + } + + // If we have a TLS config we wrap our connection. We only do this + // for net/rpc since gRPC uses its own mechanism for TLS. + if c.protocol == ProtocolNetRPC && c.config.TLSConfig != nil { + conn = tls.Client(conn, c.config.TLSConfig) + } + + return conn, nil +} + +var stdErrBufferSize = 64 * 1024 + +func (c *Client) logStderr(r io.Reader) { + defer c.clientWaitGroup.Done() + defer c.stderrWaitGroup.Done() + l := c.logger.Named(filepath.Base(c.config.Cmd.Path)) + + reader := bufio.NewReaderSize(r, stdErrBufferSize) + // continuation indicates the previous line was a prefix + continuation := false + + for { + line, isPrefix, err := reader.ReadLine() + switch { + case err == io.EOF: + return + case err != nil: + l.Error("reading plugin stderr", "error", err) + return + } + + c.config.Stderr.Write(line) + + // The line was longer than our max token size, so it's likely + // incomplete and won't unmarshal. + if isPrefix || continuation { + l.Debug(string(line)) + + // if we're finishing a continued line, add the newline back in + if !isPrefix { + c.config.Stderr.Write([]byte{'\n'}) + } + + continuation = isPrefix + continue + } + + c.config.Stderr.Write([]byte{'\n'}) + + entry, err := parseJSON(line) + // If output is not JSON format, print directly to Debug + if err != nil { + // Attempt to infer the desired log level from the commonly used + // string prefixes + switch line := string(line); { + case strings.HasPrefix(line, "[TRACE]"): + l.Trace(line) + case strings.HasPrefix(line, "[DEBUG]"): + l.Debug(line) + case strings.HasPrefix(line, "[INFO]"): + l.Info(line) + case strings.HasPrefix(line, "[WARN]"): + l.Warn(line) + case strings.HasPrefix(line, "[ERROR]"): + l.Error(line) + default: + l.Debug(line) + } + } else { + out := flattenKVPairs(entry.KVPairs) + + out = append(out, "timestamp", entry.Timestamp.Format(hclog.TimeFormat)) + switch hclog.LevelFromString(entry.Level) { + case hclog.Trace: + l.Trace(entry.Message, out...) + case hclog.Debug: + l.Debug(entry.Message, out...) + case hclog.Info: + l.Info(entry.Message, out...) + case hclog.Warn: + l.Warn(entry.Message, out...) + case hclog.Error: + l.Error(entry.Message, out...) + default: + // if there was no log level, it's likely this is unexpected + // json from something other than hclog, and we should output + // it verbatim. + l.Debug(string(line)) + } + } + } +} diff --git a/vendor/github.com/hashicorp/go-plugin/discover.go b/vendor/github.com/hashicorp/go-plugin/discover.go new file mode 100644 index 00000000000..d22c566ed50 --- /dev/null +++ b/vendor/github.com/hashicorp/go-plugin/discover.go @@ -0,0 +1,28 @@ +package plugin + +import ( + "path/filepath" +) + +// Discover discovers plugins that are in a given directory. +// +// The directory doesn't need to be absolute. For example, "." will work fine. +// +// This currently assumes any file matching the glob is a plugin. +// In the future this may be smarter about checking that a file is +// executable and so on. +// +// TODO: test +func Discover(glob, dir string) ([]string, error) { + var err error + + // Make the directory absolute if it isn't already + if !filepath.IsAbs(dir) { + dir, err = filepath.Abs(dir) + if err != nil { + return nil, err + } + } + + return filepath.Glob(filepath.Join(dir, glob)) +} diff --git a/vendor/github.com/hashicorp/go-plugin/error.go b/vendor/github.com/hashicorp/go-plugin/error.go new file mode 100644 index 00000000000..22a7baa6a0d --- /dev/null +++ b/vendor/github.com/hashicorp/go-plugin/error.go @@ -0,0 +1,24 @@ +package plugin + +// This is a type that wraps error types so that they can be messaged +// across RPC channels. Since "error" is an interface, we can't always +// gob-encode the underlying structure. This is a valid error interface +// implementer that we will push across. +type BasicError struct { + Message string +} + +// NewBasicError is used to create a BasicError. +// +// err is allowed to be nil. +func NewBasicError(err error) *BasicError { + if err == nil { + return nil + } + + return &BasicError{err.Error()} +} + +func (e *BasicError) Error() string { + return e.Message +} diff --git a/vendor/github.com/hashicorp/go-plugin/go.mod b/vendor/github.com/hashicorp/go-plugin/go.mod new file mode 100644 index 00000000000..4e182e6258f --- /dev/null +++ b/vendor/github.com/hashicorp/go-plugin/go.mod @@ -0,0 +1,15 @@ +module github.com/hashicorp/go-plugin + +go 1.13 + +require ( + github.com/golang/protobuf v1.3.4 + github.com/hashicorp/go-hclog v0.14.1 + github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb + github.com/jhump/protoreflect v1.6.0 + github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77 + github.com/oklog/run v1.0.0 + github.com/stretchr/testify v1.3.0 // indirect + golang.org/x/net v0.0.0-20190311183353-d8887717615a + google.golang.org/grpc v1.27.1 +) diff --git a/vendor/github.com/hashicorp/go-plugin/go.sum b/vendor/github.com/hashicorp/go-plugin/go.sum new file mode 100644 index 00000000000..56062044ee4 --- /dev/null +++ b/vendor/github.com/hashicorp/go-plugin/go.sum @@ -0,0 +1,87 @@ +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/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +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/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +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/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/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.4 h1:87PNWwrRvUSnqS4dlcBU/ftvOIBep4sYuBLlh6rX2wk= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +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/go-hclog v0.14.1 h1:nQcJDQwIAGnmoUWp8ubocEX40cCml/17YkF6csQLReU= +github.com/hashicorp/go-hclog v0.14.1/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= +github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb h1:b5rjCoWHc7eqmAS4/qyk21ZsHyb6Mxv/jykxvNTkU4M= +github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= +github.com/jhump/protoreflect v1.6.0 h1:h5jfMVslIg6l29nsMs0D8Wj17RDVdNYti0vDN/PZZoE= +github.com/jhump/protoreflect v1.6.0/go.mod h1:eaTn3RZAmMBcV0fifFvlm6VHNz3wSkYyXYWUh7ymB74= +github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA= +github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.10 h1:qxFzApOv4WsAL965uUPIsXzAKCZxN2p9UqdhFS4ZW10= +github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= +github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77 h1:7GoSOOW2jpsfkntVKaS2rAr1TJqfcxotyaUcuxoZSzg= +github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= +github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw= +github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +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-20180530234432-1e491301e022/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d h1:g9qWBGx4puODJTMVyoPrpoxPFgVGd+z1DZwjfRu4d0I= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/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/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-20190423024810-112230192c58/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-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20191008105621-543471e840be h1:QAcqgptGM8IQBC9K/RC4o+O9YmqEm0diQn9QmZw/0mU= +golang.org/x/sys v0.0.0-20191008105621-543471e840be/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/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +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-20170818010345-ee236bd376b0/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8 h1:Nw54tB0rB7hY/N0NQvRW8DG4Yk3Q6T9cu9RcFQDu1tc= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55 h1:gSJIx1SDwno+2ElGhA4+qG2zF97qiUzTM+rQ0klBOcE= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/grpc v1.8.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.27.1 h1:zvIju4sqAGvwKspUQOhwnpcqSbzi7/H6QomNNjTL4sk= +google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/vendor/github.com/hashicorp/go-plugin/grpc_broker.go b/vendor/github.com/hashicorp/go-plugin/grpc_broker.go new file mode 100644 index 00000000000..daf142d1709 --- /dev/null +++ b/vendor/github.com/hashicorp/go-plugin/grpc_broker.go @@ -0,0 +1,457 @@ +package plugin + +import ( + "context" + "crypto/tls" + "errors" + "fmt" + "log" + "net" + "sync" + "sync/atomic" + "time" + + "github.com/hashicorp/go-plugin/internal/plugin" + + "github.com/oklog/run" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials" +) + +// streamer interface is used in the broker to send/receive connection +// information. +type streamer interface { + Send(*plugin.ConnInfo) error + Recv() (*plugin.ConnInfo, error) + Close() +} + +// sendErr is used to pass errors back during a send. +type sendErr struct { + i *plugin.ConnInfo + ch chan error +} + +// gRPCBrokerServer is used by the plugin to start a stream and to send +// connection information to/from the plugin. Implements GRPCBrokerServer and +// streamer interfaces. +type gRPCBrokerServer struct { + // send is used to send connection info to the gRPC stream. + send chan *sendErr + + // recv is used to receive connection info from the gRPC stream. + recv chan *plugin.ConnInfo + + // quit closes down the stream. + quit chan struct{} + + // o is used to ensure we close the quit channel only once. + o sync.Once +} + +func newGRPCBrokerServer() *gRPCBrokerServer { + return &gRPCBrokerServer{ + send: make(chan *sendErr), + recv: make(chan *plugin.ConnInfo), + quit: make(chan struct{}), + } +} + +// StartStream implements the GRPCBrokerServer interface and will block until +// the quit channel is closed or the context reports Done. The stream will pass +// connection information to/from the client. +func (s *gRPCBrokerServer) StartStream(stream plugin.GRPCBroker_StartStreamServer) error { + doneCh := stream.Context().Done() + defer s.Close() + + // Proccess send stream + go func() { + for { + select { + case <-doneCh: + return + case <-s.quit: + return + case se := <-s.send: + err := stream.Send(se.i) + se.ch <- err + } + } + }() + + // Process receive stream + for { + i, err := stream.Recv() + if err != nil { + return err + } + select { + case <-doneCh: + return nil + case <-s.quit: + return nil + case s.recv <- i: + } + } + + return nil +} + +// Send is used by the GRPCBroker to pass connection information into the stream +// to the client. +func (s *gRPCBrokerServer) Send(i *plugin.ConnInfo) error { + ch := make(chan error) + defer close(ch) + + select { + case <-s.quit: + return errors.New("broker closed") + case s.send <- &sendErr{ + i: i, + ch: ch, + }: + } + + return <-ch +} + +// Recv is used by the GRPCBroker to pass connection information that has been +// sent from the client from the stream to the broker. +func (s *gRPCBrokerServer) Recv() (*plugin.ConnInfo, error) { + select { + case <-s.quit: + return nil, errors.New("broker closed") + case i := <-s.recv: + return i, nil + } +} + +// Close closes the quit channel, shutting down the stream. +func (s *gRPCBrokerServer) Close() { + s.o.Do(func() { + close(s.quit) + }) +} + +// gRPCBrokerClientImpl is used by the client to start a stream and to send +// connection information to/from the client. Implements GRPCBrokerClient and +// streamer interfaces. +type gRPCBrokerClientImpl struct { + // client is the underlying GRPC client used to make calls to the server. + client plugin.GRPCBrokerClient + + // send is used to send connection info to the gRPC stream. + send chan *sendErr + + // recv is used to receive connection info from the gRPC stream. + recv chan *plugin.ConnInfo + + // quit closes down the stream. + quit chan struct{} + + // o is used to ensure we close the quit channel only once. + o sync.Once +} + +func newGRPCBrokerClient(conn *grpc.ClientConn) *gRPCBrokerClientImpl { + return &gRPCBrokerClientImpl{ + client: plugin.NewGRPCBrokerClient(conn), + send: make(chan *sendErr), + recv: make(chan *plugin.ConnInfo), + quit: make(chan struct{}), + } +} + +// StartStream implements the GRPCBrokerClient interface and will block until +// the quit channel is closed or the context reports Done. The stream will pass +// connection information to/from the plugin. +func (s *gRPCBrokerClientImpl) StartStream() error { + ctx, cancelFunc := context.WithCancel(context.Background()) + defer cancelFunc() + defer s.Close() + + stream, err := s.client.StartStream(ctx) + if err != nil { + return err + } + doneCh := stream.Context().Done() + + go func() { + for { + select { + case <-doneCh: + return + case <-s.quit: + return + case se := <-s.send: + err := stream.Send(se.i) + se.ch <- err + } + } + }() + + for { + i, err := stream.Recv() + if err != nil { + return err + } + select { + case <-doneCh: + return nil + case <-s.quit: + return nil + case s.recv <- i: + } + } + + return nil +} + +// Send is used by the GRPCBroker to pass connection information into the stream +// to the plugin. +func (s *gRPCBrokerClientImpl) Send(i *plugin.ConnInfo) error { + ch := make(chan error) + defer close(ch) + + select { + case <-s.quit: + return errors.New("broker closed") + case s.send <- &sendErr{ + i: i, + ch: ch, + }: + } + + return <-ch +} + +// Recv is used by the GRPCBroker to pass connection information that has been +// sent from the plugin to the broker. +func (s *gRPCBrokerClientImpl) Recv() (*plugin.ConnInfo, error) { + select { + case <-s.quit: + return nil, errors.New("broker closed") + case i := <-s.recv: + return i, nil + } +} + +// Close closes the quit channel, shutting down the stream. +func (s *gRPCBrokerClientImpl) Close() { + s.o.Do(func() { + close(s.quit) + }) +} + +// GRPCBroker is responsible for brokering connections by unique ID. +// +// It is used by plugins to create multiple gRPC connections and data +// streams between the plugin process and the host process. +// +// This allows a plugin to request a channel with a specific ID to connect to +// or accept a connection from, and the broker handles the details of +// holding these channels open while they're being negotiated. +// +// The Plugin interface has access to these for both Server and Client. +// The broker can be used by either (optionally) to reserve and connect to +// new streams. This is useful for complex args and return values, +// or anything else you might need a data stream for. +type GRPCBroker struct { + nextId uint32 + streamer streamer + streams map[uint32]*gRPCBrokerPending + tls *tls.Config + doneCh chan struct{} + o sync.Once + + sync.Mutex +} + +type gRPCBrokerPending struct { + ch chan *plugin.ConnInfo + doneCh chan struct{} +} + +func newGRPCBroker(s streamer, tls *tls.Config) *GRPCBroker { + return &GRPCBroker{ + streamer: s, + streams: make(map[uint32]*gRPCBrokerPending), + tls: tls, + doneCh: make(chan struct{}), + } +} + +// Accept accepts a connection by ID. +// +// This should not be called multiple times with the same ID at one time. +func (b *GRPCBroker) Accept(id uint32) (net.Listener, error) { + listener, err := serverListener() + if err != nil { + return nil, err + } + + err = b.streamer.Send(&plugin.ConnInfo{ + ServiceId: id, + Network: listener.Addr().Network(), + Address: listener.Addr().String(), + }) + if err != nil { + return nil, err + } + + return listener, nil +} + +// AcceptAndServe is used to accept a specific stream ID and immediately +// serve a gRPC server on that stream ID. This is used to easily serve +// complex arguments. Each AcceptAndServe call opens a new listener socket and +// sends the connection info down the stream to the dialer. Since a new +// connection is opened every call, these calls should be used sparingly. +// Multiple gRPC server implementations can be registered to a single +// AcceptAndServe call. +func (b *GRPCBroker) AcceptAndServe(id uint32, s func([]grpc.ServerOption) *grpc.Server) { + listener, err := b.Accept(id) + if err != nil { + log.Printf("[ERR] plugin: plugin acceptAndServe error: %s", err) + return + } + defer listener.Close() + + var opts []grpc.ServerOption + if b.tls != nil { + opts = []grpc.ServerOption{grpc.Creds(credentials.NewTLS(b.tls))} + } + + server := s(opts) + + // Here we use a run group to close this goroutine if the server is shutdown + // or the broker is shutdown. + var g run.Group + { + // Serve on the listener, if shutting down call GracefulStop. + g.Add(func() error { + return server.Serve(listener) + }, func(err error) { + server.GracefulStop() + }) + } + { + // block on the closeCh or the doneCh. If we are shutting down close the + // closeCh. + closeCh := make(chan struct{}) + g.Add(func() error { + select { + case <-b.doneCh: + case <-closeCh: + } + return nil + }, func(err error) { + close(closeCh) + }) + } + + // Block until we are done + g.Run() +} + +// Close closes the stream and all servers. +func (b *GRPCBroker) Close() error { + b.streamer.Close() + b.o.Do(func() { + close(b.doneCh) + }) + return nil +} + +// Dial opens a connection by ID. +func (b *GRPCBroker) Dial(id uint32) (conn *grpc.ClientConn, err error) { + var c *plugin.ConnInfo + + // Open the stream + p := b.getStream(id) + select { + case c = <-p.ch: + close(p.doneCh) + case <-time.After(5 * time.Second): + return nil, fmt.Errorf("timeout waiting for connection info") + } + + var addr net.Addr + switch c.Network { + case "tcp": + addr, err = net.ResolveTCPAddr("tcp", c.Address) + case "unix": + addr, err = net.ResolveUnixAddr("unix", c.Address) + default: + err = fmt.Errorf("Unknown address type: %s", c.Address) + } + if err != nil { + return nil, err + } + + return dialGRPCConn(b.tls, netAddrDialer(addr)) +} + +// NextId returns a unique ID to use next. +// +// It is possible for very long-running plugin hosts to wrap this value, +// though it would require a very large amount of calls. In practice +// we've never seen it happen. +func (m *GRPCBroker) NextId() uint32 { + return atomic.AddUint32(&m.nextId, 1) +} + +// Run starts the brokering and should be executed in a goroutine, since it +// blocks forever, or until the session closes. +// +// Uses of GRPCBroker never need to call this. It is called internally by +// the plugin host/client. +func (m *GRPCBroker) Run() { + for { + stream, err := m.streamer.Recv() + if err != nil { + // Once we receive an error, just exit + break + } + + // Initialize the waiter + p := m.getStream(stream.ServiceId) + select { + case p.ch <- stream: + default: + } + + go m.timeoutWait(stream.ServiceId, p) + } +} + +func (m *GRPCBroker) getStream(id uint32) *gRPCBrokerPending { + m.Lock() + defer m.Unlock() + + p, ok := m.streams[id] + if ok { + return p + } + + m.streams[id] = &gRPCBrokerPending{ + ch: make(chan *plugin.ConnInfo, 1), + doneCh: make(chan struct{}), + } + return m.streams[id] +} + +func (m *GRPCBroker) timeoutWait(id uint32, p *gRPCBrokerPending) { + // Wait for the stream to either be picked up and connected, or + // for a timeout. + select { + case <-p.doneCh: + case <-time.After(5 * time.Second): + } + + m.Lock() + defer m.Unlock() + + // Delete the stream so no one else can grab it + delete(m.streams, id) +} diff --git a/vendor/github.com/hashicorp/go-plugin/grpc_client.go b/vendor/github.com/hashicorp/go-plugin/grpc_client.go new file mode 100644 index 00000000000..97812191329 --- /dev/null +++ b/vendor/github.com/hashicorp/go-plugin/grpc_client.go @@ -0,0 +1,123 @@ +package plugin + +import ( + "crypto/tls" + "fmt" + "math" + "net" + "time" + + "github.com/hashicorp/go-plugin/internal/plugin" + "golang.org/x/net/context" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials" + "google.golang.org/grpc/health/grpc_health_v1" +) + +func dialGRPCConn(tls *tls.Config, dialer func(string, time.Duration) (net.Conn, error)) (*grpc.ClientConn, error) { + // Build dialing options. + opts := make([]grpc.DialOption, 0, 5) + + // We use a custom dialer so that we can connect over unix domain sockets. + opts = append(opts, grpc.WithDialer(dialer)) + + // Fail right away + opts = append(opts, grpc.FailOnNonTempDialError(true)) + + // If we have no TLS configuration set, we need to explicitly tell grpc + // that we're connecting with an insecure connection. + if tls == nil { + opts = append(opts, grpc.WithInsecure()) + } else { + opts = append(opts, grpc.WithTransportCredentials( + credentials.NewTLS(tls))) + } + + opts = append(opts, + grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(math.MaxInt32)), + grpc.WithDefaultCallOptions(grpc.MaxCallSendMsgSize(math.MaxInt32))) + + // Connect. Note the first parameter is unused because we use a custom + // dialer that has the state to see the address. + conn, err := grpc.Dial("unused", opts...) + if err != nil { + return nil, err + } + + return conn, nil +} + +// newGRPCClient creates a new GRPCClient. The Client argument is expected +// to be successfully started already with a lock held. +func newGRPCClient(doneCtx context.Context, c *Client) (*GRPCClient, error) { + conn, err := dialGRPCConn(c.config.TLSConfig, c.dialer) + if err != nil { + return nil, err + } + + // Start the broker. + brokerGRPCClient := newGRPCBrokerClient(conn) + broker := newGRPCBroker(brokerGRPCClient, c.config.TLSConfig) + go broker.Run() + go brokerGRPCClient.StartStream() + + // Start the stdio client + stdioClient, err := newGRPCStdioClient(doneCtx, c.logger.Named("stdio"), conn) + if err != nil { + return nil, err + } + go stdioClient.Run(c.config.SyncStdout, c.config.SyncStderr) + + cl := &GRPCClient{ + Conn: conn, + Plugins: c.config.Plugins, + doneCtx: doneCtx, + broker: broker, + controller: plugin.NewGRPCControllerClient(conn), + } + + return cl, nil +} + +// GRPCClient connects to a GRPCServer over gRPC to dispense plugin types. +type GRPCClient struct { + Conn *grpc.ClientConn + Plugins map[string]Plugin + + doneCtx context.Context + broker *GRPCBroker + + controller plugin.GRPCControllerClient +} + +// ClientProtocol impl. +func (c *GRPCClient) Close() error { + c.broker.Close() + c.controller.Shutdown(c.doneCtx, &plugin.Empty{}) + return c.Conn.Close() +} + +// ClientProtocol impl. +func (c *GRPCClient) Dispense(name string) (interface{}, error) { + raw, ok := c.Plugins[name] + if !ok { + return nil, fmt.Errorf("unknown plugin type: %s", name) + } + + p, ok := raw.(GRPCPlugin) + if !ok { + return nil, fmt.Errorf("plugin %q doesn't support gRPC", name) + } + + return p.GRPCClient(c.doneCtx, c.broker, c.Conn) +} + +// ClientProtocol impl. +func (c *GRPCClient) Ping() error { + client := grpc_health_v1.NewHealthClient(c.Conn) + _, err := client.Check(context.Background(), &grpc_health_v1.HealthCheckRequest{ + Service: GRPCServiceName, + }) + + return err +} diff --git a/vendor/github.com/hashicorp/go-plugin/grpc_controller.go b/vendor/github.com/hashicorp/go-plugin/grpc_controller.go new file mode 100644 index 00000000000..1a8a8e70ea4 --- /dev/null +++ b/vendor/github.com/hashicorp/go-plugin/grpc_controller.go @@ -0,0 +1,23 @@ +package plugin + +import ( + "context" + + "github.com/hashicorp/go-plugin/internal/plugin" +) + +// GRPCControllerServer handles shutdown calls to terminate the server when the +// plugin client is closed. +type grpcControllerServer struct { + server *GRPCServer +} + +// Shutdown stops the grpc server. It first will attempt a graceful stop, then a +// full stop on the server. +func (s *grpcControllerServer) Shutdown(ctx context.Context, _ *plugin.Empty) (*plugin.Empty, error) { + resp := &plugin.Empty{} + + // TODO: figure out why GracefullStop doesn't work. + s.server.Stop() + return resp, nil +} diff --git a/vendor/github.com/hashicorp/go-plugin/grpc_server.go b/vendor/github.com/hashicorp/go-plugin/grpc_server.go new file mode 100644 index 00000000000..387628bf48f --- /dev/null +++ b/vendor/github.com/hashicorp/go-plugin/grpc_server.go @@ -0,0 +1,149 @@ +package plugin + +import ( + "bytes" + "crypto/tls" + "encoding/json" + "fmt" + "io" + "net" + + hclog "github.com/hashicorp/go-hclog" + "github.com/hashicorp/go-plugin/internal/plugin" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials" + "google.golang.org/grpc/health" + "google.golang.org/grpc/health/grpc_health_v1" + "google.golang.org/grpc/reflection" +) + +// GRPCServiceName is the name of the service that the health check should +// return as passing. +const GRPCServiceName = "plugin" + +// DefaultGRPCServer can be used with the "GRPCServer" field for Server +// as a default factory method to create a gRPC server with no extra options. +func DefaultGRPCServer(opts []grpc.ServerOption) *grpc.Server { + return grpc.NewServer(opts...) +} + +// GRPCServer is a ServerType implementation that serves plugins over +// gRPC. This allows plugins to easily be written for other languages. +// +// The GRPCServer outputs a custom configuration as a base64-encoded +// JSON structure represented by the GRPCServerConfig config structure. +type GRPCServer struct { + // Plugins are the list of plugins to serve. + Plugins map[string]Plugin + + // Server is the actual server that will accept connections. This + // will be used for plugin registration as well. + Server func([]grpc.ServerOption) *grpc.Server + + // TLS should be the TLS configuration if available. If this is nil, + // the connection will not have transport security. + TLS *tls.Config + + // DoneCh is the channel that is closed when this server has exited. + DoneCh chan struct{} + + // Stdout/StderrLis are the readers for stdout/stderr that will be copied + // to the stdout/stderr connection that is output. + Stdout io.Reader + Stderr io.Reader + + config GRPCServerConfig + server *grpc.Server + broker *GRPCBroker + stdioServer *grpcStdioServer + + logger hclog.Logger +} + +// ServerProtocol impl. +func (s *GRPCServer) Init() error { + // Create our server + var opts []grpc.ServerOption + if s.TLS != nil { + opts = append(opts, grpc.Creds(credentials.NewTLS(s.TLS))) + } + s.server = s.Server(opts) + + // Register the health service + healthCheck := health.NewServer() + healthCheck.SetServingStatus( + GRPCServiceName, grpc_health_v1.HealthCheckResponse_SERVING) + grpc_health_v1.RegisterHealthServer(s.server, healthCheck) + + // Register the reflection service + reflection.Register(s.server) + + // Register the broker service + brokerServer := newGRPCBrokerServer() + plugin.RegisterGRPCBrokerServer(s.server, brokerServer) + s.broker = newGRPCBroker(brokerServer, s.TLS) + go s.broker.Run() + + // Register the controller + controllerServer := &grpcControllerServer{server: s} + plugin.RegisterGRPCControllerServer(s.server, controllerServer) + + // Register the stdio service + s.stdioServer = newGRPCStdioServer(s.logger, s.Stdout, s.Stderr) + plugin.RegisterGRPCStdioServer(s.server, s.stdioServer) + + // Register all our plugins onto the gRPC server. + for k, raw := range s.Plugins { + p, ok := raw.(GRPCPlugin) + if !ok { + return fmt.Errorf("%q is not a GRPC-compatible plugin", k) + } + + if err := p.GRPCServer(s.broker, s.server); err != nil { + return fmt.Errorf("error registering %q: %s", k, err) + } + } + + return nil +} + +// Stop calls Stop on the underlying grpc.Server +func (s *GRPCServer) Stop() { + s.server.Stop() +} + +// GracefulStop calls GracefulStop on the underlying grpc.Server +func (s *GRPCServer) GracefulStop() { + s.server.GracefulStop() +} + +// Config is the GRPCServerConfig encoded as JSON then base64. +func (s *GRPCServer) Config() string { + // Create a buffer that will contain our final contents + var buf bytes.Buffer + + // Wrap the base64 encoding with JSON encoding. + if err := json.NewEncoder(&buf).Encode(s.config); err != nil { + // We panic since ths shouldn't happen under any scenario. We + // carefully control the structure being encoded here and it should + // always be successful. + panic(err) + } + + return buf.String() +} + +func (s *GRPCServer) Serve(lis net.Listener) { + defer close(s.DoneCh) + err := s.server.Serve(lis) + if err != nil { + s.logger.Error("grpc server", "error", err) + } +} + +// GRPCServerConfig is the extra configuration passed along for consumers +// to facilitate using GRPC plugins. +type GRPCServerConfig struct { + StdoutAddr string `json:"stdout_addr"` + StderrAddr string `json:"stderr_addr"` +} diff --git a/vendor/github.com/hashicorp/go-plugin/grpc_stdio.go b/vendor/github.com/hashicorp/go-plugin/grpc_stdio.go new file mode 100644 index 00000000000..a582181505f --- /dev/null +++ b/vendor/github.com/hashicorp/go-plugin/grpc_stdio.go @@ -0,0 +1,207 @@ +package plugin + +import ( + "bufio" + "bytes" + "context" + "io" + + empty "github.com/golang/protobuf/ptypes/empty" + hclog "github.com/hashicorp/go-hclog" + "github.com/hashicorp/go-plugin/internal/plugin" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +// grpcStdioBuffer is the buffer size we try to fill when sending a chunk of +// stdio data. This is currently 1 KB for no reason other than that seems like +// enough (stdio data isn't that common) and is fairly low. +const grpcStdioBuffer = 1 * 1024 + +// grpcStdioServer implements the Stdio service and streams stdiout/stderr. +type grpcStdioServer struct { + stdoutCh <-chan []byte + stderrCh <-chan []byte +} + +// newGRPCStdioServer creates a new grpcStdioServer and starts the stream +// copying for the given out and err readers. +// +// This must only be called ONCE per srcOut, srcErr. +func newGRPCStdioServer(log hclog.Logger, srcOut, srcErr io.Reader) *grpcStdioServer { + stdoutCh := make(chan []byte) + stderrCh := make(chan []byte) + + // Begin copying the streams + go copyChan(log, stdoutCh, srcOut) + go copyChan(log, stderrCh, srcErr) + + // Construct our server + return &grpcStdioServer{ + stdoutCh: stdoutCh, + stderrCh: stderrCh, + } +} + +// StreamStdio streams our stdout/err as the response. +func (s *grpcStdioServer) StreamStdio( + _ *empty.Empty, + srv plugin.GRPCStdio_StreamStdioServer, +) error { + // Share the same data value between runs. Sending this over the wire + // marshals it so we can reuse this. + var data plugin.StdioData + + for { + // Read our data + select { + case data.Data = <-s.stdoutCh: + data.Channel = plugin.StdioData_STDOUT + + case data.Data = <-s.stderrCh: + data.Channel = plugin.StdioData_STDERR + + case <-srv.Context().Done(): + return nil + } + + // Not sure if this is possible, but if we somehow got here and + // we didn't populate any data at all, then just continue. + if len(data.Data) == 0 { + continue + } + + // Send our data to the client. + if err := srv.Send(&data); err != nil { + return err + } + } +} + +// grpcStdioClient wraps the stdio service as a client to copy +// the stdio data to output writers. +type grpcStdioClient struct { + log hclog.Logger + stdioClient plugin.GRPCStdio_StreamStdioClient +} + +// newGRPCStdioClient creates a grpcStdioClient. This will perform the +// initial connection to the stdio service. If the stdio service is unavailable +// then this will be a no-op. This allows this to work without error for +// plugins that don't support this. +func newGRPCStdioClient( + ctx context.Context, + log hclog.Logger, + conn *grpc.ClientConn, +) (*grpcStdioClient, error) { + client := plugin.NewGRPCStdioClient(conn) + + // Connect immediately to the endpoint + stdioClient, err := client.StreamStdio(ctx, &empty.Empty{}) + + // If we get an Unavailable or Unimplemented error, this means that the plugin isn't + // updated and linking to the latest version of go-plugin that supports + // this. We fall back to the previous behavior of just not syncing anything. + if status.Code(err) == codes.Unavailable || status.Code(err) == codes.Unimplemented { + log.Warn("stdio service not available, stdout/stderr syncing unavailable") + stdioClient = nil + err = nil + } + if err != nil { + return nil, err + } + + return &grpcStdioClient{ + log: log, + stdioClient: stdioClient, + }, nil +} + +// Run starts the loop that receives stdio data and writes it to the given +// writers. This blocks and should be run in a goroutine. +func (c *grpcStdioClient) Run(stdout, stderr io.Writer) { + // This will be nil if stdio is not supported by the plugin + if c.stdioClient == nil { + c.log.Warn("stdio service unavailable, run will do nothing") + return + } + + for { + c.log.Trace("waiting for stdio data") + data, err := c.stdioClient.Recv() + if err != nil { + if err == io.EOF || + status.Code(err) == codes.Unavailable || + status.Code(err) == codes.Canceled || + status.Code(err) == codes.Unimplemented || + err == context.Canceled { + c.log.Debug("received EOF, stopping recv loop", "err", err) + return + } + + c.log.Error("error receiving data", "err", err) + return + } + + // Determine our output writer based on channel + var w io.Writer + switch data.Channel { + case plugin.StdioData_STDOUT: + w = stdout + + case plugin.StdioData_STDERR: + w = stderr + + default: + c.log.Warn("unknown channel, dropping", "channel", data.Channel) + continue + } + + // Write! In the event of an error we just continue. + if c.log.IsTrace() { + c.log.Trace("received data", "channel", data.Channel.String(), "len", len(data.Data)) + } + if _, err := io.Copy(w, bytes.NewReader(data.Data)); err != nil { + c.log.Error("failed to copy all bytes", "err", err) + } + } +} + +// copyChan copies an io.Reader into a channel. +func copyChan(log hclog.Logger, dst chan<- []byte, src io.Reader) { + bufsrc := bufio.NewReader(src) + + for { + // Make our data buffer. We allocate a new one per loop iteration + // so that we can send it over the channel. + var data [1024]byte + + // Read the data, this will block until data is available + n, err := bufsrc.Read(data[:]) + + // We have to check if we have data BEFORE err != nil. The bufio + // docs guarantee n == 0 on EOF but its better to be safe here. + if n > 0 { + // We have data! Send it on the channel. This will block if there + // is no reader on the other side. We expect that go-plugin will + // connect immediately to the stdio server to drain this so we want + // this block to happen for backpressure. + dst <- data[:n] + } + + // If we hit EOF we're done copying + if err == io.EOF { + log.Debug("stdio EOF, exiting copy loop") + return + } + + // Any other error we just exit the loop. We don't expect there to + // be errors since our use case for this is reading/writing from + // a in-process pipe (os.Pipe). + if err != nil { + log.Warn("error copying stdio data, stopping copy", "err", err) + return + } + } +} diff --git a/vendor/github.com/hashicorp/go-plugin/internal/plugin/gen.go b/vendor/github.com/hashicorp/go-plugin/internal/plugin/gen.go new file mode 100644 index 00000000000..fb9d415254f --- /dev/null +++ b/vendor/github.com/hashicorp/go-plugin/internal/plugin/gen.go @@ -0,0 +1,3 @@ +//go:generate protoc -I ./ ./grpc_broker.proto ./grpc_controller.proto ./grpc_stdio.proto --go_out=plugins=grpc:. + +package plugin diff --git a/vendor/github.com/hashicorp/go-plugin/internal/plugin/grpc_broker.pb.go b/vendor/github.com/hashicorp/go-plugin/internal/plugin/grpc_broker.pb.go new file mode 100644 index 00000000000..6bf103859f8 --- /dev/null +++ b/vendor/github.com/hashicorp/go-plugin/internal/plugin/grpc_broker.pb.go @@ -0,0 +1,203 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// source: grpc_broker.proto + +package plugin + +import proto "github.com/golang/protobuf/proto" +import fmt "fmt" +import math "math" + +import ( + context "golang.org/x/net/context" + grpc "google.golang.org/grpc" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package + +type ConnInfo struct { + ServiceId uint32 `protobuf:"varint,1,opt,name=service_id,json=serviceId,proto3" json:"service_id,omitempty"` + Network string `protobuf:"bytes,2,opt,name=network,proto3" json:"network,omitempty"` + Address string `protobuf:"bytes,3,opt,name=address,proto3" json:"address,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *ConnInfo) Reset() { *m = ConnInfo{} } +func (m *ConnInfo) String() string { return proto.CompactTextString(m) } +func (*ConnInfo) ProtoMessage() {} +func (*ConnInfo) Descriptor() ([]byte, []int) { + return fileDescriptor_grpc_broker_3322b07398605250, []int{0} +} +func (m *ConnInfo) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_ConnInfo.Unmarshal(m, b) +} +func (m *ConnInfo) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_ConnInfo.Marshal(b, m, deterministic) +} +func (dst *ConnInfo) XXX_Merge(src proto.Message) { + xxx_messageInfo_ConnInfo.Merge(dst, src) +} +func (m *ConnInfo) XXX_Size() int { + return xxx_messageInfo_ConnInfo.Size(m) +} +func (m *ConnInfo) XXX_DiscardUnknown() { + xxx_messageInfo_ConnInfo.DiscardUnknown(m) +} + +var xxx_messageInfo_ConnInfo proto.InternalMessageInfo + +func (m *ConnInfo) GetServiceId() uint32 { + if m != nil { + return m.ServiceId + } + return 0 +} + +func (m *ConnInfo) GetNetwork() string { + if m != nil { + return m.Network + } + return "" +} + +func (m *ConnInfo) GetAddress() string { + if m != nil { + return m.Address + } + return "" +} + +func init() { + proto.RegisterType((*ConnInfo)(nil), "plugin.ConnInfo") +} + +// Reference imports to suppress errors if they are not otherwise used. +var _ context.Context +var _ grpc.ClientConn + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +const _ = grpc.SupportPackageIsVersion4 + +// GRPCBrokerClient is the client API for GRPCBroker service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. +type GRPCBrokerClient interface { + StartStream(ctx context.Context, opts ...grpc.CallOption) (GRPCBroker_StartStreamClient, error) +} + +type gRPCBrokerClient struct { + cc *grpc.ClientConn +} + +func NewGRPCBrokerClient(cc *grpc.ClientConn) GRPCBrokerClient { + return &gRPCBrokerClient{cc} +} + +func (c *gRPCBrokerClient) StartStream(ctx context.Context, opts ...grpc.CallOption) (GRPCBroker_StartStreamClient, error) { + stream, err := c.cc.NewStream(ctx, &_GRPCBroker_serviceDesc.Streams[0], "/plugin.GRPCBroker/StartStream", opts...) + if err != nil { + return nil, err + } + x := &gRPCBrokerStartStreamClient{stream} + return x, nil +} + +type GRPCBroker_StartStreamClient interface { + Send(*ConnInfo) error + Recv() (*ConnInfo, error) + grpc.ClientStream +} + +type gRPCBrokerStartStreamClient struct { + grpc.ClientStream +} + +func (x *gRPCBrokerStartStreamClient) Send(m *ConnInfo) error { + return x.ClientStream.SendMsg(m) +} + +func (x *gRPCBrokerStartStreamClient) Recv() (*ConnInfo, error) { + m := new(ConnInfo) + if err := x.ClientStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + +// GRPCBrokerServer is the server API for GRPCBroker service. +type GRPCBrokerServer interface { + StartStream(GRPCBroker_StartStreamServer) error +} + +func RegisterGRPCBrokerServer(s *grpc.Server, srv GRPCBrokerServer) { + s.RegisterService(&_GRPCBroker_serviceDesc, srv) +} + +func _GRPCBroker_StartStream_Handler(srv interface{}, stream grpc.ServerStream) error { + return srv.(GRPCBrokerServer).StartStream(&gRPCBrokerStartStreamServer{stream}) +} + +type GRPCBroker_StartStreamServer interface { + Send(*ConnInfo) error + Recv() (*ConnInfo, error) + grpc.ServerStream +} + +type gRPCBrokerStartStreamServer struct { + grpc.ServerStream +} + +func (x *gRPCBrokerStartStreamServer) Send(m *ConnInfo) error { + return x.ServerStream.SendMsg(m) +} + +func (x *gRPCBrokerStartStreamServer) Recv() (*ConnInfo, error) { + m := new(ConnInfo) + if err := x.ServerStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + +var _GRPCBroker_serviceDesc = grpc.ServiceDesc{ + ServiceName: "plugin.GRPCBroker", + HandlerType: (*GRPCBrokerServer)(nil), + Methods: []grpc.MethodDesc{}, + Streams: []grpc.StreamDesc{ + { + StreamName: "StartStream", + Handler: _GRPCBroker_StartStream_Handler, + ServerStreams: true, + ClientStreams: true, + }, + }, + Metadata: "grpc_broker.proto", +} + +func init() { proto.RegisterFile("grpc_broker.proto", fileDescriptor_grpc_broker_3322b07398605250) } + +var fileDescriptor_grpc_broker_3322b07398605250 = []byte{ + // 175 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x12, 0x4c, 0x2f, 0x2a, 0x48, + 0x8e, 0x4f, 0x2a, 0xca, 0xcf, 0x4e, 0x2d, 0xd2, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x62, 0x2b, + 0xc8, 0x29, 0x4d, 0xcf, 0xcc, 0x53, 0x8a, 0xe5, 0xe2, 0x70, 0xce, 0xcf, 0xcb, 0xf3, 0xcc, 0x4b, + 0xcb, 0x17, 0x92, 0xe5, 0xe2, 0x2a, 0x4e, 0x2d, 0x2a, 0xcb, 0x4c, 0x4e, 0x8d, 0xcf, 0x4c, 0x91, + 0x60, 0x54, 0x60, 0xd4, 0xe0, 0x0d, 0xe2, 0x84, 0x8a, 0x78, 0xa6, 0x08, 0x49, 0x70, 0xb1, 0xe7, + 0xa5, 0x96, 0x94, 0xe7, 0x17, 0x65, 0x4b, 0x30, 0x29, 0x30, 0x6a, 0x70, 0x06, 0xc1, 0xb8, 0x20, + 0x99, 0xc4, 0x94, 0x94, 0xa2, 0xd4, 0xe2, 0x62, 0x09, 0x66, 0x88, 0x0c, 0x94, 0x6b, 0xe4, 0xcc, + 0xc5, 0xe5, 0x1e, 0x14, 0xe0, 0xec, 0x04, 0xb6, 0x5a, 0xc8, 0x94, 0x8b, 0x3b, 0xb8, 0x24, 0xb1, + 0xa8, 0x24, 0xb8, 0xa4, 0x28, 0x35, 0x31, 0x57, 0x48, 0x40, 0x0f, 0xe2, 0x08, 0x3d, 0x98, 0x0b, + 0xa4, 0x30, 0x44, 0x34, 0x18, 0x0d, 0x18, 0x9d, 0x38, 0xa2, 0xa0, 0xae, 0x4d, 0x62, 0x03, 0x3b, + 0xde, 0x18, 0x10, 0x00, 0x00, 0xff, 0xff, 0x10, 0x15, 0x39, 0x47, 0xd1, 0x00, 0x00, 0x00, +} diff --git a/vendor/github.com/hashicorp/go-plugin/internal/plugin/grpc_broker.proto b/vendor/github.com/hashicorp/go-plugin/internal/plugin/grpc_broker.proto new file mode 100644 index 00000000000..aa3df4630a7 --- /dev/null +++ b/vendor/github.com/hashicorp/go-plugin/internal/plugin/grpc_broker.proto @@ -0,0 +1,13 @@ +syntax = "proto3"; +package plugin; +option go_package = "plugin"; + +message ConnInfo { + uint32 service_id = 1; + string network = 2; + string address = 3; +} + +service GRPCBroker { + rpc StartStream(stream ConnInfo) returns (stream ConnInfo); +} diff --git a/vendor/github.com/hashicorp/go-plugin/internal/plugin/grpc_controller.pb.go b/vendor/github.com/hashicorp/go-plugin/internal/plugin/grpc_controller.pb.go new file mode 100644 index 00000000000..3e39da95a89 --- /dev/null +++ b/vendor/github.com/hashicorp/go-plugin/internal/plugin/grpc_controller.pb.go @@ -0,0 +1,145 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// source: grpc_controller.proto + +package plugin + +import proto "github.com/golang/protobuf/proto" +import fmt "fmt" +import math "math" + +import ( + context "golang.org/x/net/context" + grpc "google.golang.org/grpc" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package + +type Empty struct { + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *Empty) Reset() { *m = Empty{} } +func (m *Empty) String() string { return proto.CompactTextString(m) } +func (*Empty) ProtoMessage() {} +func (*Empty) Descriptor() ([]byte, []int) { + return fileDescriptor_grpc_controller_08f8296ef6d80436, []int{0} +} +func (m *Empty) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_Empty.Unmarshal(m, b) +} +func (m *Empty) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_Empty.Marshal(b, m, deterministic) +} +func (dst *Empty) XXX_Merge(src proto.Message) { + xxx_messageInfo_Empty.Merge(dst, src) +} +func (m *Empty) XXX_Size() int { + return xxx_messageInfo_Empty.Size(m) +} +func (m *Empty) XXX_DiscardUnknown() { + xxx_messageInfo_Empty.DiscardUnknown(m) +} + +var xxx_messageInfo_Empty proto.InternalMessageInfo + +func init() { + proto.RegisterType((*Empty)(nil), "plugin.Empty") +} + +// Reference imports to suppress errors if they are not otherwise used. +var _ context.Context +var _ grpc.ClientConn + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +const _ = grpc.SupportPackageIsVersion4 + +// GRPCControllerClient is the client API for GRPCController service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. +type GRPCControllerClient interface { + Shutdown(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*Empty, error) +} + +type gRPCControllerClient struct { + cc *grpc.ClientConn +} + +func NewGRPCControllerClient(cc *grpc.ClientConn) GRPCControllerClient { + return &gRPCControllerClient{cc} +} + +func (c *gRPCControllerClient) Shutdown(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*Empty, error) { + out := new(Empty) + err := c.cc.Invoke(ctx, "/plugin.GRPCController/Shutdown", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// GRPCControllerServer is the server API for GRPCController service. +type GRPCControllerServer interface { + Shutdown(context.Context, *Empty) (*Empty, error) +} + +func RegisterGRPCControllerServer(s *grpc.Server, srv GRPCControllerServer) { + s.RegisterService(&_GRPCController_serviceDesc, srv) +} + +func _GRPCController_Shutdown_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(Empty) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(GRPCControllerServer).Shutdown(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/plugin.GRPCController/Shutdown", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(GRPCControllerServer).Shutdown(ctx, req.(*Empty)) + } + return interceptor(ctx, in, info, handler) +} + +var _GRPCController_serviceDesc = grpc.ServiceDesc{ + ServiceName: "plugin.GRPCController", + HandlerType: (*GRPCControllerServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "Shutdown", + Handler: _GRPCController_Shutdown_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "grpc_controller.proto", +} + +func init() { + proto.RegisterFile("grpc_controller.proto", fileDescriptor_grpc_controller_08f8296ef6d80436) +} + +var fileDescriptor_grpc_controller_08f8296ef6d80436 = []byte{ + // 108 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x12, 0x4d, 0x2f, 0x2a, 0x48, + 0x8e, 0x4f, 0xce, 0xcf, 0x2b, 0x29, 0xca, 0xcf, 0xc9, 0x49, 0x2d, 0xd2, 0x2b, 0x28, 0xca, 0x2f, + 0xc9, 0x17, 0x62, 0x2b, 0xc8, 0x29, 0x4d, 0xcf, 0xcc, 0x53, 0x62, 0xe7, 0x62, 0x75, 0xcd, 0x2d, + 0x28, 0xa9, 0x34, 0xb2, 0xe2, 0xe2, 0x73, 0x0f, 0x0a, 0x70, 0x76, 0x86, 0x2b, 0x14, 0xd2, 0xe0, + 0xe2, 0x08, 0xce, 0x28, 0x2d, 0x49, 0xc9, 0x2f, 0xcf, 0x13, 0xe2, 0xd5, 0x83, 0xa8, 0xd7, 0x03, + 0x2b, 0x96, 0x42, 0xe5, 0x3a, 0x71, 0x44, 0x41, 0x8d, 0x4b, 0x62, 0x03, 0x9b, 0x6e, 0x0c, 0x08, + 0x00, 0x00, 0xff, 0xff, 0xab, 0x7c, 0x27, 0xe5, 0x76, 0x00, 0x00, 0x00, +} diff --git a/vendor/github.com/hashicorp/go-plugin/internal/plugin/grpc_controller.proto b/vendor/github.com/hashicorp/go-plugin/internal/plugin/grpc_controller.proto new file mode 100644 index 00000000000..345d0a1c1f2 --- /dev/null +++ b/vendor/github.com/hashicorp/go-plugin/internal/plugin/grpc_controller.proto @@ -0,0 +1,11 @@ +syntax = "proto3"; +package plugin; +option go_package = "plugin"; + +message Empty { +} + +// The GRPCController is responsible for telling the plugin server to shutdown. +service GRPCController { + rpc Shutdown(Empty) returns (Empty); +} diff --git a/vendor/github.com/hashicorp/go-plugin/internal/plugin/grpc_stdio.pb.go b/vendor/github.com/hashicorp/go-plugin/internal/plugin/grpc_stdio.pb.go new file mode 100644 index 00000000000..c8f94921b46 --- /dev/null +++ b/vendor/github.com/hashicorp/go-plugin/internal/plugin/grpc_stdio.pb.go @@ -0,0 +1,233 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// source: grpc_stdio.proto + +package plugin + +import proto "github.com/golang/protobuf/proto" +import fmt "fmt" +import math "math" +import empty "github.com/golang/protobuf/ptypes/empty" + +import ( + context "golang.org/x/net/context" + grpc "google.golang.org/grpc" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package + +type StdioData_Channel int32 + +const ( + StdioData_INVALID StdioData_Channel = 0 + StdioData_STDOUT StdioData_Channel = 1 + StdioData_STDERR StdioData_Channel = 2 +) + +var StdioData_Channel_name = map[int32]string{ + 0: "INVALID", + 1: "STDOUT", + 2: "STDERR", +} +var StdioData_Channel_value = map[string]int32{ + "INVALID": 0, + "STDOUT": 1, + "STDERR": 2, +} + +func (x StdioData_Channel) String() string { + return proto.EnumName(StdioData_Channel_name, int32(x)) +} +func (StdioData_Channel) EnumDescriptor() ([]byte, []int) { + return fileDescriptor_grpc_stdio_db2934322ca63bd5, []int{0, 0} +} + +// StdioData is a single chunk of stdout or stderr data that is streamed +// from GRPCStdio. +type StdioData struct { + Channel StdioData_Channel `protobuf:"varint,1,opt,name=channel,proto3,enum=plugin.StdioData_Channel" json:"channel,omitempty"` + Data []byte `protobuf:"bytes,2,opt,name=data,proto3" json:"data,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *StdioData) Reset() { *m = StdioData{} } +func (m *StdioData) String() string { return proto.CompactTextString(m) } +func (*StdioData) ProtoMessage() {} +func (*StdioData) Descriptor() ([]byte, []int) { + return fileDescriptor_grpc_stdio_db2934322ca63bd5, []int{0} +} +func (m *StdioData) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_StdioData.Unmarshal(m, b) +} +func (m *StdioData) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_StdioData.Marshal(b, m, deterministic) +} +func (dst *StdioData) XXX_Merge(src proto.Message) { + xxx_messageInfo_StdioData.Merge(dst, src) +} +func (m *StdioData) XXX_Size() int { + return xxx_messageInfo_StdioData.Size(m) +} +func (m *StdioData) XXX_DiscardUnknown() { + xxx_messageInfo_StdioData.DiscardUnknown(m) +} + +var xxx_messageInfo_StdioData proto.InternalMessageInfo + +func (m *StdioData) GetChannel() StdioData_Channel { + if m != nil { + return m.Channel + } + return StdioData_INVALID +} + +func (m *StdioData) GetData() []byte { + if m != nil { + return m.Data + } + return nil +} + +func init() { + proto.RegisterType((*StdioData)(nil), "plugin.StdioData") + proto.RegisterEnum("plugin.StdioData_Channel", StdioData_Channel_name, StdioData_Channel_value) +} + +// Reference imports to suppress errors if they are not otherwise used. +var _ context.Context +var _ grpc.ClientConn + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +const _ = grpc.SupportPackageIsVersion4 + +// GRPCStdioClient is the client API for GRPCStdio service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. +type GRPCStdioClient interface { + // StreamStdio returns a stream that contains all the stdout/stderr. + // This RPC endpoint must only be called ONCE. Once stdio data is consumed + // it is not sent again. + // + // Callers should connect early to prevent blocking on the plugin process. + StreamStdio(ctx context.Context, in *empty.Empty, opts ...grpc.CallOption) (GRPCStdio_StreamStdioClient, error) +} + +type gRPCStdioClient struct { + cc *grpc.ClientConn +} + +func NewGRPCStdioClient(cc *grpc.ClientConn) GRPCStdioClient { + return &gRPCStdioClient{cc} +} + +func (c *gRPCStdioClient) StreamStdio(ctx context.Context, in *empty.Empty, opts ...grpc.CallOption) (GRPCStdio_StreamStdioClient, error) { + stream, err := c.cc.NewStream(ctx, &_GRPCStdio_serviceDesc.Streams[0], "/plugin.GRPCStdio/StreamStdio", opts...) + if err != nil { + return nil, err + } + x := &gRPCStdioStreamStdioClient{stream} + if err := x.ClientStream.SendMsg(in); err != nil { + return nil, err + } + if err := x.ClientStream.CloseSend(); err != nil { + return nil, err + } + return x, nil +} + +type GRPCStdio_StreamStdioClient interface { + Recv() (*StdioData, error) + grpc.ClientStream +} + +type gRPCStdioStreamStdioClient struct { + grpc.ClientStream +} + +func (x *gRPCStdioStreamStdioClient) Recv() (*StdioData, error) { + m := new(StdioData) + if err := x.ClientStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + +// GRPCStdioServer is the server API for GRPCStdio service. +type GRPCStdioServer interface { + // StreamStdio returns a stream that contains all the stdout/stderr. + // This RPC endpoint must only be called ONCE. Once stdio data is consumed + // it is not sent again. + // + // Callers should connect early to prevent blocking on the plugin process. + StreamStdio(*empty.Empty, GRPCStdio_StreamStdioServer) error +} + +func RegisterGRPCStdioServer(s *grpc.Server, srv GRPCStdioServer) { + s.RegisterService(&_GRPCStdio_serviceDesc, srv) +} + +func _GRPCStdio_StreamStdio_Handler(srv interface{}, stream grpc.ServerStream) error { + m := new(empty.Empty) + if err := stream.RecvMsg(m); err != nil { + return err + } + return srv.(GRPCStdioServer).StreamStdio(m, &gRPCStdioStreamStdioServer{stream}) +} + +type GRPCStdio_StreamStdioServer interface { + Send(*StdioData) error + grpc.ServerStream +} + +type gRPCStdioStreamStdioServer struct { + grpc.ServerStream +} + +func (x *gRPCStdioStreamStdioServer) Send(m *StdioData) error { + return x.ServerStream.SendMsg(m) +} + +var _GRPCStdio_serviceDesc = grpc.ServiceDesc{ + ServiceName: "plugin.GRPCStdio", + HandlerType: (*GRPCStdioServer)(nil), + Methods: []grpc.MethodDesc{}, + Streams: []grpc.StreamDesc{ + { + StreamName: "StreamStdio", + Handler: _GRPCStdio_StreamStdio_Handler, + ServerStreams: true, + }, + }, + Metadata: "grpc_stdio.proto", +} + +func init() { proto.RegisterFile("grpc_stdio.proto", fileDescriptor_grpc_stdio_db2934322ca63bd5) } + +var fileDescriptor_grpc_stdio_db2934322ca63bd5 = []byte{ + // 221 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x12, 0x48, 0x2f, 0x2a, 0x48, + 0x8e, 0x2f, 0x2e, 0x49, 0xc9, 0xcc, 0xd7, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x62, 0x2b, 0xc8, + 0x29, 0x4d, 0xcf, 0xcc, 0x93, 0x92, 0x4e, 0xcf, 0xcf, 0x4f, 0xcf, 0x49, 0xd5, 0x07, 0x8b, 0x26, + 0x95, 0xa6, 0xe9, 0xa7, 0xe6, 0x16, 0x94, 0x54, 0x42, 0x14, 0x29, 0xb5, 0x30, 0x72, 0x71, 0x06, + 0x83, 0x34, 0xb9, 0x24, 0x96, 0x24, 0x0a, 0x19, 0x73, 0xb1, 0x27, 0x67, 0x24, 0xe6, 0xe5, 0xa5, + 0xe6, 0x48, 0x30, 0x2a, 0x30, 0x6a, 0xf0, 0x19, 0x49, 0xea, 0x41, 0x0c, 0xd1, 0x83, 0xab, 0xd1, + 0x73, 0x86, 0x28, 0x08, 0x82, 0xa9, 0x14, 0x12, 0xe2, 0x62, 0x49, 0x49, 0x2c, 0x49, 0x94, 0x60, + 0x52, 0x60, 0xd4, 0xe0, 0x09, 0x02, 0xb3, 0x95, 0xf4, 0xb8, 0xd8, 0xa1, 0xea, 0x84, 0xb8, 0xb9, + 0xd8, 0x3d, 0xfd, 0xc2, 0x1c, 0x7d, 0x3c, 0x5d, 0x04, 0x18, 0x84, 0xb8, 0xb8, 0xd8, 0x82, 0x43, + 0x5c, 0xfc, 0x43, 0x43, 0x04, 0x18, 0xa1, 0x6c, 0xd7, 0xa0, 0x20, 0x01, 0x26, 0x23, 0x77, 0x2e, + 0x4e, 0xf7, 0xa0, 0x00, 0x67, 0xb0, 0x2d, 0x42, 0x56, 0x5c, 0xdc, 0xc1, 0x25, 0x45, 0xa9, 0x89, + 0xb9, 0x10, 0xae, 0x98, 0x1e, 0xc4, 0x03, 0x7a, 0x30, 0x0f, 0xe8, 0xb9, 0x82, 0x3c, 0x20, 0x25, + 0x88, 0xe1, 0x36, 0x03, 0x46, 0x27, 0x8e, 0x28, 0xa8, 0xb7, 0x93, 0xd8, 0xc0, 0xca, 0x8d, 0x01, + 0x01, 0x00, 0x00, 0xff, 0xff, 0x5d, 0xbb, 0xe0, 0x69, 0x19, 0x01, 0x00, 0x00, +} diff --git a/vendor/github.com/hashicorp/go-plugin/internal/plugin/grpc_stdio.proto b/vendor/github.com/hashicorp/go-plugin/internal/plugin/grpc_stdio.proto new file mode 100644 index 00000000000..ce1a1223035 --- /dev/null +++ b/vendor/github.com/hashicorp/go-plugin/internal/plugin/grpc_stdio.proto @@ -0,0 +1,30 @@ +syntax = "proto3"; +package plugin; +option go_package = "plugin"; + +import "google/protobuf/empty.proto"; + +// GRPCStdio is a service that is automatically run by the plugin process +// to stream any stdout/err data so that it can be mirrored on the plugin +// host side. +service GRPCStdio { + // StreamStdio returns a stream that contains all the stdout/stderr. + // This RPC endpoint must only be called ONCE. Once stdio data is consumed + // it is not sent again. + // + // Callers should connect early to prevent blocking on the plugin process. + rpc StreamStdio(google.protobuf.Empty) returns (stream StdioData); +} + +// StdioData is a single chunk of stdout or stderr data that is streamed +// from GRPCStdio. +message StdioData { + enum Channel { + INVALID = 0; + STDOUT = 1; + STDERR = 2; + } + + Channel channel = 1; + bytes data = 2; +} diff --git a/vendor/github.com/hashicorp/go-plugin/log_entry.go b/vendor/github.com/hashicorp/go-plugin/log_entry.go new file mode 100644 index 00000000000..fb2ef930caa --- /dev/null +++ b/vendor/github.com/hashicorp/go-plugin/log_entry.go @@ -0,0 +1,73 @@ +package plugin + +import ( + "encoding/json" + "time" +) + +// logEntry is the JSON payload that gets sent to Stderr from the plugin to the host +type logEntry struct { + Message string `json:"@message"` + Level string `json:"@level"` + Timestamp time.Time `json:"timestamp"` + KVPairs []*logEntryKV `json:"kv_pairs"` +} + +// logEntryKV is a key value pair within the Output payload +type logEntryKV struct { + Key string `json:"key"` + Value interface{} `json:"value"` +} + +// flattenKVPairs is used to flatten KVPair slice into []interface{} +// for hclog consumption. +func flattenKVPairs(kvs []*logEntryKV) []interface{} { + var result []interface{} + for _, kv := range kvs { + result = append(result, kv.Key) + result = append(result, kv.Value) + } + + return result +} + +// parseJSON handles parsing JSON output +func parseJSON(input []byte) (*logEntry, error) { + var raw map[string]interface{} + entry := &logEntry{} + + err := json.Unmarshal(input, &raw) + if err != nil { + return nil, err + } + + // Parse hclog-specific objects + if v, ok := raw["@message"]; ok { + entry.Message = v.(string) + delete(raw, "@message") + } + + if v, ok := raw["@level"]; ok { + entry.Level = v.(string) + delete(raw, "@level") + } + + if v, ok := raw["@timestamp"]; ok { + t, err := time.Parse("2006-01-02T15:04:05.000000Z07:00", v.(string)) + if err != nil { + return nil, err + } + entry.Timestamp = t + delete(raw, "@timestamp") + } + + // Parse dynamic KV args from the hclog payload. + for k, v := range raw { + entry.KVPairs = append(entry.KVPairs, &logEntryKV{ + Key: k, + Value: v, + }) + } + + return entry, nil +} diff --git a/vendor/github.com/hashicorp/go-plugin/mtls.go b/vendor/github.com/hashicorp/go-plugin/mtls.go new file mode 100644 index 00000000000..88955245877 --- /dev/null +++ b/vendor/github.com/hashicorp/go-plugin/mtls.go @@ -0,0 +1,73 @@ +package plugin + +import ( + "bytes" + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "crypto/x509" + "crypto/x509/pkix" + "encoding/pem" + "math/big" + "time" +) + +// generateCert generates a temporary certificate for plugin authentication. The +// certificate and private key are returns in PEM format. +func generateCert() (cert []byte, privateKey []byte, err error) { + key, err := ecdsa.GenerateKey(elliptic.P521(), rand.Reader) + if err != nil { + return nil, nil, err + } + + serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128) + sn, err := rand.Int(rand.Reader, serialNumberLimit) + if err != nil { + return nil, nil, err + } + + host := "localhost" + + template := &x509.Certificate{ + Subject: pkix.Name{ + CommonName: host, + Organization: []string{"HashiCorp"}, + }, + DNSNames: []string{host}, + ExtKeyUsage: []x509.ExtKeyUsage{ + x509.ExtKeyUsageClientAuth, + x509.ExtKeyUsageServerAuth, + }, + KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment | x509.KeyUsageKeyAgreement | x509.KeyUsageCertSign, + BasicConstraintsValid: true, + SerialNumber: sn, + NotBefore: time.Now().Add(-30 * time.Second), + NotAfter: time.Now().Add(262980 * time.Hour), + IsCA: true, + } + + der, err := x509.CreateCertificate(rand.Reader, template, template, key.Public(), key) + if err != nil { + return nil, nil, err + } + + var certOut bytes.Buffer + if err := pem.Encode(&certOut, &pem.Block{Type: "CERTIFICATE", Bytes: der}); err != nil { + return nil, nil, err + } + + keyBytes, err := x509.MarshalECPrivateKey(key) + if err != nil { + return nil, nil, err + } + + var keyOut bytes.Buffer + if err := pem.Encode(&keyOut, &pem.Block{Type: "EC PRIVATE KEY", Bytes: keyBytes}); err != nil { + return nil, nil, err + } + + cert = certOut.Bytes() + privateKey = keyOut.Bytes() + + return cert, privateKey, nil +} diff --git a/vendor/github.com/hashicorp/go-plugin/mux_broker.go b/vendor/github.com/hashicorp/go-plugin/mux_broker.go new file mode 100644 index 00000000000..01c45ad7c68 --- /dev/null +++ b/vendor/github.com/hashicorp/go-plugin/mux_broker.go @@ -0,0 +1,204 @@ +package plugin + +import ( + "encoding/binary" + "fmt" + "log" + "net" + "sync" + "sync/atomic" + "time" + + "github.com/hashicorp/yamux" +) + +// MuxBroker is responsible for brokering multiplexed connections by unique ID. +// +// It is used by plugins to multiplex multiple RPC connections and data +// streams on top of a single connection between the plugin process and the +// host process. +// +// This allows a plugin to request a channel with a specific ID to connect to +// or accept a connection from, and the broker handles the details of +// holding these channels open while they're being negotiated. +// +// The Plugin interface has access to these for both Server and Client. +// The broker can be used by either (optionally) to reserve and connect to +// new multiplexed streams. This is useful for complex args and return values, +// or anything else you might need a data stream for. +type MuxBroker struct { + nextId uint32 + session *yamux.Session + streams map[uint32]*muxBrokerPending + + sync.Mutex +} + +type muxBrokerPending struct { + ch chan net.Conn + doneCh chan struct{} +} + +func newMuxBroker(s *yamux.Session) *MuxBroker { + return &MuxBroker{ + session: s, + streams: make(map[uint32]*muxBrokerPending), + } +} + +// Accept accepts a connection by ID. +// +// This should not be called multiple times with the same ID at one time. +func (m *MuxBroker) Accept(id uint32) (net.Conn, error) { + var c net.Conn + p := m.getStream(id) + select { + case c = <-p.ch: + close(p.doneCh) + case <-time.After(5 * time.Second): + m.Lock() + defer m.Unlock() + delete(m.streams, id) + + return nil, fmt.Errorf("timeout waiting for accept") + } + + // Ack our connection + if err := binary.Write(c, binary.LittleEndian, id); err != nil { + c.Close() + return nil, err + } + + return c, nil +} + +// AcceptAndServe is used to accept a specific stream ID and immediately +// serve an RPC server on that stream ID. This is used to easily serve +// complex arguments. +// +// The served interface is always registered to the "Plugin" name. +func (m *MuxBroker) AcceptAndServe(id uint32, v interface{}) { + conn, err := m.Accept(id) + if err != nil { + log.Printf("[ERR] plugin: plugin acceptAndServe error: %s", err) + return + } + + serve(conn, "Plugin", v) +} + +// Close closes the connection and all sub-connections. +func (m *MuxBroker) Close() error { + return m.session.Close() +} + +// Dial opens a connection by ID. +func (m *MuxBroker) Dial(id uint32) (net.Conn, error) { + // Open the stream + stream, err := m.session.OpenStream() + if err != nil { + return nil, err + } + + // Write the stream ID onto the wire. + if err := binary.Write(stream, binary.LittleEndian, id); err != nil { + stream.Close() + return nil, err + } + + // Read the ack that we connected. Then we're off! + var ack uint32 + if err := binary.Read(stream, binary.LittleEndian, &ack); err != nil { + stream.Close() + return nil, err + } + if ack != id { + stream.Close() + return nil, fmt.Errorf("bad ack: %d (expected %d)", ack, id) + } + + return stream, nil +} + +// NextId returns a unique ID to use next. +// +// It is possible for very long-running plugin hosts to wrap this value, +// though it would require a very large amount of RPC calls. In practice +// we've never seen it happen. +func (m *MuxBroker) NextId() uint32 { + return atomic.AddUint32(&m.nextId, 1) +} + +// Run starts the brokering and should be executed in a goroutine, since it +// blocks forever, or until the session closes. +// +// Uses of MuxBroker never need to call this. It is called internally by +// the plugin host/client. +func (m *MuxBroker) Run() { + for { + stream, err := m.session.AcceptStream() + if err != nil { + // Once we receive an error, just exit + break + } + + // Read the stream ID from the stream + var id uint32 + if err := binary.Read(stream, binary.LittleEndian, &id); err != nil { + stream.Close() + continue + } + + // Initialize the waiter + p := m.getStream(id) + select { + case p.ch <- stream: + default: + } + + // Wait for a timeout + go m.timeoutWait(id, p) + } +} + +func (m *MuxBroker) getStream(id uint32) *muxBrokerPending { + m.Lock() + defer m.Unlock() + + p, ok := m.streams[id] + if ok { + return p + } + + m.streams[id] = &muxBrokerPending{ + ch: make(chan net.Conn, 1), + doneCh: make(chan struct{}), + } + return m.streams[id] +} + +func (m *MuxBroker) timeoutWait(id uint32, p *muxBrokerPending) { + // Wait for the stream to either be picked up and connected, or + // for a timeout. + timeout := false + select { + case <-p.doneCh: + case <-time.After(5 * time.Second): + timeout = true + } + + m.Lock() + defer m.Unlock() + + // Delete the stream so no one else can grab it + delete(m.streams, id) + + // If we timed out, then check if we have a channel in the buffer, + // and if so, close it. + if timeout { + select { + case s := <-p.ch: + s.Close() + } + } +} diff --git a/vendor/github.com/hashicorp/go-plugin/plugin.go b/vendor/github.com/hashicorp/go-plugin/plugin.go new file mode 100644 index 00000000000..79d9674633a --- /dev/null +++ b/vendor/github.com/hashicorp/go-plugin/plugin.go @@ -0,0 +1,58 @@ +// The plugin package exposes functions and helpers for communicating to +// plugins which are implemented as standalone binary applications. +// +// plugin.Client fully manages the lifecycle of executing the application, +// connecting to it, and returning the RPC client for dispensing plugins. +// +// plugin.Serve fully manages listeners to expose an RPC server from a binary +// that plugin.Client can connect to. +package plugin + +import ( + "context" + "errors" + "net/rpc" + + "google.golang.org/grpc" +) + +// Plugin is the interface that is implemented to serve/connect to an +// inteface implementation. +type Plugin interface { + // Server should return the RPC server compatible struct to serve + // the methods that the Client calls over net/rpc. + Server(*MuxBroker) (interface{}, error) + + // Client returns an interface implementation for the plugin you're + // serving that communicates to the server end of the plugin. + Client(*MuxBroker, *rpc.Client) (interface{}, error) +} + +// GRPCPlugin is the interface that is implemented to serve/connect to +// a plugin over gRPC. +type GRPCPlugin interface { + // GRPCServer should register this plugin for serving with the + // given GRPCServer. Unlike Plugin.Server, this is only called once + // since gRPC plugins serve singletons. + GRPCServer(*GRPCBroker, *grpc.Server) error + + // GRPCClient should return the interface implementation for the plugin + // you're serving via gRPC. The provided context will be canceled by + // go-plugin in the event of the plugin process exiting. + GRPCClient(context.Context, *GRPCBroker, *grpc.ClientConn) (interface{}, error) +} + +// NetRPCUnsupportedPlugin implements Plugin but returns errors for the +// Server and Client functions. This will effectively disable support for +// net/rpc based plugins. +// +// This struct can be embedded in your struct. +type NetRPCUnsupportedPlugin struct{} + +func (p NetRPCUnsupportedPlugin) Server(*MuxBroker) (interface{}, error) { + return nil, errors.New("net/rpc plugin protocol not supported") +} + +func (p NetRPCUnsupportedPlugin) Client(*MuxBroker, *rpc.Client) (interface{}, error) { + return nil, errors.New("net/rpc plugin protocol not supported") +} diff --git a/vendor/github.com/hashicorp/go-plugin/process.go b/vendor/github.com/hashicorp/go-plugin/process.go new file mode 100644 index 00000000000..88c999a580d --- /dev/null +++ b/vendor/github.com/hashicorp/go-plugin/process.go @@ -0,0 +1,24 @@ +package plugin + +import ( + "time" +) + +// pidAlive checks whether a pid is alive. +func pidAlive(pid int) bool { + return _pidAlive(pid) +} + +// pidWait blocks for a process to exit. +func pidWait(pid int) error { + ticker := time.NewTicker(1 * time.Second) + defer ticker.Stop() + + for range ticker.C { + if !pidAlive(pid) { + break + } + } + + return nil +} diff --git a/vendor/github.com/hashicorp/go-plugin/process_posix.go b/vendor/github.com/hashicorp/go-plugin/process_posix.go new file mode 100644 index 00000000000..70ba546bf6d --- /dev/null +++ b/vendor/github.com/hashicorp/go-plugin/process_posix.go @@ -0,0 +1,19 @@ +// +build !windows + +package plugin + +import ( + "os" + "syscall" +) + +// _pidAlive tests whether a process is alive or not by sending it Signal 0, +// since Go otherwise has no way to test this. +func _pidAlive(pid int) bool { + proc, err := os.FindProcess(pid) + if err == nil { + err = proc.Signal(syscall.Signal(0)) + } + + return err == nil +} diff --git a/vendor/github.com/hashicorp/go-plugin/process_windows.go b/vendor/github.com/hashicorp/go-plugin/process_windows.go new file mode 100644 index 00000000000..9f7b0180901 --- /dev/null +++ b/vendor/github.com/hashicorp/go-plugin/process_windows.go @@ -0,0 +1,29 @@ +package plugin + +import ( + "syscall" +) + +const ( + // Weird name but matches the MSDN docs + exit_STILL_ACTIVE = 259 + + processDesiredAccess = syscall.STANDARD_RIGHTS_READ | + syscall.PROCESS_QUERY_INFORMATION | + syscall.SYNCHRONIZE +) + +// _pidAlive tests whether a process is alive or not +func _pidAlive(pid int) bool { + h, err := syscall.OpenProcess(processDesiredAccess, false, uint32(pid)) + if err != nil { + return false + } + + var ec uint32 + if e := syscall.GetExitCodeProcess(h, &ec); e != nil { + return false + } + + return ec == exit_STILL_ACTIVE +} diff --git a/vendor/github.com/hashicorp/go-plugin/protocol.go b/vendor/github.com/hashicorp/go-plugin/protocol.go new file mode 100644 index 00000000000..0cfc19e52d6 --- /dev/null +++ b/vendor/github.com/hashicorp/go-plugin/protocol.go @@ -0,0 +1,45 @@ +package plugin + +import ( + "io" + "net" +) + +// Protocol is an enum representing the types of protocols. +type Protocol string + +const ( + ProtocolInvalid Protocol = "" + ProtocolNetRPC Protocol = "netrpc" + ProtocolGRPC Protocol = "grpc" +) + +// ServerProtocol is an interface that must be implemented for new plugin +// protocols to be servers. +type ServerProtocol interface { + // Init is called once to configure and initialize the protocol, but + // not start listening. This is the point at which all validation should + // be done and errors returned. + Init() error + + // Config is extra configuration to be outputted to stdout. This will + // be automatically base64 encoded to ensure it can be parsed properly. + // This can be an empty string if additional configuration is not needed. + Config() string + + // Serve is called to serve connections on the given listener. This should + // continue until the listener is closed. + Serve(net.Listener) +} + +// ClientProtocol is an interface that must be implemented for new plugin +// protocols to be clients. +type ClientProtocol interface { + io.Closer + + // Dispense dispenses a new instance of the plugin with the given name. + Dispense(string) (interface{}, error) + + // Ping checks that the client connection is still healthy. + Ping() error +} diff --git a/vendor/github.com/hashicorp/go-plugin/rpc_client.go b/vendor/github.com/hashicorp/go-plugin/rpc_client.go new file mode 100644 index 00000000000..f30a4b1d387 --- /dev/null +++ b/vendor/github.com/hashicorp/go-plugin/rpc_client.go @@ -0,0 +1,170 @@ +package plugin + +import ( + "crypto/tls" + "fmt" + "io" + "net" + "net/rpc" + + "github.com/hashicorp/yamux" +) + +// RPCClient connects to an RPCServer over net/rpc to dispense plugin types. +type RPCClient struct { + broker *MuxBroker + control *rpc.Client + plugins map[string]Plugin + + // These are the streams used for the various stdout/err overrides + stdout, stderr net.Conn +} + +// newRPCClient creates a new RPCClient. The Client argument is expected +// to be successfully started already with a lock held. +func newRPCClient(c *Client) (*RPCClient, error) { + // Connect to the client + conn, err := net.Dial(c.address.Network(), c.address.String()) + if err != nil { + return nil, err + } + if tcpConn, ok := conn.(*net.TCPConn); ok { + // Make sure to set keep alive so that the connection doesn't die + tcpConn.SetKeepAlive(true) + } + + if c.config.TLSConfig != nil { + conn = tls.Client(conn, c.config.TLSConfig) + } + + // Create the actual RPC client + result, err := NewRPCClient(conn, c.config.Plugins) + if err != nil { + conn.Close() + return nil, err + } + + // Begin the stream syncing so that stdin, out, err work properly + err = result.SyncStreams( + c.config.SyncStdout, + c.config.SyncStderr) + if err != nil { + result.Close() + return nil, err + } + + return result, nil +} + +// NewRPCClient creates a client from an already-open connection-like value. +// Dial is typically used instead. +func NewRPCClient(conn io.ReadWriteCloser, plugins map[string]Plugin) (*RPCClient, error) { + // Create the yamux client so we can multiplex + mux, err := yamux.Client(conn, nil) + if err != nil { + conn.Close() + return nil, err + } + + // Connect to the control stream. + control, err := mux.Open() + if err != nil { + mux.Close() + return nil, err + } + + // Connect stdout, stderr streams + stdstream := make([]net.Conn, 2) + for i, _ := range stdstream { + stdstream[i], err = mux.Open() + if err != nil { + mux.Close() + return nil, err + } + } + + // Create the broker and start it up + broker := newMuxBroker(mux) + go broker.Run() + + // Build the client using our broker and control channel. + return &RPCClient{ + broker: broker, + control: rpc.NewClient(control), + plugins: plugins, + stdout: stdstream[0], + stderr: stdstream[1], + }, nil +} + +// SyncStreams should be called to enable syncing of stdout, +// stderr with the plugin. +// +// This will return immediately and the syncing will continue to happen +// in the background. You do not need to launch this in a goroutine itself. +// +// This should never be called multiple times. +func (c *RPCClient) SyncStreams(stdout io.Writer, stderr io.Writer) error { + go copyStream("stdout", stdout, c.stdout) + go copyStream("stderr", stderr, c.stderr) + return nil +} + +// Close closes the connection. The client is no longer usable after this +// is called. +func (c *RPCClient) Close() error { + // Call the control channel and ask it to gracefully exit. If this + // errors, then we save it so that we always return an error but we + // want to try to close the other channels anyways. + var empty struct{} + returnErr := c.control.Call("Control.Quit", true, &empty) + + // Close the other streams we have + if err := c.control.Close(); err != nil { + return err + } + if err := c.stdout.Close(); err != nil { + return err + } + if err := c.stderr.Close(); err != nil { + return err + } + if err := c.broker.Close(); err != nil { + return err + } + + // Return back the error we got from Control.Quit. This is very important + // since we MUST return non-nil error if this fails so that Client.Kill + // will properly try a process.Kill. + return returnErr +} + +func (c *RPCClient) Dispense(name string) (interface{}, error) { + p, ok := c.plugins[name] + if !ok { + return nil, fmt.Errorf("unknown plugin type: %s", name) + } + + var id uint32 + if err := c.control.Call( + "Dispenser.Dispense", name, &id); err != nil { + return nil, err + } + + conn, err := c.broker.Dial(id) + if err != nil { + return nil, err + } + + return p.Client(c.broker, rpc.NewClient(conn)) +} + +// Ping pings the connection to ensure it is still alive. +// +// The error from the RPC call is returned exactly if you want to inspect +// it for further error analysis. Any error returned from here would indicate +// that the connection to the plugin is not healthy. +func (c *RPCClient) Ping() error { + var empty struct{} + return c.control.Call("Control.Ping", true, &empty) +} diff --git a/vendor/github.com/hashicorp/go-plugin/rpc_server.go b/vendor/github.com/hashicorp/go-plugin/rpc_server.go new file mode 100644 index 00000000000..5bb18dd5db1 --- /dev/null +++ b/vendor/github.com/hashicorp/go-plugin/rpc_server.go @@ -0,0 +1,197 @@ +package plugin + +import ( + "errors" + "fmt" + "io" + "log" + "net" + "net/rpc" + "sync" + + "github.com/hashicorp/yamux" +) + +// RPCServer listens for network connections and then dispenses interface +// implementations over net/rpc. +// +// After setting the fields below, they shouldn't be read again directly +// from the structure which may be reading/writing them concurrently. +type RPCServer struct { + Plugins map[string]Plugin + + // Stdout, Stderr are what this server will use instead of the + // normal stdin/out/err. This is because due to the multi-process nature + // of our plugin system, we can't use the normal process values so we + // make our own custom one we pipe across. + Stdout io.Reader + Stderr io.Reader + + // DoneCh should be set to a non-nil channel that will be closed + // when the control requests the RPC server to end. + DoneCh chan<- struct{} + + lock sync.Mutex +} + +// ServerProtocol impl. +func (s *RPCServer) Init() error { return nil } + +// ServerProtocol impl. +func (s *RPCServer) Config() string { return "" } + +// ServerProtocol impl. +func (s *RPCServer) Serve(lis net.Listener) { + for { + conn, err := lis.Accept() + if err != nil { + log.Printf("[ERR] plugin: plugin server: %s", err) + return + } + + go s.ServeConn(conn) + } +} + +// ServeConn runs a single connection. +// +// ServeConn blocks, serving the connection until the client hangs up. +func (s *RPCServer) ServeConn(conn io.ReadWriteCloser) { + // First create the yamux server to wrap this connection + mux, err := yamux.Server(conn, nil) + if err != nil { + conn.Close() + log.Printf("[ERR] plugin: error creating yamux server: %s", err) + return + } + + // Accept the control connection + control, err := mux.Accept() + if err != nil { + mux.Close() + if err != io.EOF { + log.Printf("[ERR] plugin: error accepting control connection: %s", err) + } + + return + } + + // Connect the stdstreams (in, out, err) + stdstream := make([]net.Conn, 2) + for i, _ := range stdstream { + stdstream[i], err = mux.Accept() + if err != nil { + mux.Close() + log.Printf("[ERR] plugin: accepting stream %d: %s", i, err) + return + } + } + + // Copy std streams out to the proper place + go copyStream("stdout", stdstream[0], s.Stdout) + go copyStream("stderr", stdstream[1], s.Stderr) + + // Create the broker and start it up + broker := newMuxBroker(mux) + go broker.Run() + + // Use the control connection to build the dispenser and serve the + // connection. + server := rpc.NewServer() + server.RegisterName("Control", &controlServer{ + server: s, + }) + server.RegisterName("Dispenser", &dispenseServer{ + broker: broker, + plugins: s.Plugins, + }) + server.ServeConn(control) +} + +// done is called internally by the control server to trigger the +// doneCh to close which is listened to by the main process to cleanly +// exit. +func (s *RPCServer) done() { + s.lock.Lock() + defer s.lock.Unlock() + + if s.DoneCh != nil { + close(s.DoneCh) + s.DoneCh = nil + } +} + +// dispenseServer dispenses variousinterface implementations for Terraform. +type controlServer struct { + server *RPCServer +} + +// Ping can be called to verify the connection (and likely the binary) +// is still alive to a plugin. +func (c *controlServer) Ping( + null bool, response *struct{}) error { + *response = struct{}{} + return nil +} + +func (c *controlServer) Quit( + null bool, response *struct{}) error { + // End the server + c.server.done() + + // Always return true + *response = struct{}{} + + return nil +} + +// dispenseServer dispenses variousinterface implementations for Terraform. +type dispenseServer struct { + broker *MuxBroker + plugins map[string]Plugin +} + +func (d *dispenseServer) Dispense( + name string, response *uint32) error { + // Find the function to create this implementation + p, ok := d.plugins[name] + if !ok { + return fmt.Errorf("unknown plugin type: %s", name) + } + + // Create the implementation first so we know if there is an error. + impl, err := p.Server(d.broker) + if err != nil { + // We turn the error into an errors error so that it works across RPC + return errors.New(err.Error()) + } + + // Reserve an ID for our implementation + id := d.broker.NextId() + *response = id + + // Run the rest in a goroutine since it can only happen once this RPC + // call returns. We wait for a connection for the plugin implementation + // and serve it. + go func() { + conn, err := d.broker.Accept(id) + if err != nil { + log.Printf("[ERR] go-plugin: plugin dispense error: %s: %s", name, err) + return + } + + serve(conn, "Plugin", impl) + }() + + return nil +} + +func serve(conn io.ReadWriteCloser, name string, v interface{}) { + server := rpc.NewServer() + if err := server.RegisterName(name, v); err != nil { + log.Printf("[ERR] go-plugin: plugin dispense error: %s", err) + return + } + + server.ServeConn(conn) +} diff --git a/vendor/github.com/hashicorp/go-plugin/server.go b/vendor/github.com/hashicorp/go-plugin/server.go new file mode 100644 index 00000000000..80f0ac396a4 --- /dev/null +++ b/vendor/github.com/hashicorp/go-plugin/server.go @@ -0,0 +1,588 @@ +package plugin + +import ( + "context" + "crypto/tls" + "crypto/x509" + "encoding/base64" + "errors" + "fmt" + "io" + "io/ioutil" + "net" + "os" + "os/signal" + "runtime" + "sort" + "strconv" + "strings" + + hclog "github.com/hashicorp/go-hclog" + "google.golang.org/grpc" +) + +// CoreProtocolVersion is the ProtocolVersion of the plugin system itself. +// We will increment this whenever we change any protocol behavior. This +// will invalidate any prior plugins but will at least allow us to iterate +// on the core in a safe way. We will do our best to do this very +// infrequently. +const CoreProtocolVersion = 1 + +// HandshakeConfig is the configuration used by client and servers to +// handshake before starting a plugin connection. This is embedded by +// both ServeConfig and ClientConfig. +// +// In practice, the plugin host creates a HandshakeConfig that is exported +// and plugins then can easily consume it. +type HandshakeConfig struct { + // ProtocolVersion is the version that clients must match on to + // agree they can communicate. This should match the ProtocolVersion + // set on ClientConfig when using a plugin. + // This field is not required if VersionedPlugins are being used in the + // Client or Server configurations. + ProtocolVersion uint + + // MagicCookieKey and value are used as a very basic verification + // that a plugin is intended to be launched. This is not a security + // measure, just a UX feature. If the magic cookie doesn't match, + // we show human-friendly output. + MagicCookieKey string + MagicCookieValue string +} + +// PluginSet is a set of plugins provided to be registered in the plugin +// server. +type PluginSet map[string]Plugin + +// ServeConfig configures what sorts of plugins are served. +type ServeConfig struct { + // HandshakeConfig is the configuration that must match clients. + HandshakeConfig + + // TLSProvider is a function that returns a configured tls.Config. + TLSProvider func() (*tls.Config, error) + + // Plugins are the plugins that are served. + // The implied version of this PluginSet is the Handshake.ProtocolVersion. + Plugins PluginSet + + // VersionedPlugins is a map of PluginSets for specific protocol versions. + // These can be used to negotiate a compatible version between client and + // server. If this is set, Handshake.ProtocolVersion is not required. + VersionedPlugins map[int]PluginSet + + // GRPCServer should be non-nil to enable serving the plugins over + // gRPC. This is a function to create the server when needed with the + // given server options. The server options populated by go-plugin will + // be for TLS if set. You may modify the input slice. + // + // Note that the grpc.Server will automatically be registered with + // the gRPC health checking service. This is not optional since go-plugin + // relies on this to implement Ping(). + GRPCServer func([]grpc.ServerOption) *grpc.Server + + // Logger is used to pass a logger into the server. If none is provided the + // server will create a default logger. + Logger hclog.Logger + + // Test, if non-nil, will put plugin serving into "test mode". This is + // meant to be used as part of `go test` within a plugin's codebase to + // launch the plugin in-process and output a ReattachConfig. + // + // This changes the behavior of the server in a number of ways to + // accomodate the expectation of running in-process: + // + // * The handshake cookie is not validated. + // * Stdout/stderr will receive plugin reads and writes + // * Connection information will not be sent to stdout + // + Test *ServeTestConfig +} + +// ServeTestConfig configures plugin serving for test mode. See ServeConfig.Test. +type ServeTestConfig struct { + // Context, if set, will force the plugin serving to end when cancelled. + // This is only a test configuration because the non-test configuration + // expects to take over the process and therefore end on an interrupt or + // kill signal. For tests, we need to kill the plugin serving routinely + // and this provides a way to do so. + // + // If you want to wait for the plugin process to close before moving on, + // you can wait on CloseCh. + Context context.Context + + // If this channel is non-nil, we will send the ReattachConfig via + // this channel. This can be encoded (via JSON recommended) to the + // plugin client to attach to this plugin. + ReattachConfigCh chan<- *ReattachConfig + + // CloseCh, if non-nil, will be closed when serving exits. This can be + // used along with Context to determine when the server is fully shut down. + // If this is not set, you can still use Context on its own, but note there + // may be a period of time between canceling the context and the plugin + // server being shut down. + CloseCh chan<- struct{} + + // SyncStdio, if true, will enable the client side "SyncStdout/Stderr" + // functionality to work. This defaults to false because the implementation + // of making this work within test environments is particularly messy + // and SyncStdio functionality is fairly rare, so we default to the simple + // scenario. + SyncStdio bool +} + +// protocolVersion determines the protocol version and plugin set to be used by +// the server. In the event that there is no suitable version, the last version +// in the config is returned leaving the client to report the incompatibility. +func protocolVersion(opts *ServeConfig) (int, Protocol, PluginSet) { + protoVersion := int(opts.ProtocolVersion) + pluginSet := opts.Plugins + protoType := ProtocolNetRPC + // Check if the client sent a list of acceptable versions + var clientVersions []int + if vs := os.Getenv("PLUGIN_PROTOCOL_VERSIONS"); vs != "" { + for _, s := range strings.Split(vs, ",") { + v, err := strconv.Atoi(s) + if err != nil { + fmt.Fprintf(os.Stderr, "server sent invalid plugin version %q", s) + continue + } + clientVersions = append(clientVersions, v) + } + } + + // We want to iterate in reverse order, to ensure we match the newest + // compatible plugin version. + sort.Sort(sort.Reverse(sort.IntSlice(clientVersions))) + + // set the old un-versioned fields as if they were versioned plugins + if opts.VersionedPlugins == nil { + opts.VersionedPlugins = make(map[int]PluginSet) + } + + if pluginSet != nil { + opts.VersionedPlugins[protoVersion] = pluginSet + } + + // Sort the version to make sure we match the latest first + var versions []int + for v := range opts.VersionedPlugins { + versions = append(versions, v) + } + + sort.Sort(sort.Reverse(sort.IntSlice(versions))) + + // See if we have multiple versions of Plugins to choose from + for _, version := range versions { + // Record each version, since we guarantee that this returns valid + // values even if they are not a protocol match. + protoVersion = version + pluginSet = opts.VersionedPlugins[version] + + // If we have a configured gRPC server we should select a protocol + if opts.GRPCServer != nil { + // All plugins in a set must use the same transport, so check the first + // for the protocol type + for _, p := range pluginSet { + switch p.(type) { + case GRPCPlugin: + protoType = ProtocolGRPC + default: + protoType = ProtocolNetRPC + } + break + } + } + + for _, clientVersion := range clientVersions { + if clientVersion == protoVersion { + return protoVersion, protoType, pluginSet + } + } + } + + // Return the lowest version as the fallback. + // Since we iterated over all the versions in reverse order above, these + // values are from the lowest version number plugins (which may be from + // a combination of the Handshake.ProtocolVersion and ServeConfig.Plugins + // fields). This allows serving the oldest version of our plugins to a + // legacy client that did not send a PLUGIN_PROTOCOL_VERSIONS list. + return protoVersion, protoType, pluginSet +} + +// Serve serves the plugins given by ServeConfig. +// +// Serve doesn't return until the plugin is done being executed. Any +// fixable errors will be output to os.Stderr and the process will +// exit with a status code of 1. Serve will panic for unexpected +// conditions where a user's fix is unknown. +// +// This is the method that plugins should call in their main() functions. +func Serve(opts *ServeConfig) { + exitCode := -1 + // We use this to trigger an `os.Exit` so that we can execute our other + // deferred functions. In test mode, we just output the err to stderr + // and return. + defer func() { + if opts.Test == nil && exitCode >= 0 { + os.Exit(exitCode) + } + + if opts.Test != nil && opts.Test.CloseCh != nil { + close(opts.Test.CloseCh) + } + }() + + if opts.Test == nil { + // Validate the handshake config + if opts.MagicCookieKey == "" || opts.MagicCookieValue == "" { + fmt.Fprintf(os.Stderr, + "Misconfigured ServeConfig given to serve this plugin: no magic cookie\n"+ + "key or value was set. Please notify the plugin author and report\n"+ + "this as a bug.\n") + exitCode = 1 + return + } + + // First check the cookie + if os.Getenv(opts.MagicCookieKey) != opts.MagicCookieValue { + fmt.Fprintf(os.Stderr, + "This binary is a plugin. These are not meant to be executed directly.\n"+ + "Please execute the program that consumes these plugins, which will\n"+ + "load any plugins automatically\n") + exitCode = 1 + return + } + } + + // negotiate the version and plugins + // start with default version in the handshake config + protoVersion, protoType, pluginSet := protocolVersion(opts) + + logger := opts.Logger + if logger == nil { + // internal logger to os.Stderr + logger = hclog.New(&hclog.LoggerOptions{ + Level: hclog.Trace, + Output: os.Stderr, + JSONFormat: true, + }) + } + + // Register a listener so we can accept a connection + listener, err := serverListener() + if err != nil { + logger.Error("plugin init error", "error", err) + return + } + + // Close the listener on return. We wrap this in a func() on purpose + // because the "listener" reference may change to TLS. + defer func() { + listener.Close() + }() + + var tlsConfig *tls.Config + if opts.TLSProvider != nil { + tlsConfig, err = opts.TLSProvider() + if err != nil { + logger.Error("plugin tls init", "error", err) + return + } + } + + var serverCert string + clientCert := os.Getenv("PLUGIN_CLIENT_CERT") + // If the client is configured using AutoMTLS, the certificate will be here, + // and we need to generate our own in response. + if tlsConfig == nil && clientCert != "" { + logger.Info("configuring server automatic mTLS") + clientCertPool := x509.NewCertPool() + if !clientCertPool.AppendCertsFromPEM([]byte(clientCert)) { + logger.Error("client cert provided but failed to parse", "cert", clientCert) + } + + certPEM, keyPEM, err := generateCert() + if err != nil { + logger.Error("failed to generate client certificate", "error", err) + panic(err) + } + + cert, err := tls.X509KeyPair(certPEM, keyPEM) + if err != nil { + logger.Error("failed to parse client certificate", "error", err) + panic(err) + } + + tlsConfig = &tls.Config{ + Certificates: []tls.Certificate{cert}, + ClientAuth: tls.RequireAndVerifyClientCert, + ClientCAs: clientCertPool, + MinVersion: tls.VersionTLS12, + } + + // We send back the raw leaf cert data for the client rather than the + // PEM, since the protocol can't handle newlines. + serverCert = base64.RawStdEncoding.EncodeToString(cert.Certificate[0]) + } + + // Create the channel to tell us when we're done + doneCh := make(chan struct{}) + + // Create our new stdout, stderr files. These will override our built-in + // stdout/stderr so that it works across the stream boundary. + var stdout_r, stderr_r io.Reader + stdout_r, stdout_w, err := os.Pipe() + if err != nil { + fmt.Fprintf(os.Stderr, "Error preparing plugin: %s\n", err) + os.Exit(1) + } + stderr_r, stderr_w, err := os.Pipe() + if err != nil { + fmt.Fprintf(os.Stderr, "Error preparing plugin: %s\n", err) + os.Exit(1) + } + + // If we're in test mode, we tee off the reader and write the data + // as-is to our normal Stdout and Stderr so that they continue working + // while stdio works. This is because in test mode, we assume we're running + // in `go test` or some equivalent and we want output to go to standard + // locations. + if opts.Test != nil { + // TODO(mitchellh): This isn't super ideal because a TeeReader + // only works if the reader side is actively read. If we never + // connect via a plugin client, the output still gets swallowed. + stdout_r = io.TeeReader(stdout_r, os.Stdout) + stderr_r = io.TeeReader(stderr_r, os.Stderr) + } + + // Build the server type + var server ServerProtocol + switch protoType { + case ProtocolNetRPC: + // If we have a TLS configuration then we wrap the listener + // ourselves and do it at that level. + if tlsConfig != nil { + listener = tls.NewListener(listener, tlsConfig) + } + + // Create the RPC server to dispense + server = &RPCServer{ + Plugins: pluginSet, + Stdout: stdout_r, + Stderr: stderr_r, + DoneCh: doneCh, + } + + case ProtocolGRPC: + // Create the gRPC server + server = &GRPCServer{ + Plugins: pluginSet, + Server: opts.GRPCServer, + TLS: tlsConfig, + Stdout: stdout_r, + Stderr: stderr_r, + DoneCh: doneCh, + logger: logger, + } + + default: + panic("unknown server protocol: " + protoType) + } + + // Initialize the servers + if err := server.Init(); err != nil { + logger.Error("protocol init", "error", err) + return + } + + logger.Debug("plugin address", "network", listener.Addr().Network(), "address", listener.Addr().String()) + + // Output the address and service name to stdout so that the client can + // bring it up. In test mode, we don't do this because clients will + // attach via a reattach config. + if opts.Test == nil { + fmt.Printf("%d|%d|%s|%s|%s|%s\n", + CoreProtocolVersion, + protoVersion, + listener.Addr().Network(), + listener.Addr().String(), + protoType, + serverCert) + os.Stdout.Sync() + } else if ch := opts.Test.ReattachConfigCh; ch != nil { + // Send back the reattach config that can be used. This isn't + // quite ready if they connect immediately but the client should + // retry a few times. + ch <- &ReattachConfig{ + Protocol: protoType, + Addr: listener.Addr(), + Pid: os.Getpid(), + Test: true, + } + } + + // Eat the interrupts. In test mode we disable this so that go test + // can be cancelled properly. + if opts.Test == nil { + ch := make(chan os.Signal, 1) + signal.Notify(ch, os.Interrupt) + go func() { + count := 0 + for { + <-ch + count++ + logger.Trace("plugin received interrupt signal, ignoring", "count", count) + } + }() + } + + // Set our stdout, stderr to the stdio stream that clients can retrieve + // using ClientConfig.SyncStdout/err. We only do this for non-test mode + // or if the test mode explicitly requests it. + // + // In test mode, we use a multiwriter so that the data continues going + // to the normal stdout/stderr so output can show up in test logs. We + // also send to the stdio stream so that clients can continue working + // if they depend on that. + if opts.Test == nil || opts.Test.SyncStdio { + if opts.Test != nil { + // In test mode we need to maintain the original values so we can + // reset it. + defer func(out, err *os.File) { + os.Stdout = out + os.Stderr = err + }(os.Stdout, os.Stderr) + } + os.Stdout = stdout_w + os.Stderr = stderr_w + } + + // Accept connections and wait for completion + go server.Serve(listener) + + ctx := context.Background() + if opts.Test != nil && opts.Test.Context != nil { + ctx = opts.Test.Context + } + select { + case <-ctx.Done(): + // Cancellation. We can stop the server by closing the listener. + // This isn't graceful at all but this is currently only used by + // tests and its our only way to stop. + listener.Close() + + // If this is a grpc server, then we also ask the server itself to + // end which will kill all connections. There isn't an easy way to do + // this for net/rpc currently but net/rpc is more and more unused. + if s, ok := server.(*GRPCServer); ok { + s.Stop() + } + + // Wait for the server itself to shut down + <-doneCh + + case <-doneCh: + // Note that given the documentation of Serve we should probably be + // setting exitCode = 0 and using os.Exit here. That's how it used to + // work before extracting this library. However, for years we've done + // this so we'll keep this functionality. + } +} + +func serverListener() (net.Listener, error) { + if runtime.GOOS == "windows" { + return serverListener_tcp() + } + + return serverListener_unix() +} + +func serverListener_tcp() (net.Listener, error) { + envMinPort := os.Getenv("PLUGIN_MIN_PORT") + envMaxPort := os.Getenv("PLUGIN_MAX_PORT") + + var minPort, maxPort int64 + var err error + + switch { + case len(envMinPort) == 0: + minPort = 0 + default: + minPort, err = strconv.ParseInt(envMinPort, 10, 32) + if err != nil { + return nil, fmt.Errorf("Couldn't get value from PLUGIN_MIN_PORT: %v", err) + } + } + + switch { + case len(envMaxPort) == 0: + maxPort = 0 + default: + maxPort, err = strconv.ParseInt(envMaxPort, 10, 32) + if err != nil { + return nil, fmt.Errorf("Couldn't get value from PLUGIN_MAX_PORT: %v", err) + } + } + + if minPort > maxPort { + return nil, fmt.Errorf("PLUGIN_MIN_PORT value of %d is greater than PLUGIN_MAX_PORT value of %d", minPort, maxPort) + } + + for port := minPort; port <= maxPort; port++ { + address := fmt.Sprintf("127.0.0.1:%d", port) + listener, err := net.Listen("tcp", address) + if err == nil { + return listener, nil + } + } + + return nil, errors.New("Couldn't bind plugin TCP listener") +} + +func serverListener_unix() (net.Listener, error) { + tf, err := ioutil.TempFile("", "plugin") + if err != nil { + return nil, err + } + path := tf.Name() + + // Close the file and remove it because it has to not exist for + // the domain socket. + if err := tf.Close(); err != nil { + return nil, err + } + if err := os.Remove(path); err != nil { + return nil, err + } + + l, err := net.Listen("unix", path) + if err != nil { + return nil, err + } + + // Wrap the listener in rmListener so that the Unix domain socket file + // is removed on close. + return &rmListener{ + Listener: l, + Path: path, + }, nil +} + +// rmListener is an implementation of net.Listener that forwards most +// calls to the listener but also removes a file as part of the close. We +// use this to cleanup the unix domain socket on close. +type rmListener struct { + net.Listener + Path string +} + +func (l *rmListener) Close() error { + // Close the listener itself + if err := l.Listener.Close(); err != nil { + return err + } + + // Remove the file + return os.Remove(l.Path) +} diff --git a/vendor/github.com/hashicorp/go-plugin/server_mux.go b/vendor/github.com/hashicorp/go-plugin/server_mux.go new file mode 100644 index 00000000000..033079ea0fc --- /dev/null +++ b/vendor/github.com/hashicorp/go-plugin/server_mux.go @@ -0,0 +1,31 @@ +package plugin + +import ( + "fmt" + "os" +) + +// ServeMuxMap is the type that is used to configure ServeMux +type ServeMuxMap map[string]*ServeConfig + +// ServeMux is like Serve, but serves multiple types of plugins determined +// by the argument given on the command-line. +// +// This command doesn't return until the plugin is done being executed. Any +// errors are logged or output to stderr. +func ServeMux(m ServeMuxMap) { + if len(os.Args) != 2 { + fmt.Fprintf(os.Stderr, + "Invoked improperly. This is an internal command that shouldn't\n"+ + "be manually invoked.\n") + os.Exit(1) + } + + opts, ok := m[os.Args[1]] + if !ok { + fmt.Fprintf(os.Stderr, "Unknown plugin: %s\n", os.Args[1]) + os.Exit(1) + } + + Serve(opts) +} diff --git a/vendor/github.com/hashicorp/go-plugin/stream.go b/vendor/github.com/hashicorp/go-plugin/stream.go new file mode 100644 index 00000000000..1d547aaaab3 --- /dev/null +++ b/vendor/github.com/hashicorp/go-plugin/stream.go @@ -0,0 +1,18 @@ +package plugin + +import ( + "io" + "log" +) + +func copyStream(name string, dst io.Writer, src io.Reader) { + if src == nil { + panic(name + ": src is nil") + } + if dst == nil { + panic(name + ": dst is nil") + } + if _, err := io.Copy(dst, src); err != nil && err != io.EOF { + log.Printf("[ERR] plugin: stream copy '%s' error: %s", name, err) + } +} diff --git a/vendor/github.com/hashicorp/go-plugin/testing.go b/vendor/github.com/hashicorp/go-plugin/testing.go new file mode 100644 index 00000000000..e36f2eb2b7c --- /dev/null +++ b/vendor/github.com/hashicorp/go-plugin/testing.go @@ -0,0 +1,180 @@ +package plugin + +import ( + "bytes" + "context" + "io" + "net" + "net/rpc" + + hclog "github.com/hashicorp/go-hclog" + "github.com/hashicorp/go-plugin/internal/plugin" + "github.com/mitchellh/go-testing-interface" + "google.golang.org/grpc" +) + +// TestOptions allows specifying options that can affect the behavior of the +// test functions +type TestOptions struct { + //ServerStdout causes the given value to be used in place of a blank buffer + //for RPCServer's Stdout + ServerStdout io.ReadCloser + + //ServerStderr causes the given value to be used in place of a blank buffer + //for RPCServer's Stderr + ServerStderr io.ReadCloser +} + +// The testing file contains test helpers that you can use outside of +// this package for making it easier to test plugins themselves. + +// TestConn is a helper function for returning a client and server +// net.Conn connected to each other. +func TestConn(t testing.T) (net.Conn, net.Conn) { + // Listen to any local port. This listener will be closed + // after a single connection is established. + l, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + t.Fatalf("err: %s", err) + } + + // Start a goroutine to accept our client connection + var serverConn net.Conn + doneCh := make(chan struct{}) + go func() { + defer close(doneCh) + defer l.Close() + var err error + serverConn, err = l.Accept() + if err != nil { + t.Fatalf("err: %s", err) + } + }() + + // Connect to the server + clientConn, err := net.Dial("tcp", l.Addr().String()) + if err != nil { + t.Fatalf("err: %s", err) + } + + // Wait for the server side to acknowledge it has connected + <-doneCh + + return clientConn, serverConn +} + +// TestRPCConn returns a rpc client and server connected to each other. +func TestRPCConn(t testing.T) (*rpc.Client, *rpc.Server) { + clientConn, serverConn := TestConn(t) + + server := rpc.NewServer() + go server.ServeConn(serverConn) + + client := rpc.NewClient(clientConn) + return client, server +} + +// TestPluginRPCConn returns a plugin RPC client and server that are connected +// together and configured. +func TestPluginRPCConn(t testing.T, ps map[string]Plugin, opts *TestOptions) (*RPCClient, *RPCServer) { + // Create two net.Conns we can use to shuttle our control connection + clientConn, serverConn := TestConn(t) + + // Start up the server + server := &RPCServer{Plugins: ps, Stdout: new(bytes.Buffer), Stderr: new(bytes.Buffer)} + if opts != nil { + if opts.ServerStdout != nil { + server.Stdout = opts.ServerStdout + } + if opts.ServerStderr != nil { + server.Stderr = opts.ServerStderr + } + } + go server.ServeConn(serverConn) + + // Connect the client to the server + client, err := NewRPCClient(clientConn, ps) + if err != nil { + t.Fatalf("err: %s", err) + } + + return client, server +} + +// TestGRPCConn returns a gRPC client conn and grpc server that are connected +// together and configured. The register function is used to register services +// prior to the Serve call. This is used to test gRPC connections. +func TestGRPCConn(t testing.T, register func(*grpc.Server)) (*grpc.ClientConn, *grpc.Server) { + // Create a listener + l, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + t.Fatalf("err: %s", err) + } + + server := grpc.NewServer() + register(server) + go server.Serve(l) + + // Connect to the server + conn, err := grpc.Dial( + l.Addr().String(), + grpc.WithBlock(), + grpc.WithInsecure()) + if err != nil { + t.Fatalf("err: %s", err) + } + + // Connection successful, close the listener + l.Close() + + return conn, server +} + +// TestPluginGRPCConn returns a plugin gRPC client and server that are connected +// together and configured. This is used to test gRPC connections. +func TestPluginGRPCConn(t testing.T, ps map[string]Plugin) (*GRPCClient, *GRPCServer) { + // Create a listener + l, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + t.Fatalf("err: %s", err) + } + + // Start up the server + server := &GRPCServer{ + Plugins: ps, + DoneCh: make(chan struct{}), + Server: DefaultGRPCServer, + Stdout: new(bytes.Buffer), + Stderr: new(bytes.Buffer), + logger: hclog.Default(), + } + if err := server.Init(); err != nil { + t.Fatalf("err: %s", err) + } + go server.Serve(l) + + // Connect to the server + conn, err := grpc.Dial( + l.Addr().String(), + grpc.WithBlock(), + grpc.WithInsecure()) + if err != nil { + t.Fatalf("err: %s", err) + } + + brokerGRPCClient := newGRPCBrokerClient(conn) + broker := newGRPCBroker(brokerGRPCClient, nil) + go broker.Run() + go brokerGRPCClient.StartStream() + + // Create the client + client := &GRPCClient{ + Conn: conn, + Plugins: ps, + broker: broker, + doneCtx: context.Background(), + controller: plugin.NewGRPCControllerClient(conn), + } + + return client, server +} diff --git a/vendor/github.com/hashicorp/yamux/.gitignore b/vendor/github.com/hashicorp/yamux/.gitignore new file mode 100644 index 00000000000..836562412fe --- /dev/null +++ b/vendor/github.com/hashicorp/yamux/.gitignore @@ -0,0 +1,23 @@ +# Compiled Object files, Static and Dynamic libs (Shared Objects) +*.o +*.a +*.so + +# Folders +_obj +_test + +# Architecture specific extensions/prefixes +*.[568vq] +[568vq].out + +*.cgo1.go +*.cgo2.c +_cgo_defun.c +_cgo_gotypes.go +_cgo_export.* + +_testmain.go + +*.exe +*.test diff --git a/vendor/github.com/hashicorp/yamux/LICENSE b/vendor/github.com/hashicorp/yamux/LICENSE new file mode 100644 index 00000000000..f0e5c79e181 --- /dev/null +++ b/vendor/github.com/hashicorp/yamux/LICENSE @@ -0,0 +1,362 @@ +Mozilla Public License, version 2.0 + +1. Definitions + +1.1. "Contributor" + + means each individual or legal entity that creates, contributes to the + creation of, or owns Covered Software. + +1.2. "Contributor Version" + + means the combination of the Contributions of others (if any) used by a + Contributor and that particular Contributor's Contribution. + +1.3. "Contribution" + + means Covered Software of a particular Contributor. + +1.4. "Covered Software" + + means Source Code Form to which the initial Contributor has attached the + notice in Exhibit A, the Executable Form of such Source Code Form, and + Modifications of such Source Code Form, in each case including portions + thereof. + +1.5. "Incompatible With Secondary Licenses" + means + + a. that the initial Contributor has attached the notice described in + Exhibit B to the Covered Software; or + + b. that the Covered Software was made available under the terms of + version 1.1 or earlier of the License, but not also under the terms of + a Secondary License. + +1.6. "Executable Form" + + means any form of the work other than Source Code Form. + +1.7. "Larger Work" + + means a work that combines Covered Software with other material, in a + separate file or files, that is not Covered Software. + +1.8. "License" + + means this document. + +1.9. "Licensable" + + means having the right to grant, to the maximum extent possible, whether + at the time of the initial grant or subsequently, any and all of the + rights conveyed by this License. + +1.10. "Modifications" + + means any of the following: + + a. any file in Source Code Form that results from an addition to, + deletion from, or modification of the contents of Covered Software; or + + b. any new file in Source Code Form that contains any Covered Software. + +1.11. "Patent Claims" of a Contributor + + means any patent claim(s), including without limitation, method, + process, and apparatus claims, in any patent Licensable by such + Contributor that would be infringed, but for the grant of the License, + by the making, using, selling, offering for sale, having made, import, + or transfer of either its Contributions or its Contributor Version. + +1.12. "Secondary License" + + means either the GNU General Public License, Version 2.0, the GNU Lesser + General Public License, Version 2.1, the GNU Affero General Public + License, Version 3.0, or any later versions of those licenses. + +1.13. "Source Code Form" + + means the form of the work preferred for making modifications. + +1.14. "You" (or "Your") + + means an individual or a legal entity exercising rights under this + License. For legal entities, "You" includes any entity that controls, is + controlled by, or is under common control with You. For purposes of this + definition, "control" means (a) the power, direct or indirect, to cause + the direction or management of such entity, whether by contract or + otherwise, or (b) ownership of more than fifty percent (50%) of the + outstanding shares or beneficial ownership of such entity. + + +2. License Grants and Conditions + +2.1. Grants + + Each Contributor hereby grants You a world-wide, royalty-free, + non-exclusive license: + + a. under intellectual property rights (other than patent or trademark) + Licensable by such Contributor to use, reproduce, make available, + modify, display, perform, distribute, and otherwise exploit its + Contributions, either on an unmodified basis, with Modifications, or + as part of a Larger Work; and + + b. under Patent Claims of such Contributor to make, use, sell, offer for + sale, have made, import, and otherwise transfer either its + Contributions or its Contributor Version. + +2.2. Effective Date + + The licenses granted in Section 2.1 with respect to any Contribution + become effective for each Contribution on the date the Contributor first + distributes such Contribution. + +2.3. Limitations on Grant Scope + + The licenses granted in this Section 2 are the only rights granted under + this License. No additional rights or licenses will be implied from the + distribution or licensing of Covered Software under this License. + Notwithstanding Section 2.1(b) above, no patent license is granted by a + Contributor: + + a. for any code that a Contributor has removed from Covered Software; or + + b. for infringements caused by: (i) Your and any other third party's + modifications of Covered Software, or (ii) the combination of its + Contributions with other software (except as part of its Contributor + Version); or + + c. under Patent Claims infringed by Covered Software in the absence of + its Contributions. + + This License does not grant any rights in the trademarks, service marks, + or logos of any Contributor (except as may be necessary to comply with + the notice requirements in Section 3.4). + +2.4. Subsequent Licenses + + No Contributor makes additional grants as a result of Your choice to + distribute the Covered Software under a subsequent version of this + License (see Section 10.2) or under the terms of a Secondary License (if + permitted under the terms of Section 3.3). + +2.5. Representation + + Each Contributor represents that the Contributor believes its + Contributions are its original creation(s) or it has sufficient rights to + grant the rights to its Contributions conveyed by this License. + +2.6. Fair Use + + This License is not intended to limit any rights You have under + applicable copyright doctrines of fair use, fair dealing, or other + equivalents. + +2.7. Conditions + + Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in + Section 2.1. + + +3. Responsibilities + +3.1. Distribution of Source Form + + All distribution of Covered Software in Source Code Form, including any + Modifications that You create or to which You contribute, must be under + the terms of this License. You must inform recipients that the Source + Code Form of the Covered Software is governed by the terms of this + License, and how they can obtain a copy of this License. You may not + attempt to alter or restrict the recipients' rights in the Source Code + Form. + +3.2. Distribution of Executable Form + + If You distribute Covered Software in Executable Form then: + + a. such Covered Software must also be made available in Source Code Form, + as described in Section 3.1, and You must inform recipients of the + Executable Form how they can obtain a copy of such Source Code Form by + reasonable means in a timely manner, at a charge no more than the cost + of distribution to the recipient; and + + b. You may distribute such Executable Form under the terms of this + License, or sublicense it under different terms, provided that the + license for the Executable Form does not attempt to limit or alter the + recipients' rights in the Source Code Form under this License. + +3.3. Distribution of a Larger Work + + You may create and distribute a Larger Work under terms of Your choice, + provided that You also comply with the requirements of this License for + the Covered Software. If the Larger Work is a combination of Covered + Software with a work governed by one or more Secondary Licenses, and the + Covered Software is not Incompatible With Secondary Licenses, this + License permits You to additionally distribute such Covered Software + under the terms of such Secondary License(s), so that the recipient of + the Larger Work may, at their option, further distribute the Covered + Software under the terms of either this License or such Secondary + License(s). + +3.4. Notices + + You may not remove or alter the substance of any license notices + (including copyright notices, patent notices, disclaimers of warranty, or + limitations of liability) contained within the Source Code Form of the + Covered Software, except that You may alter any license notices to the + extent required to remedy known factual inaccuracies. + +3.5. Application of Additional Terms + + You may choose to offer, and to charge a fee for, warranty, support, + indemnity or liability obligations to one or more recipients of Covered + Software. However, You may do so only on Your own behalf, and not on + behalf of any Contributor. You must make it absolutely clear that any + such warranty, support, indemnity, or liability obligation is offered by + You alone, and You hereby agree to indemnify every Contributor for any + liability incurred by such Contributor as a result of warranty, support, + indemnity or liability terms You offer. You may include additional + disclaimers of warranty and limitations of liability specific to any + jurisdiction. + +4. Inability to Comply Due to Statute or Regulation + + If it is impossible for You to comply with any of the terms of this License + with respect to some or all of the Covered Software due to statute, + judicial order, or regulation then You must: (a) comply with the terms of + this License to the maximum extent possible; and (b) describe the + limitations and the code they affect. Such description must be placed in a + text file included with all distributions of the Covered Software under + this License. Except to the extent prohibited by statute or regulation, + such description must be sufficiently detailed for a recipient of ordinary + skill to be able to understand it. + +5. Termination + +5.1. The rights granted under this License will terminate automatically if You + fail to comply with any of its terms. However, if You become compliant, + then the rights granted under this License from a particular Contributor + are reinstated (a) provisionally, unless and until such Contributor + explicitly and finally terminates Your grants, and (b) on an ongoing + basis, if such Contributor fails to notify You of the non-compliance by + some reasonable means prior to 60 days after You have come back into + compliance. Moreover, Your grants from a particular Contributor are + reinstated on an ongoing basis if such Contributor notifies You of the + non-compliance by some reasonable means, this is the first time You have + received notice of non-compliance with this License from such + Contributor, and You become compliant prior to 30 days after Your receipt + of the notice. + +5.2. If You initiate litigation against any entity by asserting a patent + infringement claim (excluding declaratory judgment actions, + counter-claims, and cross-claims) alleging that a Contributor Version + directly or indirectly infringes any patent, then the rights granted to + You by any and all Contributors for the Covered Software under Section + 2.1 of this License shall terminate. + +5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user + license agreements (excluding distributors and resellers) which have been + validly granted by You or Your distributors under this License prior to + termination shall survive termination. + +6. Disclaimer of Warranty + + Covered Software is provided under this License on an "as is" basis, + without warranty of any kind, either expressed, implied, or statutory, + including, without limitation, warranties that the Covered Software is free + of defects, merchantable, fit for a particular purpose or non-infringing. + The entire risk as to the quality and performance of the Covered Software + is with You. Should any Covered Software prove defective in any respect, + You (not any Contributor) assume the cost of any necessary servicing, + repair, or correction. This disclaimer of warranty constitutes an essential + part of this License. No use of any Covered Software is authorized under + this License except under this disclaimer. + +7. Limitation of Liability + + Under no circumstances and under no legal theory, whether tort (including + negligence), contract, or otherwise, shall any Contributor, or anyone who + distributes Covered Software as permitted above, be liable to You for any + direct, indirect, special, incidental, or consequential damages of any + character including, without limitation, damages for lost profits, loss of + goodwill, work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses, even if such party shall have been + informed of the possibility of such damages. This limitation of liability + shall not apply to liability for death or personal injury resulting from + such party's negligence to the extent applicable law prohibits such + limitation. Some jurisdictions do not allow the exclusion or limitation of + incidental or consequential damages, so this exclusion and limitation may + not apply to You. + +8. Litigation + + Any litigation relating to this License may be brought only in the courts + of a jurisdiction where the defendant maintains its principal place of + business and such litigation shall be governed by laws of that + jurisdiction, without reference to its conflict-of-law provisions. Nothing + in this Section shall prevent a party's ability to bring cross-claims or + counter-claims. + +9. Miscellaneous + + This License represents the complete agreement concerning the subject + matter hereof. If any provision of this License is held to be + unenforceable, such provision shall be reformed only to the extent + necessary to make it enforceable. Any law or regulation which provides that + the language of a contract shall be construed against the drafter shall not + be used to construe this License against a Contributor. + + +10. Versions of the License + +10.1. New Versions + + Mozilla Foundation is the license steward. Except as provided in Section + 10.3, no one other than the license steward has the right to modify or + publish new versions of this License. Each version will be given a + distinguishing version number. + +10.2. Effect of New Versions + + You may distribute the Covered Software under the terms of the version + of the License under which You originally received the Covered Software, + or under the terms of any subsequent version published by the license + steward. + +10.3. Modified Versions + + If you create software not governed by this License, and you want to + create a new license for such software, you may create and use a + modified version of this License if you rename the license and remove + any references to the name of the license steward (except to note that + such modified license differs from this License). + +10.4. Distributing Source Code Form that is Incompatible With Secondary + Licenses If You choose to distribute Source Code Form that is + Incompatible With Secondary Licenses under the terms of this version of + the License, the notice described in Exhibit B of this License must be + attached. + +Exhibit A - Source Code Form License Notice + + This Source Code Form is subject to the + terms of the Mozilla Public License, v. + 2.0. If a copy of the MPL was not + distributed with this file, You can + obtain one at + http://mozilla.org/MPL/2.0/. + +If it is not possible or desirable to put the notice in a particular file, +then You may include the notice in a location (such as a LICENSE file in a +relevant directory) where a recipient would be likely to look for such a +notice. + +You may add additional accurate notices of copyright ownership. + +Exhibit B - "Incompatible With Secondary Licenses" Notice + + This Source Code Form is "Incompatible + With Secondary Licenses", as defined by + the Mozilla Public License, v. 2.0. \ No newline at end of file diff --git a/vendor/github.com/hashicorp/yamux/README.md b/vendor/github.com/hashicorp/yamux/README.md new file mode 100644 index 00000000000..d4db7fc99be --- /dev/null +++ b/vendor/github.com/hashicorp/yamux/README.md @@ -0,0 +1,86 @@ +# Yamux + +Yamux (Yet another Multiplexer) is a multiplexing library for Golang. +It relies on an underlying connection to provide reliability +and ordering, such as TCP or Unix domain sockets, and provides +stream-oriented multiplexing. It is inspired by SPDY but is not +interoperable with it. + +Yamux features include: + +* Bi-directional streams + * Streams can be opened by either client or server + * Useful for NAT traversal + * Server-side push support +* Flow control + * Avoid starvation + * Back-pressure to prevent overwhelming a receiver +* Keep Alives + * Enables persistent connections over a load balancer +* Efficient + * Enables thousands of logical streams with low overhead + +## Documentation + +For complete documentation, see the associated [Godoc](http://godoc.org/github.com/hashicorp/yamux). + +## Specification + +The full specification for Yamux is provided in the `spec.md` file. +It can be used as a guide to implementors of interoperable libraries. + +## Usage + +Using Yamux is remarkably simple: + +```go + +func client() { + // Get a TCP connection + conn, err := net.Dial(...) + if err != nil { + panic(err) + } + + // Setup client side of yamux + session, err := yamux.Client(conn, nil) + if err != nil { + panic(err) + } + + // Open a new stream + stream, err := session.Open() + if err != nil { + panic(err) + } + + // Stream implements net.Conn + stream.Write([]byte("ping")) +} + +func server() { + // Accept a TCP connection + conn, err := listener.Accept() + if err != nil { + panic(err) + } + + // Setup server side of yamux + session, err := yamux.Server(conn, nil) + if err != nil { + panic(err) + } + + // Accept a stream + stream, err := session.Accept() + if err != nil { + panic(err) + } + + // Listen for a message + buf := make([]byte, 4) + stream.Read(buf) +} + +``` + diff --git a/vendor/github.com/hashicorp/yamux/addr.go b/vendor/github.com/hashicorp/yamux/addr.go new file mode 100644 index 00000000000..be6ebca9c78 --- /dev/null +++ b/vendor/github.com/hashicorp/yamux/addr.go @@ -0,0 +1,60 @@ +package yamux + +import ( + "fmt" + "net" +) + +// hasAddr is used to get the address from the underlying connection +type hasAddr interface { + LocalAddr() net.Addr + RemoteAddr() net.Addr +} + +// yamuxAddr is used when we cannot get the underlying address +type yamuxAddr struct { + Addr string +} + +func (*yamuxAddr) Network() string { + return "yamux" +} + +func (y *yamuxAddr) String() string { + return fmt.Sprintf("yamux:%s", y.Addr) +} + +// Addr is used to get the address of the listener. +func (s *Session) Addr() net.Addr { + return s.LocalAddr() +} + +// LocalAddr is used to get the local address of the +// underlying connection. +func (s *Session) LocalAddr() net.Addr { + addr, ok := s.conn.(hasAddr) + if !ok { + return &yamuxAddr{"local"} + } + return addr.LocalAddr() +} + +// RemoteAddr is used to get the address of remote end +// of the underlying connection +func (s *Session) RemoteAddr() net.Addr { + addr, ok := s.conn.(hasAddr) + if !ok { + return &yamuxAddr{"remote"} + } + return addr.RemoteAddr() +} + +// LocalAddr returns the local address +func (s *Stream) LocalAddr() net.Addr { + return s.session.LocalAddr() +} + +// LocalAddr returns the remote address +func (s *Stream) RemoteAddr() net.Addr { + return s.session.RemoteAddr() +} diff --git a/vendor/github.com/hashicorp/yamux/const.go b/vendor/github.com/hashicorp/yamux/const.go new file mode 100644 index 00000000000..4f52938287f --- /dev/null +++ b/vendor/github.com/hashicorp/yamux/const.go @@ -0,0 +1,157 @@ +package yamux + +import ( + "encoding/binary" + "fmt" +) + +var ( + // ErrInvalidVersion means we received a frame with an + // invalid version + ErrInvalidVersion = fmt.Errorf("invalid protocol version") + + // ErrInvalidMsgType means we received a frame with an + // invalid message type + ErrInvalidMsgType = fmt.Errorf("invalid msg type") + + // ErrSessionShutdown is used if there is a shutdown during + // an operation + ErrSessionShutdown = fmt.Errorf("session shutdown") + + // ErrStreamsExhausted is returned if we have no more + // stream ids to issue + ErrStreamsExhausted = fmt.Errorf("streams exhausted") + + // ErrDuplicateStream is used if a duplicate stream is + // opened inbound + ErrDuplicateStream = fmt.Errorf("duplicate stream initiated") + + // ErrReceiveWindowExceeded indicates the window was exceeded + ErrRecvWindowExceeded = fmt.Errorf("recv window exceeded") + + // ErrTimeout is used when we reach an IO deadline + ErrTimeout = fmt.Errorf("i/o deadline reached") + + // ErrStreamClosed is returned when using a closed stream + ErrStreamClosed = fmt.Errorf("stream closed") + + // ErrUnexpectedFlag is set when we get an unexpected flag + ErrUnexpectedFlag = fmt.Errorf("unexpected flag") + + // ErrRemoteGoAway is used when we get a go away from the other side + ErrRemoteGoAway = fmt.Errorf("remote end is not accepting connections") + + // ErrConnectionReset is sent if a stream is reset. This can happen + // if the backlog is exceeded, or if there was a remote GoAway. + ErrConnectionReset = fmt.Errorf("connection reset") + + // ErrConnectionWriteTimeout indicates that we hit the "safety valve" + // timeout writing to the underlying stream connection. + ErrConnectionWriteTimeout = fmt.Errorf("connection write timeout") + + // ErrKeepAliveTimeout is sent if a missed keepalive caused the stream close + ErrKeepAliveTimeout = fmt.Errorf("keepalive timeout") +) + +const ( + // protoVersion is the only version we support + protoVersion uint8 = 0 +) + +const ( + // Data is used for data frames. They are followed + // by length bytes worth of payload. + typeData uint8 = iota + + // WindowUpdate is used to change the window of + // a given stream. The length indicates the delta + // update to the window. + typeWindowUpdate + + // Ping is sent as a keep-alive or to measure + // the RTT. The StreamID and Length value are echoed + // back in the response. + typePing + + // GoAway is sent to terminate a session. The StreamID + // should be 0 and the length is an error code. + typeGoAway +) + +const ( + // SYN is sent to signal a new stream. May + // be sent with a data payload + flagSYN uint16 = 1 << iota + + // ACK is sent to acknowledge a new stream. May + // be sent with a data payload + flagACK + + // FIN is sent to half-close the given stream. + // May be sent with a data payload. + flagFIN + + // RST is used to hard close a given stream. + flagRST +) + +const ( + // initialStreamWindow is the initial stream window size + initialStreamWindow uint32 = 256 * 1024 +) + +const ( + // goAwayNormal is sent on a normal termination + goAwayNormal uint32 = iota + + // goAwayProtoErr sent on a protocol error + goAwayProtoErr + + // goAwayInternalErr sent on an internal error + goAwayInternalErr +) + +const ( + sizeOfVersion = 1 + sizeOfType = 1 + sizeOfFlags = 2 + sizeOfStreamID = 4 + sizeOfLength = 4 + headerSize = sizeOfVersion + sizeOfType + sizeOfFlags + + sizeOfStreamID + sizeOfLength +) + +type header []byte + +func (h header) Version() uint8 { + return h[0] +} + +func (h header) MsgType() uint8 { + return h[1] +} + +func (h header) Flags() uint16 { + return binary.BigEndian.Uint16(h[2:4]) +} + +func (h header) StreamID() uint32 { + return binary.BigEndian.Uint32(h[4:8]) +} + +func (h header) Length() uint32 { + return binary.BigEndian.Uint32(h[8:12]) +} + +func (h header) String() string { + return fmt.Sprintf("Vsn:%d Type:%d Flags:%d StreamID:%d Length:%d", + h.Version(), h.MsgType(), h.Flags(), h.StreamID(), h.Length()) +} + +func (h header) encode(msgType uint8, flags uint16, streamID uint32, length uint32) { + h[0] = protoVersion + h[1] = msgType + binary.BigEndian.PutUint16(h[2:4], flags) + binary.BigEndian.PutUint32(h[4:8], streamID) + binary.BigEndian.PutUint32(h[8:12], length) +} diff --git a/vendor/github.com/hashicorp/yamux/go.mod b/vendor/github.com/hashicorp/yamux/go.mod new file mode 100644 index 00000000000..672a0e58112 --- /dev/null +++ b/vendor/github.com/hashicorp/yamux/go.mod @@ -0,0 +1 @@ +module github.com/hashicorp/yamux diff --git a/vendor/github.com/hashicorp/yamux/mux.go b/vendor/github.com/hashicorp/yamux/mux.go new file mode 100644 index 00000000000..18a078c8ad9 --- /dev/null +++ b/vendor/github.com/hashicorp/yamux/mux.go @@ -0,0 +1,98 @@ +package yamux + +import ( + "fmt" + "io" + "log" + "os" + "time" +) + +// Config is used to tune the Yamux session +type Config struct { + // AcceptBacklog is used to limit how many streams may be + // waiting an accept. + AcceptBacklog int + + // EnableKeepalive is used to do a period keep alive + // messages using a ping. + EnableKeepAlive bool + + // KeepAliveInterval is how often to perform the keep alive + KeepAliveInterval time.Duration + + // ConnectionWriteTimeout is meant to be a "safety valve" timeout after + // we which will suspect a problem with the underlying connection and + // close it. This is only applied to writes, where's there's generally + // an expectation that things will move along quickly. + ConnectionWriteTimeout time.Duration + + // MaxStreamWindowSize is used to control the maximum + // window size that we allow for a stream. + MaxStreamWindowSize uint32 + + // LogOutput is used to control the log destination. Either Logger or + // LogOutput can be set, not both. + LogOutput io.Writer + + // Logger is used to pass in the logger to be used. Either Logger or + // LogOutput can be set, not both. + Logger *log.Logger +} + +// DefaultConfig is used to return a default configuration +func DefaultConfig() *Config { + return &Config{ + AcceptBacklog: 256, + EnableKeepAlive: true, + KeepAliveInterval: 30 * time.Second, + ConnectionWriteTimeout: 10 * time.Second, + MaxStreamWindowSize: initialStreamWindow, + LogOutput: os.Stderr, + } +} + +// VerifyConfig is used to verify the sanity of configuration +func VerifyConfig(config *Config) error { + if config.AcceptBacklog <= 0 { + return fmt.Errorf("backlog must be positive") + } + if config.KeepAliveInterval == 0 { + return fmt.Errorf("keep-alive interval must be positive") + } + if config.MaxStreamWindowSize < initialStreamWindow { + return fmt.Errorf("MaxStreamWindowSize must be larger than %d", initialStreamWindow) + } + if config.LogOutput != nil && config.Logger != nil { + return fmt.Errorf("both Logger and LogOutput may not be set, select one") + } else if config.LogOutput == nil && config.Logger == nil { + return fmt.Errorf("one of Logger or LogOutput must be set, select one") + } + return nil +} + +// Server is used to initialize a new server-side connection. +// There must be at most one server-side connection. If a nil config is +// provided, the DefaultConfiguration will be used. +func Server(conn io.ReadWriteCloser, config *Config) (*Session, error) { + if config == nil { + config = DefaultConfig() + } + if err := VerifyConfig(config); err != nil { + return nil, err + } + return newSession(config, conn, false), nil +} + +// Client is used to initialize a new client-side connection. +// There must be at most one client-side connection. +func Client(conn io.ReadWriteCloser, config *Config) (*Session, error) { + if config == nil { + config = DefaultConfig() + } + + if err := VerifyConfig(config); err != nil { + return nil, err + } + return newSession(config, conn, true), nil +} diff --git a/vendor/github.com/hashicorp/yamux/session.go b/vendor/github.com/hashicorp/yamux/session.go new file mode 100644 index 00000000000..a80ddec35ea --- /dev/null +++ b/vendor/github.com/hashicorp/yamux/session.go @@ -0,0 +1,653 @@ +package yamux + +import ( + "bufio" + "fmt" + "io" + "io/ioutil" + "log" + "math" + "net" + "strings" + "sync" + "sync/atomic" + "time" +) + +// Session is used to wrap a reliable ordered connection and to +// multiplex it into multiple streams. +type Session struct { + // remoteGoAway indicates the remote side does + // not want futher connections. Must be first for alignment. + remoteGoAway int32 + + // localGoAway indicates that we should stop + // accepting futher connections. Must be first for alignment. + localGoAway int32 + + // nextStreamID is the next stream we should + // send. This depends if we are a client/server. + nextStreamID uint32 + + // config holds our configuration + config *Config + + // logger is used for our logs + logger *log.Logger + + // conn is the underlying connection + conn io.ReadWriteCloser + + // bufRead is a buffered reader + bufRead *bufio.Reader + + // pings is used to track inflight pings + pings map[uint32]chan struct{} + pingID uint32 + pingLock sync.Mutex + + // streams maps a stream id to a stream, and inflight has an entry + // for any outgoing stream that has not yet been established. Both are + // protected by streamLock. + streams map[uint32]*Stream + inflight map[uint32]struct{} + streamLock sync.Mutex + + // synCh acts like a semaphore. It is sized to the AcceptBacklog which + // is assumed to be symmetric between the client and server. This allows + // the client to avoid exceeding the backlog and instead blocks the open. + synCh chan struct{} + + // acceptCh is used to pass ready streams to the client + acceptCh chan *Stream + + // sendCh is used to mark a stream as ready to send, + // or to send a header out directly. + sendCh chan sendReady + + // recvDoneCh is closed when recv() exits to avoid a race + // between stream registration and stream shutdown + recvDoneCh chan struct{} + + // shutdown is used to safely close a session + shutdown bool + shutdownErr error + shutdownCh chan struct{} + shutdownLock sync.Mutex +} + +// sendReady is used to either mark a stream as ready +// or to directly send a header +type sendReady struct { + Hdr []byte + Body io.Reader + Err chan error +} + +// newSession is used to construct a new session +func newSession(config *Config, conn io.ReadWriteCloser, client bool) *Session { + logger := config.Logger + if logger == nil { + logger = log.New(config.LogOutput, "", log.LstdFlags) + } + + s := &Session{ + config: config, + logger: logger, + conn: conn, + bufRead: bufio.NewReader(conn), + pings: make(map[uint32]chan struct{}), + streams: make(map[uint32]*Stream), + inflight: make(map[uint32]struct{}), + synCh: make(chan struct{}, config.AcceptBacklog), + acceptCh: make(chan *Stream, config.AcceptBacklog), + sendCh: make(chan sendReady, 64), + recvDoneCh: make(chan struct{}), + shutdownCh: make(chan struct{}), + } + if client { + s.nextStreamID = 1 + } else { + s.nextStreamID = 2 + } + go s.recv() + go s.send() + if config.EnableKeepAlive { + go s.keepalive() + } + return s +} + +// IsClosed does a safe check to see if we have shutdown +func (s *Session) IsClosed() bool { + select { + case <-s.shutdownCh: + return true + default: + return false + } +} + +// CloseChan returns a read-only channel which is closed as +// soon as the session is closed. +func (s *Session) CloseChan() <-chan struct{} { + return s.shutdownCh +} + +// NumStreams returns the number of currently open streams +func (s *Session) NumStreams() int { + s.streamLock.Lock() + num := len(s.streams) + s.streamLock.Unlock() + return num +} + +// Open is used to create a new stream as a net.Conn +func (s *Session) Open() (net.Conn, error) { + conn, err := s.OpenStream() + if err != nil { + return nil, err + } + return conn, nil +} + +// OpenStream is used to create a new stream +func (s *Session) OpenStream() (*Stream, error) { + if s.IsClosed() { + return nil, ErrSessionShutdown + } + if atomic.LoadInt32(&s.remoteGoAway) == 1 { + return nil, ErrRemoteGoAway + } + + // Block if we have too many inflight SYNs + select { + case s.synCh <- struct{}{}: + case <-s.shutdownCh: + return nil, ErrSessionShutdown + } + +GET_ID: + // Get an ID, and check for stream exhaustion + id := atomic.LoadUint32(&s.nextStreamID) + if id >= math.MaxUint32-1 { + return nil, ErrStreamsExhausted + } + if !atomic.CompareAndSwapUint32(&s.nextStreamID, id, id+2) { + goto GET_ID + } + + // Register the stream + stream := newStream(s, id, streamInit) + s.streamLock.Lock() + s.streams[id] = stream + s.inflight[id] = struct{}{} + s.streamLock.Unlock() + + // Send the window update to create + if err := stream.sendWindowUpdate(); err != nil { + select { + case <-s.synCh: + default: + s.logger.Printf("[ERR] yamux: aborted stream open without inflight syn semaphore") + } + return nil, err + } + return stream, nil +} + +// Accept is used to block until the next available stream +// is ready to be accepted. +func (s *Session) Accept() (net.Conn, error) { + conn, err := s.AcceptStream() + if err != nil { + return nil, err + } + return conn, err +} + +// AcceptStream is used to block until the next available stream +// is ready to be accepted. +func (s *Session) AcceptStream() (*Stream, error) { + select { + case stream := <-s.acceptCh: + if err := stream.sendWindowUpdate(); err != nil { + return nil, err + } + return stream, nil + case <-s.shutdownCh: + return nil, s.shutdownErr + } +} + +// Close is used to close the session and all streams. +// Attempts to send a GoAway before closing the connection. +func (s *Session) Close() error { + s.shutdownLock.Lock() + defer s.shutdownLock.Unlock() + + if s.shutdown { + return nil + } + s.shutdown = true + if s.shutdownErr == nil { + s.shutdownErr = ErrSessionShutdown + } + close(s.shutdownCh) + s.conn.Close() + <-s.recvDoneCh + + s.streamLock.Lock() + defer s.streamLock.Unlock() + for _, stream := range s.streams { + stream.forceClose() + } + return nil +} + +// exitErr is used to handle an error that is causing the +// session to terminate. +func (s *Session) exitErr(err error) { + s.shutdownLock.Lock() + if s.shutdownErr == nil { + s.shutdownErr = err + } + s.shutdownLock.Unlock() + s.Close() +} + +// GoAway can be used to prevent accepting further +// connections. It does not close the underlying conn. +func (s *Session) GoAway() error { + return s.waitForSend(s.goAway(goAwayNormal), nil) +} + +// goAway is used to send a goAway message +func (s *Session) goAway(reason uint32) header { + atomic.SwapInt32(&s.localGoAway, 1) + hdr := header(make([]byte, headerSize)) + hdr.encode(typeGoAway, 0, 0, reason) + return hdr +} + +// Ping is used to measure the RTT response time +func (s *Session) Ping() (time.Duration, error) { + // Get a channel for the ping + ch := make(chan struct{}) + + // Get a new ping id, mark as pending + s.pingLock.Lock() + id := s.pingID + s.pingID++ + s.pings[id] = ch + s.pingLock.Unlock() + + // Send the ping request + hdr := header(make([]byte, headerSize)) + hdr.encode(typePing, flagSYN, 0, id) + if err := s.waitForSend(hdr, nil); err != nil { + return 0, err + } + + // Wait for a response + start := time.Now() + select { + case <-ch: + case <-time.After(s.config.ConnectionWriteTimeout): + s.pingLock.Lock() + delete(s.pings, id) // Ignore it if a response comes later. + s.pingLock.Unlock() + return 0, ErrTimeout + case <-s.shutdownCh: + return 0, ErrSessionShutdown + } + + // Compute the RTT + return time.Now().Sub(start), nil +} + +// keepalive is a long running goroutine that periodically does +// a ping to keep the connection alive. +func (s *Session) keepalive() { + for { + select { + case <-time.After(s.config.KeepAliveInterval): + _, err := s.Ping() + if err != nil { + if err != ErrSessionShutdown { + s.logger.Printf("[ERR] yamux: keepalive failed: %v", err) + s.exitErr(ErrKeepAliveTimeout) + } + return + } + case <-s.shutdownCh: + return + } + } +} + +// waitForSendErr waits to send a header, checking for a potential shutdown +func (s *Session) waitForSend(hdr header, body io.Reader) error { + errCh := make(chan error, 1) + return s.waitForSendErr(hdr, body, errCh) +} + +// waitForSendErr waits to send a header with optional data, checking for a +// potential shutdown. Since there's the expectation that sends can happen +// in a timely manner, we enforce the connection write timeout here. +func (s *Session) waitForSendErr(hdr header, body io.Reader, errCh chan error) error { + t := timerPool.Get() + timer := t.(*time.Timer) + timer.Reset(s.config.ConnectionWriteTimeout) + defer func() { + timer.Stop() + select { + case <-timer.C: + default: + } + timerPool.Put(t) + }() + + ready := sendReady{Hdr: hdr, Body: body, Err: errCh} + select { + case s.sendCh <- ready: + case <-s.shutdownCh: + return ErrSessionShutdown + case <-timer.C: + return ErrConnectionWriteTimeout + } + + select { + case err := <-errCh: + return err + case <-s.shutdownCh: + return ErrSessionShutdown + case <-timer.C: + return ErrConnectionWriteTimeout + } +} + +// sendNoWait does a send without waiting. Since there's the expectation that +// the send happens right here, we enforce the connection write timeout if we +// can't queue the header to be sent. +func (s *Session) sendNoWait(hdr header) error { + t := timerPool.Get() + timer := t.(*time.Timer) + timer.Reset(s.config.ConnectionWriteTimeout) + defer func() { + timer.Stop() + select { + case <-timer.C: + default: + } + timerPool.Put(t) + }() + + select { + case s.sendCh <- sendReady{Hdr: hdr}: + return nil + case <-s.shutdownCh: + return ErrSessionShutdown + case <-timer.C: + return ErrConnectionWriteTimeout + } +} + +// send is a long running goroutine that sends data +func (s *Session) send() { + for { + select { + case ready := <-s.sendCh: + // Send a header if ready + if ready.Hdr != nil { + sent := 0 + for sent < len(ready.Hdr) { + n, err := s.conn.Write(ready.Hdr[sent:]) + if err != nil { + s.logger.Printf("[ERR] yamux: Failed to write header: %v", err) + asyncSendErr(ready.Err, err) + s.exitErr(err) + return + } + sent += n + } + } + + // Send data from a body if given + if ready.Body != nil { + _, err := io.Copy(s.conn, ready.Body) + if err != nil { + s.logger.Printf("[ERR] yamux: Failed to write body: %v", err) + asyncSendErr(ready.Err, err) + s.exitErr(err) + return + } + } + + // No error, successful send + asyncSendErr(ready.Err, nil) + case <-s.shutdownCh: + return + } + } +} + +// recv is a long running goroutine that accepts new data +func (s *Session) recv() { + if err := s.recvLoop(); err != nil { + s.exitErr(err) + } +} + +// Ensure that the index of the handler (typeData/typeWindowUpdate/etc) matches the message type +var ( + handlers = []func(*Session, header) error{ + typeData: (*Session).handleStreamMessage, + typeWindowUpdate: (*Session).handleStreamMessage, + typePing: (*Session).handlePing, + typeGoAway: (*Session).handleGoAway, + } +) + +// recvLoop continues to receive data until a fatal error is encountered +func (s *Session) recvLoop() error { + defer close(s.recvDoneCh) + hdr := header(make([]byte, headerSize)) + for { + // Read the header + if _, err := io.ReadFull(s.bufRead, hdr); err != nil { + if err != io.EOF && !strings.Contains(err.Error(), "closed") && !strings.Contains(err.Error(), "reset by peer") { + s.logger.Printf("[ERR] yamux: Failed to read header: %v", err) + } + return err + } + + // Verify the version + if hdr.Version() != protoVersion { + s.logger.Printf("[ERR] yamux: Invalid protocol version: %d", hdr.Version()) + return ErrInvalidVersion + } + + mt := hdr.MsgType() + if mt < typeData || mt > typeGoAway { + return ErrInvalidMsgType + } + + if err := handlers[mt](s, hdr); err != nil { + return err + } + } +} + +// handleStreamMessage handles either a data or window update frame +func (s *Session) handleStreamMessage(hdr header) error { + // Check for a new stream creation + id := hdr.StreamID() + flags := hdr.Flags() + if flags&flagSYN == flagSYN { + if err := s.incomingStream(id); err != nil { + return err + } + } + + // Get the stream + s.streamLock.Lock() + stream := s.streams[id] + s.streamLock.Unlock() + + // If we do not have a stream, likely we sent a RST + if stream == nil { + // Drain any data on the wire + if hdr.MsgType() == typeData && hdr.Length() > 0 { + s.logger.Printf("[WARN] yamux: Discarding data for stream: %d", id) + if _, err := io.CopyN(ioutil.Discard, s.bufRead, int64(hdr.Length())); err != nil { + s.logger.Printf("[ERR] yamux: Failed to discard data: %v", err) + return nil + } + } else { + s.logger.Printf("[WARN] yamux: frame for missing stream: %v", hdr) + } + return nil + } + + // Check if this is a window update + if hdr.MsgType() == typeWindowUpdate { + if err := stream.incrSendWindow(hdr, flags); err != nil { + if sendErr := s.sendNoWait(s.goAway(goAwayProtoErr)); sendErr != nil { + s.logger.Printf("[WARN] yamux: failed to send go away: %v", sendErr) + } + return err + } + return nil + } + + // Read the new data + if err := stream.readData(hdr, flags, s.bufRead); err != nil { + if sendErr := s.sendNoWait(s.goAway(goAwayProtoErr)); sendErr != nil { + s.logger.Printf("[WARN] yamux: failed to send go away: %v", sendErr) + } + return err + } + return nil +} + +// handlePing is invokde for a typePing frame +func (s *Session) handlePing(hdr header) error { + flags := hdr.Flags() + pingID := hdr.Length() + + // Check if this is a query, respond back in a separate context so we + // don't interfere with the receiving thread blocking for the write. + if flags&flagSYN == flagSYN { + go func() { + hdr := header(make([]byte, headerSize)) + hdr.encode(typePing, flagACK, 0, pingID) + if err := s.sendNoWait(hdr); err != nil { + s.logger.Printf("[WARN] yamux: failed to send ping reply: %v", err) + } + }() + return nil + } + + // Handle a response + s.pingLock.Lock() + ch := s.pings[pingID] + if ch != nil { + delete(s.pings, pingID) + close(ch) + } + s.pingLock.Unlock() + return nil +} + +// handleGoAway is invokde for a typeGoAway frame +func (s *Session) handleGoAway(hdr header) error { + code := hdr.Length() + switch code { + case goAwayNormal: + atomic.SwapInt32(&s.remoteGoAway, 1) + case goAwayProtoErr: + s.logger.Printf("[ERR] yamux: received protocol error go away") + return fmt.Errorf("yamux protocol error") + case goAwayInternalErr: + s.logger.Printf("[ERR] yamux: received internal error go away") + return fmt.Errorf("remote yamux internal error") + default: + s.logger.Printf("[ERR] yamux: received unexpected go away") + return fmt.Errorf("unexpected go away received") + } + return nil +} + +// incomingStream is used to create a new incoming stream +func (s *Session) incomingStream(id uint32) error { + // Reject immediately if we are doing a go away + if atomic.LoadInt32(&s.localGoAway) == 1 { + hdr := header(make([]byte, headerSize)) + hdr.encode(typeWindowUpdate, flagRST, id, 0) + return s.sendNoWait(hdr) + } + + // Allocate a new stream + stream := newStream(s, id, streamSYNReceived) + + s.streamLock.Lock() + defer s.streamLock.Unlock() + + // Check if stream already exists + if _, ok := s.streams[id]; ok { + s.logger.Printf("[ERR] yamux: duplicate stream declared") + if sendErr := s.sendNoWait(s.goAway(goAwayProtoErr)); sendErr != nil { + s.logger.Printf("[WARN] yamux: failed to send go away: %v", sendErr) + } + return ErrDuplicateStream + } + + // Register the stream + s.streams[id] = stream + + // Check if we've exceeded the backlog + select { + case s.acceptCh <- stream: + return nil + default: + // Backlog exceeded! RST the stream + s.logger.Printf("[WARN] yamux: backlog exceeded, forcing connection reset") + delete(s.streams, id) + stream.sendHdr.encode(typeWindowUpdate, flagRST, id, 0) + return s.sendNoWait(stream.sendHdr) + } +} + +// closeStream is used to close a stream once both sides have +// issued a close. If there was an in-flight SYN and the stream +// was not yet established, then this will give the credit back. +func (s *Session) closeStream(id uint32) { + s.streamLock.Lock() + if _, ok := s.inflight[id]; ok { + select { + case <-s.synCh: + default: + s.logger.Printf("[ERR] yamux: SYN tracking out of sync") + } + } + delete(s.streams, id) + s.streamLock.Unlock() +} + +// establishStream is used to mark a stream that was in the +// SYN Sent state as established. +func (s *Session) establishStream(id uint32) { + s.streamLock.Lock() + if _, ok := s.inflight[id]; ok { + delete(s.inflight, id) + } else { + s.logger.Printf("[ERR] yamux: established stream without inflight SYN (no tracking entry)") + } + select { + case <-s.synCh: + default: + s.logger.Printf("[ERR] yamux: established stream without inflight SYN (didn't have semaphore)") + } + s.streamLock.Unlock() +} diff --git a/vendor/github.com/hashicorp/yamux/spec.md b/vendor/github.com/hashicorp/yamux/spec.md new file mode 100644 index 00000000000..183d797bdea --- /dev/null +++ b/vendor/github.com/hashicorp/yamux/spec.md @@ -0,0 +1,140 @@ +# Specification + +We use this document to detail the internal specification of Yamux. +This is used both as a guide for implementing Yamux, but also for +alternative interoperable libraries to be built. + +# Framing + +Yamux uses a streaming connection underneath, but imposes a message +framing so that it can be shared between many logical streams. Each +frame contains a header like: + +* Version (8 bits) +* Type (8 bits) +* Flags (16 bits) +* StreamID (32 bits) +* Length (32 bits) + +This means that each header has a 12 byte overhead. +All fields are encoded in network order (big endian). +Each field is described below: + +## Version Field + +The version field is used for future backward compatibility. At the +current time, the field is always set to 0, to indicate the initial +version. + +## Type Field + +The type field is used to switch the frame message type. The following +message types are supported: + +* 0x0 Data - Used to transmit data. May transmit zero length payloads + depending on the flags. + +* 0x1 Window Update - Used to updated the senders receive window size. + This is used to implement per-session flow control. + +* 0x2 Ping - Used to measure RTT. It can also be used to heart-beat + and do keep-alives over TCP. + +* 0x3 Go Away - Used to close a session. + +## Flag Field + +The flags field is used to provide additional information related +to the message type. The following flags are supported: + +* 0x1 SYN - Signals the start of a new stream. May be sent with a data or + window update message. Also sent with a ping to indicate outbound. + +* 0x2 ACK - Acknowledges the start of a new stream. May be sent with a data + or window update message. Also sent with a ping to indicate response. + +* 0x4 FIN - Performs a half-close of a stream. May be sent with a data + message or window update. + +* 0x8 RST - Reset a stream immediately. May be sent with a data or + window update message. + +## StreamID Field + +The StreamID field is used to identify the logical stream the frame +is addressing. The client side should use odd ID's, and the server even. +This prevents any collisions. Additionally, the 0 ID is reserved to represent +the session. + +Both Ping and Go Away messages should always use the 0 StreamID. + +## Length Field + +The meaning of the length field depends on the message type: + +* Data - provides the length of bytes following the header +* Window update - provides a delta update to the window size +* Ping - Contains an opaque value, echoed back +* Go Away - Contains an error code + +# Message Flow + +There is no explicit connection setup, as Yamux relies on an underlying +transport to be provided. However, there is a distinction between client +and server side of the connection. + +## Opening a stream + +To open a stream, an initial data or window update frame is sent +with a new StreamID. The SYN flag should be set to signal a new stream. + +The receiver must then reply with either a data or window update frame +with the StreamID along with the ACK flag to accept the stream or with +the RST flag to reject the stream. + +Because we are relying on the reliable stream underneath, a connection +can begin sending data once the SYN flag is sent. The corresponding +ACK does not need to be received. This is particularly well suited +for an RPC system where a client wants to open a stream and immediately +fire a request without waiting for the RTT of the ACK. + +This does introduce the possibility of a connection being rejected +after data has been sent already. This is a slight semantic difference +from TCP, where the conection cannot be refused after it is opened. +Clients should be prepared to handle this by checking for an error +that indicates a RST was received. + +## Closing a stream + +To close a stream, either side sends a data or window update frame +along with the FIN flag. This does a half-close indicating the sender +will send no further data. + +Once both sides have closed the connection, the stream is closed. + +Alternatively, if an error occurs, the RST flag can be used to +hard close a stream immediately. + +## Flow Control + +When Yamux is initially starts each stream with a 256KB window size. +There is no window size for the session. + +To prevent the streams from stalling, window update frames should be +sent regularly. Yamux can be configured to provide a larger limit for +windows sizes. Both sides assume the initial 256KB window, but can +immediately send a window update as part of the SYN/ACK indicating a +larger window. + +Both sides should track the number of bytes sent in Data frames +only, as only they are tracked as part of the window size. + +## Session termination + +When a session is being terminated, the Go Away message should +be sent. The Length should be set to one of the following to +provide an error code: + +* 0x0 Normal termination +* 0x1 Protocol error +* 0x2 Internal error diff --git a/vendor/github.com/hashicorp/yamux/stream.go b/vendor/github.com/hashicorp/yamux/stream.go new file mode 100644 index 00000000000..aa239197398 --- /dev/null +++ b/vendor/github.com/hashicorp/yamux/stream.go @@ -0,0 +1,470 @@ +package yamux + +import ( + "bytes" + "io" + "sync" + "sync/atomic" + "time" +) + +type streamState int + +const ( + streamInit streamState = iota + streamSYNSent + streamSYNReceived + streamEstablished + streamLocalClose + streamRemoteClose + streamClosed + streamReset +) + +// Stream is used to represent a logical stream +// within a session. +type Stream struct { + recvWindow uint32 + sendWindow uint32 + + id uint32 + session *Session + + state streamState + stateLock sync.Mutex + + recvBuf *bytes.Buffer + recvLock sync.Mutex + + controlHdr header + controlErr chan error + controlHdrLock sync.Mutex + + sendHdr header + sendErr chan error + sendLock sync.Mutex + + recvNotifyCh chan struct{} + sendNotifyCh chan struct{} + + readDeadline atomic.Value // time.Time + writeDeadline atomic.Value // time.Time +} + +// newStream is used to construct a new stream within +// a given session for an ID +func newStream(session *Session, id uint32, state streamState) *Stream { + s := &Stream{ + id: id, + session: session, + state: state, + controlHdr: header(make([]byte, headerSize)), + controlErr: make(chan error, 1), + sendHdr: header(make([]byte, headerSize)), + sendErr: make(chan error, 1), + recvWindow: initialStreamWindow, + sendWindow: initialStreamWindow, + recvNotifyCh: make(chan struct{}, 1), + sendNotifyCh: make(chan struct{}, 1), + } + s.readDeadline.Store(time.Time{}) + s.writeDeadline.Store(time.Time{}) + return s +} + +// Session returns the associated stream session +func (s *Stream) Session() *Session { + return s.session +} + +// StreamID returns the ID of this stream +func (s *Stream) StreamID() uint32 { + return s.id +} + +// Read is used to read from the stream +func (s *Stream) Read(b []byte) (n int, err error) { + defer asyncNotify(s.recvNotifyCh) +START: + s.stateLock.Lock() + switch s.state { + case streamLocalClose: + fallthrough + case streamRemoteClose: + fallthrough + case streamClosed: + s.recvLock.Lock() + if s.recvBuf == nil || s.recvBuf.Len() == 0 { + s.recvLock.Unlock() + s.stateLock.Unlock() + return 0, io.EOF + } + s.recvLock.Unlock() + case streamReset: + s.stateLock.Unlock() + return 0, ErrConnectionReset + } + s.stateLock.Unlock() + + // If there is no data available, block + s.recvLock.Lock() + if s.recvBuf == nil || s.recvBuf.Len() == 0 { + s.recvLock.Unlock() + goto WAIT + } + + // Read any bytes + n, _ = s.recvBuf.Read(b) + s.recvLock.Unlock() + + // Send a window update potentially + err = s.sendWindowUpdate() + return n, err + +WAIT: + var timeout <-chan time.Time + var timer *time.Timer + readDeadline := s.readDeadline.Load().(time.Time) + if !readDeadline.IsZero() { + delay := readDeadline.Sub(time.Now()) + timer = time.NewTimer(delay) + timeout = timer.C + } + select { + case <-s.recvNotifyCh: + if timer != nil { + timer.Stop() + } + goto START + case <-timeout: + return 0, ErrTimeout + } +} + +// Write is used to write to the stream +func (s *Stream) Write(b []byte) (n int, err error) { + s.sendLock.Lock() + defer s.sendLock.Unlock() + total := 0 + for total < len(b) { + n, err := s.write(b[total:]) + total += n + if err != nil { + return total, err + } + } + return total, nil +} + +// write is used to write to the stream, may return on +// a short write. +func (s *Stream) write(b []byte) (n int, err error) { + var flags uint16 + var max uint32 + var body io.Reader +START: + s.stateLock.Lock() + switch s.state { + case streamLocalClose: + fallthrough + case streamClosed: + s.stateLock.Unlock() + return 0, ErrStreamClosed + case streamReset: + s.stateLock.Unlock() + return 0, ErrConnectionReset + } + s.stateLock.Unlock() + + // If there is no data available, block + window := atomic.LoadUint32(&s.sendWindow) + if window == 0 { + goto WAIT + } + + // Determine the flags if any + flags = s.sendFlags() + + // Send up to our send window + max = min(window, uint32(len(b))) + body = bytes.NewReader(b[:max]) + + // Send the header + s.sendHdr.encode(typeData, flags, s.id, max) + if err = s.session.waitForSendErr(s.sendHdr, body, s.sendErr); err != nil { + return 0, err + } + + // Reduce our send window + atomic.AddUint32(&s.sendWindow, ^uint32(max-1)) + + // Unlock + return int(max), err + +WAIT: + var timeout <-chan time.Time + writeDeadline := s.writeDeadline.Load().(time.Time) + if !writeDeadline.IsZero() { + delay := writeDeadline.Sub(time.Now()) + timeout = time.After(delay) + } + select { + case <-s.sendNotifyCh: + goto START + case <-timeout: + return 0, ErrTimeout + } + return 0, nil +} + +// sendFlags determines any flags that are appropriate +// based on the current stream state +func (s *Stream) sendFlags() uint16 { + s.stateLock.Lock() + defer s.stateLock.Unlock() + var flags uint16 + switch s.state { + case streamInit: + flags |= flagSYN + s.state = streamSYNSent + case streamSYNReceived: + flags |= flagACK + s.state = streamEstablished + } + return flags +} + +// sendWindowUpdate potentially sends a window update enabling +// further writes to take place. Must be invoked with the lock. +func (s *Stream) sendWindowUpdate() error { + s.controlHdrLock.Lock() + defer s.controlHdrLock.Unlock() + + // Determine the delta update + max := s.session.config.MaxStreamWindowSize + var bufLen uint32 + s.recvLock.Lock() + if s.recvBuf != nil { + bufLen = uint32(s.recvBuf.Len()) + } + delta := (max - bufLen) - s.recvWindow + + // Determine the flags if any + flags := s.sendFlags() + + // Check if we can omit the update + if delta < (max/2) && flags == 0 { + s.recvLock.Unlock() + return nil + } + + // Update our window + s.recvWindow += delta + s.recvLock.Unlock() + + // Send the header + s.controlHdr.encode(typeWindowUpdate, flags, s.id, delta) + if err := s.session.waitForSendErr(s.controlHdr, nil, s.controlErr); err != nil { + return err + } + return nil +} + +// sendClose is used to send a FIN +func (s *Stream) sendClose() error { + s.controlHdrLock.Lock() + defer s.controlHdrLock.Unlock() + + flags := s.sendFlags() + flags |= flagFIN + s.controlHdr.encode(typeWindowUpdate, flags, s.id, 0) + if err := s.session.waitForSendErr(s.controlHdr, nil, s.controlErr); err != nil { + return err + } + return nil +} + +// Close is used to close the stream +func (s *Stream) Close() error { + closeStream := false + s.stateLock.Lock() + switch s.state { + // Opened means we need to signal a close + case streamSYNSent: + fallthrough + case streamSYNReceived: + fallthrough + case streamEstablished: + s.state = streamLocalClose + goto SEND_CLOSE + + case streamLocalClose: + case streamRemoteClose: + s.state = streamClosed + closeStream = true + goto SEND_CLOSE + + case streamClosed: + case streamReset: + default: + panic("unhandled state") + } + s.stateLock.Unlock() + return nil +SEND_CLOSE: + s.stateLock.Unlock() + s.sendClose() + s.notifyWaiting() + if closeStream { + s.session.closeStream(s.id) + } + return nil +} + +// forceClose is used for when the session is exiting +func (s *Stream) forceClose() { + s.stateLock.Lock() + s.state = streamClosed + s.stateLock.Unlock() + s.notifyWaiting() +} + +// processFlags is used to update the state of the stream +// based on set flags, if any. Lock must be held +func (s *Stream) processFlags(flags uint16) error { + // Close the stream without holding the state lock + closeStream := false + defer func() { + if closeStream { + s.session.closeStream(s.id) + } + }() + + s.stateLock.Lock() + defer s.stateLock.Unlock() + if flags&flagACK == flagACK { + if s.state == streamSYNSent { + s.state = streamEstablished + } + s.session.establishStream(s.id) + } + if flags&flagFIN == flagFIN { + switch s.state { + case streamSYNSent: + fallthrough + case streamSYNReceived: + fallthrough + case streamEstablished: + s.state = streamRemoteClose + s.notifyWaiting() + case streamLocalClose: + s.state = streamClosed + closeStream = true + s.notifyWaiting() + default: + s.session.logger.Printf("[ERR] yamux: unexpected FIN flag in state %d", s.state) + return ErrUnexpectedFlag + } + } + if flags&flagRST == flagRST { + s.state = streamReset + closeStream = true + s.notifyWaiting() + } + return nil +} + +// notifyWaiting notifies all the waiting channels +func (s *Stream) notifyWaiting() { + asyncNotify(s.recvNotifyCh) + asyncNotify(s.sendNotifyCh) +} + +// incrSendWindow updates the size of our send window +func (s *Stream) incrSendWindow(hdr header, flags uint16) error { + if err := s.processFlags(flags); err != nil { + return err + } + + // Increase window, unblock a sender + atomic.AddUint32(&s.sendWindow, hdr.Length()) + asyncNotify(s.sendNotifyCh) + return nil +} + +// readData is used to handle a data frame +func (s *Stream) readData(hdr header, flags uint16, conn io.Reader) error { + if err := s.processFlags(flags); err != nil { + return err + } + + // Check that our recv window is not exceeded + length := hdr.Length() + if length == 0 { + return nil + } + + // Wrap in a limited reader + conn = &io.LimitedReader{R: conn, N: int64(length)} + + // Copy into buffer + s.recvLock.Lock() + + if length > s.recvWindow { + s.session.logger.Printf("[ERR] yamux: receive window exceeded (stream: %d, remain: %d, recv: %d)", s.id, s.recvWindow, length) + return ErrRecvWindowExceeded + } + + if s.recvBuf == nil { + // Allocate the receive buffer just-in-time to fit the full data frame. + // This way we can read in the whole packet without further allocations. + s.recvBuf = bytes.NewBuffer(make([]byte, 0, length)) + } + if _, err := io.Copy(s.recvBuf, conn); err != nil { + s.session.logger.Printf("[ERR] yamux: Failed to read stream data: %v", err) + s.recvLock.Unlock() + return err + } + + // Decrement the receive window + s.recvWindow -= length + s.recvLock.Unlock() + + // Unblock any readers + asyncNotify(s.recvNotifyCh) + return nil +} + +// SetDeadline sets the read and write deadlines +func (s *Stream) SetDeadline(t time.Time) error { + if err := s.SetReadDeadline(t); err != nil { + return err + } + if err := s.SetWriteDeadline(t); err != nil { + return err + } + return nil +} + +// SetReadDeadline sets the deadline for future Read calls. +func (s *Stream) SetReadDeadline(t time.Time) error { + s.readDeadline.Store(t) + return nil +} + +// SetWriteDeadline sets the deadline for future Write calls +func (s *Stream) SetWriteDeadline(t time.Time) error { + s.writeDeadline.Store(t) + return nil +} + +// Shrink is used to compact the amount of buffers utilized +// This is useful when using Yamux in a connection pool to reduce +// the idle memory utilization. +func (s *Stream) Shrink() { + s.recvLock.Lock() + if s.recvBuf != nil && s.recvBuf.Len() == 0 { + s.recvBuf = nil + } + s.recvLock.Unlock() +} diff --git a/vendor/github.com/hashicorp/yamux/util.go b/vendor/github.com/hashicorp/yamux/util.go new file mode 100644 index 00000000000..8a73e9249a6 --- /dev/null +++ b/vendor/github.com/hashicorp/yamux/util.go @@ -0,0 +1,43 @@ +package yamux + +import ( + "sync" + "time" +) + +var ( + timerPool = &sync.Pool{ + New: func() interface{} { + timer := time.NewTimer(time.Hour * 1e6) + timer.Stop() + return timer + }, + } +) + +// asyncSendErr is used to try an async send of an error +func asyncSendErr(ch chan error, err error) { + if ch == nil { + return + } + select { + case ch <- err: + default: + } +} + +// asyncNotify is used to signal a waiting goroutine +func asyncNotify(ch chan struct{}) { + select { + case ch <- struct{}{}: + default: + } +} + +// min computes the minimum of two values +func min(a, b uint32) uint32 { + if a < b { + return a + } + return b +} diff --git a/vendor/github.com/mitchellh/go-testing-interface/.travis.yml b/vendor/github.com/mitchellh/go-testing-interface/.travis.yml new file mode 100644 index 00000000000..928d000ec49 --- /dev/null +++ b/vendor/github.com/mitchellh/go-testing-interface/.travis.yml @@ -0,0 +1,13 @@ +language: go + +go: + - 1.8 + - 1.x + - tip + +script: + - go test + +matrix: + allow_failures: + - go: tip diff --git a/vendor/github.com/mitchellh/go-testing-interface/LICENSE b/vendor/github.com/mitchellh/go-testing-interface/LICENSE new file mode 100644 index 00000000000..a3866a291fd --- /dev/null +++ b/vendor/github.com/mitchellh/go-testing-interface/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2016 Mitchell Hashimoto + +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/mitchellh/go-testing-interface/README.md b/vendor/github.com/mitchellh/go-testing-interface/README.md new file mode 100644 index 00000000000..26781bbae88 --- /dev/null +++ b/vendor/github.com/mitchellh/go-testing-interface/README.md @@ -0,0 +1,52 @@ +# go-testing-interface + +go-testing-interface is a Go library that exports an interface that +`*testing.T` implements as well as a runtime version you can use in its +place. + +The purpose of this library is so that you can export test helpers as a +public API without depending on the "testing" package, since you can't +create a `*testing.T` struct manually. This lets you, for example, use the +public testing APIs to generate mock data at runtime, rather than just at +test time. + +## Usage & Example + +For usage and examples see the [Godoc](http://godoc.org/github.com/mitchellh/go-testing-interface). + +Given a test helper written using `go-testing-interface` like this: + + import "github.com/mitchellh/go-testing-interface" + + func TestHelper(t testing.T) { + t.Fatal("I failed") + } + +You can call the test helper in a real test easily: + + import "testing" + + func TestThing(t *testing.T) { + TestHelper(t) + } + +You can also call the test helper at runtime if needed: + + import "github.com/mitchellh/go-testing-interface" + + func main() { + TestHelper(&testing.RuntimeT{}) + } + +## Why?! + +**Why would I call a test helper that takes a *testing.T at runtime?** + +You probably shouldn't. The only use case I've seen (and I've had) for this +is to implement a "dev mode" for a service where the test helpers are used +to populate mock data, create a mock DB, perhaps run service dependencies +in-memory, etc. + +Outside of a "dev mode", I've never seen a use case for this and I think +there shouldn't be one since the point of the `testing.T` interface is that +you can fail immediately. diff --git a/vendor/github.com/mitchellh/go-testing-interface/go.mod b/vendor/github.com/mitchellh/go-testing-interface/go.mod new file mode 100644 index 00000000000..062796de727 --- /dev/null +++ b/vendor/github.com/mitchellh/go-testing-interface/go.mod @@ -0,0 +1 @@ +module github.com/mitchellh/go-testing-interface diff --git a/vendor/github.com/mitchellh/go-testing-interface/testing.go b/vendor/github.com/mitchellh/go-testing-interface/testing.go new file mode 100644 index 00000000000..204afb42005 --- /dev/null +++ b/vendor/github.com/mitchellh/go-testing-interface/testing.go @@ -0,0 +1,84 @@ +// +build !go1.9 + +package testing + +import ( + "fmt" + "log" +) + +// T is the interface that mimics the standard library *testing.T. +// +// In unit tests you can just pass a *testing.T struct. At runtime, outside +// of tests, you can pass in a RuntimeT struct from this package. +type T interface { + Error(args ...interface{}) + Errorf(format string, args ...interface{}) + Fail() + FailNow() + Failed() bool + Fatal(args ...interface{}) + Fatalf(format string, args ...interface{}) + Log(args ...interface{}) + Logf(format string, args ...interface{}) + Name() string + Skip(args ...interface{}) + SkipNow() + Skipf(format string, args ...interface{}) + Skipped() bool +} + +// RuntimeT implements T and can be instantiated and run at runtime to +// mimic *testing.T behavior. Unlike *testing.T, this will simply panic +// for calls to Fatal. For calls to Error, you'll have to check the errors +// list to determine whether to exit yourself. Name and Skip methods are +// unimplemented noops. +type RuntimeT struct { + failed bool +} + +func (t *RuntimeT) Error(args ...interface{}) { + log.Println(fmt.Sprintln(args...)) + t.Fail() +} + +func (t *RuntimeT) Errorf(format string, args ...interface{}) { + log.Println(fmt.Sprintf(format, args...)) + t.Fail() +} + +func (t *RuntimeT) Fatal(args ...interface{}) { + log.Println(fmt.Sprintln(args...)) + t.FailNow() +} + +func (t *RuntimeT) Fatalf(format string, args ...interface{}) { + log.Println(fmt.Sprintf(format, args...)) + t.FailNow() +} + +func (t *RuntimeT) Fail() { + t.failed = true +} + +func (t *RuntimeT) FailNow() { + panic("testing.T failed, see logs for output (if any)") +} + +func (t *RuntimeT) Failed() bool { + return t.failed +} + +func (t *RuntimeT) Log(args ...interface{}) { + log.Println(fmt.Sprintln(args...)) +} + +func (t *RuntimeT) Logf(format string, args ...interface{}) { + log.Println(fmt.Sprintf(format, args...)) +} + +func (t *RuntimeT) Name() string { return "" } +func (t *RuntimeT) Skip(args ...interface{}) {} +func (t *RuntimeT) SkipNow() {} +func (t *RuntimeT) Skipf(format string, args ...interface{}) {} +func (t *RuntimeT) Skipped() bool { return false } diff --git a/vendor/github.com/mitchellh/go-testing-interface/testing_go19.go b/vendor/github.com/mitchellh/go-testing-interface/testing_go19.go new file mode 100644 index 00000000000..31b42cadf8d --- /dev/null +++ b/vendor/github.com/mitchellh/go-testing-interface/testing_go19.go @@ -0,0 +1,108 @@ +// +build go1.9 + +// NOTE: This is a temporary copy of testing.go for Go 1.9 with the addition +// of "Helper" to the T interface. Go 1.9 at the time of typing is in RC +// and is set for release shortly. We'll support this on master as the default +// as soon as 1.9 is released. + +package testing + +import ( + "fmt" + "log" +) + +// T is the interface that mimics the standard library *testing.T. +// +// In unit tests you can just pass a *testing.T struct. At runtime, outside +// of tests, you can pass in a RuntimeT struct from this package. +type T interface { + Error(args ...interface{}) + Errorf(format string, args ...interface{}) + Fail() + FailNow() + Failed() bool + Fatal(args ...interface{}) + Fatalf(format string, args ...interface{}) + Log(args ...interface{}) + Logf(format string, args ...interface{}) + Name() string + Skip(args ...interface{}) + SkipNow() + Skipf(format string, args ...interface{}) + Skipped() bool + Helper() +} + +// RuntimeT implements T and can be instantiated and run at runtime to +// mimic *testing.T behavior. Unlike *testing.T, this will simply panic +// for calls to Fatal. For calls to Error, you'll have to check the errors +// list to determine whether to exit yourself. +type RuntimeT struct { + skipped bool + failed bool +} + +func (t *RuntimeT) Error(args ...interface{}) { + log.Println(fmt.Sprintln(args...)) + t.Fail() +} + +func (t *RuntimeT) Errorf(format string, args ...interface{}) { + log.Printf(format, args...) + t.Fail() +} + +func (t *RuntimeT) Fail() { + t.failed = true +} + +func (t *RuntimeT) FailNow() { + panic("testing.T failed, see logs for output (if any)") +} + +func (t *RuntimeT) Failed() bool { + return t.failed +} + +func (t *RuntimeT) Fatal(args ...interface{}) { + log.Print(args...) + t.FailNow() +} + +func (t *RuntimeT) Fatalf(format string, args ...interface{}) { + log.Printf(format, args...) + t.FailNow() +} + +func (t *RuntimeT) Log(args ...interface{}) { + log.Println(fmt.Sprintln(args...)) +} + +func (t *RuntimeT) Logf(format string, args ...interface{}) { + log.Println(fmt.Sprintf(format, args...)) +} + +func (t *RuntimeT) Name() string { + return "" +} + +func (t *RuntimeT) Skip(args ...interface{}) { + log.Print(args...) + t.SkipNow() +} + +func (t *RuntimeT) SkipNow() { + t.skipped = true +} + +func (t *RuntimeT) Skipf(format string, args ...interface{}) { + log.Printf(format, args...) + t.SkipNow() +} + +func (t *RuntimeT) Skipped() bool { + return t.skipped +} + +func (t *RuntimeT) Helper() {} diff --git a/vendor/github.com/oklog/run/.gitignore b/vendor/github.com/oklog/run/.gitignore new file mode 100644 index 00000000000..a1338d68517 --- /dev/null +++ b/vendor/github.com/oklog/run/.gitignore @@ -0,0 +1,14 @@ +# Binaries for programs and plugins +*.exe +*.dll +*.so +*.dylib + +# Test binary, build with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736 +.glide/ diff --git a/vendor/github.com/oklog/run/LICENSE b/vendor/github.com/oklog/run/LICENSE new file mode 100644 index 00000000000..261eeb9e9f8 --- /dev/null +++ b/vendor/github.com/oklog/run/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/oklog/run/README.md b/vendor/github.com/oklog/run/README.md new file mode 100644 index 00000000000..eba7d11cf3a --- /dev/null +++ b/vendor/github.com/oklog/run/README.md @@ -0,0 +1,75 @@ +# run + +[![GoDoc](https://godoc.org/github.com/oklog/run?status.svg)](https://godoc.org/github.com/oklog/run) +[![Build Status](https://img.shields.io/endpoint.svg?url=https%3A%2F%2Factions-badge.atrox.dev%2Foklog%2Frun%2Fbadge&style=flat-square&label=build)](https://github.com/oklog/run/actions?query=workflow%3ATest) +[![Go Report Card](https://goreportcard.com/badge/github.com/oklog/run)](https://goreportcard.com/report/github.com/oklog/run) +[![Apache 2 licensed](https://img.shields.io/badge/license-Apache2-blue.svg)](https://raw.githubusercontent.com/oklog/run/master/LICENSE) + +run.Group is a universal mechanism to manage goroutine lifecycles. + +Create a zero-value run.Group, and then add actors to it. Actors are defined as +a pair of functions: an **execute** function, which should run synchronously; +and an **interrupt** function, which, when invoked, should cause the execute +function to return. Finally, invoke Run, which concurrently runs all of the +actors, waits until the first actor exits, invokes the interrupt functions, and +finally returns control to the caller only once all actors have returned. This +general-purpose API allows callers to model pretty much any runnable task, and +achieve well-defined lifecycle semantics for the group. + +run.Group was written to manage component lifecycles in func main for +[OK Log](https://github.com/oklog/oklog). +But it's useful in any circumstance where you need to orchestrate multiple +goroutines as a unit whole. +[Click here](https://www.youtube.com/watch?v=LHe1Cb_Ud_M&t=15m45s) to see a +video of a talk where run.Group is described. + +## Examples + +### context.Context + +```go +ctx, cancel := context.WithCancel(context.Background()) +g.Add(func() error { + return myProcess(ctx, ...) +}, func(error) { + cancel() +}) +``` + +### net.Listener + +```go +ln, _ := net.Listen("tcp", ":8080") +g.Add(func() error { + return http.Serve(ln, nil) +}, func(error) { + ln.Close() +}) +``` + +### io.ReadCloser + +```go +var conn io.ReadCloser = ... +g.Add(func() error { + s := bufio.NewScanner(conn) + for s.Scan() { + println(s.Text()) + } + return s.Err() +}, func(error) { + conn.Close() +}) +``` + +## Comparisons + +Package run is somewhat similar to package +[errgroup](https://godoc.org/golang.org/x/sync/errgroup), +except it doesn't require actor goroutines to understand context semantics. + +It's somewhat similar to package +[tomb.v1](https://godoc.org/gopkg.in/tomb.v1) or +[tomb.v2](https://godoc.org/gopkg.in/tomb.v2), +except it has a much smaller API surface, delegating e.g. staged shutdown of +goroutines to the caller. diff --git a/vendor/github.com/oklog/run/actors.go b/vendor/github.com/oklog/run/actors.go new file mode 100644 index 00000000000..ef93495d3f0 --- /dev/null +++ b/vendor/github.com/oklog/run/actors.go @@ -0,0 +1,38 @@ +package run + +import ( + "context" + "fmt" + "os" + "os/signal" +) + +// SignalHandler returns an actor, i.e. an execute and interrupt func, that +// terminates with SignalError when the process receives one of the provided +// signals, or the parent context is canceled. +func SignalHandler(ctx context.Context, signals ...os.Signal) (execute func() error, interrupt func(error)) { + ctx, cancel := context.WithCancel(ctx) + return func() error { + c := make(chan os.Signal, 1) + signal.Notify(c, signals...) + select { + case sig := <-c: + return SignalError{Signal: sig} + case <-ctx.Done(): + return ctx.Err() + } + }, func(error) { + cancel() + } +} + +// SignalError is returned by the signal handler's execute function +// when it terminates due to a received signal. +type SignalError struct { + Signal os.Signal +} + +// Error implements the error interface. +func (e SignalError) Error() string { + return fmt.Sprintf("received signal %s", e.Signal) +} diff --git a/vendor/github.com/oklog/run/go.mod b/vendor/github.com/oklog/run/go.mod new file mode 100644 index 00000000000..b1297232ee8 --- /dev/null +++ b/vendor/github.com/oklog/run/go.mod @@ -0,0 +1,3 @@ +module github.com/oklog/run + +go 1.13 diff --git a/vendor/github.com/oklog/run/group.go b/vendor/github.com/oklog/run/group.go new file mode 100644 index 00000000000..832d47dd169 --- /dev/null +++ b/vendor/github.com/oklog/run/group.go @@ -0,0 +1,62 @@ +// Package run implements an actor-runner with deterministic teardown. It is +// somewhat similar to package errgroup, except it does not require actor +// goroutines to understand context semantics. This makes it suitable for use in +// more circumstances; for example, goroutines which are handling connections +// from net.Listeners, or scanning input from a closable io.Reader. +package run + +// Group collects actors (functions) and runs them concurrently. +// When one actor (function) returns, all actors are interrupted. +// The zero value of a Group is useful. +type Group struct { + actors []actor +} + +// Add an actor (function) to the group. Each actor must be pre-emptable by an +// interrupt function. That is, if interrupt is invoked, execute should return. +// Also, it must be safe to call interrupt even after execute has returned. +// +// The first actor (function) to return interrupts all running actors. +// The error is passed to the interrupt functions, and is returned by Run. +func (g *Group) Add(execute func() error, interrupt func(error)) { + g.actors = append(g.actors, actor{execute, interrupt}) +} + +// Run all actors (functions) concurrently. +// When the first actor returns, all others are interrupted. +// Run only returns when all actors have exited. +// Run returns the error returned by the first exiting actor. +func (g *Group) Run() error { + if len(g.actors) == 0 { + return nil + } + + // Run each actor. + errors := make(chan error, len(g.actors)) + for _, a := range g.actors { + go func(a actor) { + errors <- a.execute() + }(a) + } + + // Wait for the first actor to stop. + err := <-errors + + // Signal all actors to stop. + for _, a := range g.actors { + a.interrupt(err) + } + + // Wait for all actors to stop. + for i := 1; i < cap(errors); i++ { + <-errors + } + + // Return the original error. + return err +} + +type actor struct { + execute func() error + interrupt func(error) +} diff --git a/vendor/github.com/timberio/go-datemath/.gitignore b/vendor/github.com/timberio/go-datemath/.gitignore new file mode 100644 index 00000000000..26520536f94 --- /dev/null +++ b/vendor/github.com/timberio/go-datemath/.gitignore @@ -0,0 +1 @@ +y.output diff --git a/vendor/github.com/timberio/go-datemath/CONTRIBUTING.md b/vendor/github.com/timberio/go-datemath/CONTRIBUTING.md new file mode 100644 index 00000000000..62fb4e295a0 --- /dev/null +++ b/vendor/github.com/timberio/go-datemath/CONTRIBUTING.md @@ -0,0 +1,16 @@ +# Contributing + +Bug fixes and other contributions via pull request greatly welcomed! + +This library relies on [goyacc](https://godoc.org/golang.org/x/tools/cmd/goyacc) and +[golex](https://godoc.org/modernc.org/golex) for parsing and evaluating datemath grammar. + +To install, run: + +* `go get golang.org/x/tools/cmd/goyacc modernc.org/golex` + +After modifying either the `datemath.l` or `datemath.y` you can rerun `go generate`. + +When in doubt on semantics of the library, [Elasticsearch's +implementation](https://www.elastic.co/guide/en/elasticsearch/reference/7.3/common-options.html#date-math) should be +considered the canonical specification. diff --git a/vendor/github.com/timberio/go-datemath/LICENSE b/vendor/github.com/timberio/go-datemath/LICENSE new file mode 100644 index 00000000000..740f831482b --- /dev/null +++ b/vendor/github.com/timberio/go-datemath/LICENSE @@ -0,0 +1,13 @@ +Copyright (c) 2019, Timber Technologies, Inc. + +Permission to use, copy, modify, and/or distribute this software for any purpose +with or without fee is hereby granted, provided that the above copyright notice +and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS +OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER +TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF +THIS SOFTWARE. diff --git a/vendor/github.com/timberio/go-datemath/README.md b/vendor/github.com/timberio/go-datemath/README.md new file mode 100644 index 00000000000..71923de932c --- /dev/null +++ b/vendor/github.com/timberio/go-datemath/README.md @@ -0,0 +1,29 @@ +# go-datemath + +[![GoDoc](https://godoc.org/github.com/timberio/go-datemath?status.svg)](http://godoc.org/github.com/timberio/go-datemath) +[![Circle CI](https://circleci.com/gh/timberio/go-datemath.svg?style=svg)](https://circleci.com/gh/timberio/go-datemath) +[![Go Report Card](https://goreportcard.com/badge/github.com/timberio/go-datemath)](https://goreportcard.com/report/github.com/timberio/go-datemath) +[![coverage](https://gocover.io/_badge/github.com/timberio/go-datemath?0 "coverage")](http://gocover.io/github.com/timberio/go-datemath) + +This library provides support for parsing datemath expressions compatibly with [Elasticsearch datemath +expressions](https://www.elastic.co/guide/en/elasticsearch/reference/7.3/common-options.html#date-math). These are +useful for allowing users to specify, and for encoding, relative dates. Examples: + +* `now+15m`: 15 minutes from now +* `now-1w+1d`: one day after on week ago +* `2015-05-05T00:00:00||+1M`: one month after 2019-05-05 + +These expressions will seem familiar if you have used Grafana or Kibana. + +Example usage: + +```go +expr, _ := datemath.Parse("now-15m") +fmt.Println(t.Time(datemath.WithNow(now))) +``` + +See [package documentation](http://godoc.org/github.com/timberio/go-datemath) for usage and more examples. + +## Development / Contributing + +See [CONTRIBUTING.md](CONTRIBUTING.md). diff --git a/vendor/github.com/timberio/go-datemath/datemath.go b/vendor/github.com/timberio/go-datemath/datemath.go new file mode 100644 index 00000000000..c89bbc008bc --- /dev/null +++ b/vendor/github.com/timberio/go-datemath/datemath.go @@ -0,0 +1,352 @@ +// Requires golang.org/x/tools/cmd/goyacc and modernc.org/golex +// +//go:generate goyacc -o datemath.y.go datemath.y +//go:generate golex -o datemath.l.go datemath.l + +/* +Package datemath provides an expression language for relative dates based on Elasticsearch's date math. + +This package is useful for letting end-users describe dates in a simple format similar to Grafana and Kibana and for +persisting them as relative dates. + +The expression starts with an anchor date, which can either be "now", or an ISO8601 date string ending with ||. This +anchor date can optionally be followed by one or more date math expressions, for example: + + now+1h Add one hour + now-1d Subtract one day + now/d Round down to the nearest day + +The supported time units are: + y Years + M Months + w Weeks + d Days + b Business Days (excludes Saturday and Sunday by default, use WithBusinessDayFunc to override) + h Hours + H Hours + m Minutes + s Seconds + +Compatibility with Elasticsearch datemath + +This package aims to be a superset of Elasticsearch's expressions. That is, any datemath expression that is valid for +Elasticsearch should evaluate in the same way here. + +Currently the package does not support expressions outside of those also considered valid by Elasticsearch, but this may +change in the future to include additional functionality. +*/ +package datemath + +import ( + "fmt" + "strconv" + "strings" + "time" +) + +func init() { + // have goyacc parser return more verbose syntax error messages + yyErrorVerbose = true +} + +var missingTimeZone = time.FixedZone("MISSING", 0) + +type timeUnit rune + +const ( + timeUnitYear = timeUnit('y') + timeUnitMonth = timeUnit('M') + timeUnitWeek = timeUnit('w') + timeUnitDay = timeUnit('d') + timeUnitBusinessDay = timeUnit('b') + timeUnitHour = timeUnit('h') + timeUnitMinute = timeUnit('m') + timeUnitSecond = timeUnit('s') +) + +func (u timeUnit) String() string { + return string(u) +} + +// Expression represents a parsed datemath expression +type Expression struct { + input string + + mathExpression +} + +type mathExpression struct { + anchorDateExpression anchorDateExpression + adjustments []timeAdjuster +} + +func newMathExpression(anchorDateExpression anchorDateExpression, adjustments []timeAdjuster) mathExpression { + return mathExpression{ + anchorDateExpression: anchorDateExpression, + adjustments: adjustments, + } +} + +// MarshalJSON implements the json.Marshaler interface +// +// It serializes as the string expression the Expression was created with +func (e Expression) MarshalJSON() ([]byte, error) { + return []byte(strconv.Quote(e.String())), nil +} + +// UnmarshalJSON implements the json.Unmarshaler interface +// +// Parses the datemath expression from a JSON string +func (e *Expression) UnmarshalJSON(data []byte) error { + s, err := strconv.Unquote(string(data)) + if err != nil { + return err + } + + expression, err := Parse(s) + if err != nil { + return nil + } + + *e = expression + return nil +} + +// String returns a the string used to create the expression +func (e Expression) String() string { + return e.input +} + +// Options represesent configurable behavior for interpreting the datemath expression +type Options struct { + // Use this this time as "now" + // Default is `time.Now()` + Now time.Time + + // Use this location if there is no timezone in the expression + // Defaults to time.UTC + Location *time.Location + + // Use this weekday as the start of the week + // Defaults to time.Monday + StartOfWeek time.Weekday + + // Rounding to period should be done to the end of the period + // Defaults to false + RoundUp bool + + BusinessDayFunc func(time.Time) bool +} + +// WithNow use the given time as "now" +func WithNow(now time.Time) func(*Options) { + return func(o *Options) { + o.Now = now + } +} + +// WithStartOfWeek uses the given weekday as the start of the week +func WithStartOfWeek(day time.Weekday) func(*Options) { + return func(o *Options) { + o.StartOfWeek = day + } +} + +// WithLocation uses the given location as the timezone of the date if unspecified +func WithLocation(l *time.Location) func(*Options) { + return func(o *Options) { + o.Location = l + } +} + +// WithRoundUp sets the rounding of time to the end of the period instead of the beginning +func WithRoundUp(b bool) func(*Options) { + return func(o *Options) { + o.RoundUp = b + } +} + +// WithBusinessDayFunc use the given fn to check if a day is a business day +func WithBusinessDayFunc(fn func(time.Time) bool) func(*Options) { + return func(o *Options) { + o.BusinessDayFunc = fn + } +} + +func isNotWeekend(t time.Time) bool { + return t.Weekday() != time.Saturday && t.Weekday() != time.Sunday +} + +// Time evaluate the expression with the given options to get the time it represents +func (e Expression) Time(opts ...func(*Options)) time.Time { + options := Options{ + Now: time.Now(), + Location: time.UTC, + StartOfWeek: time.Monday, + } + for _, opt := range opts { + opt(&options) + } + + t := e.anchorDateExpression(options) + for _, adjustment := range e.adjustments { + t = adjustment(t, options) + } + return t +} + +// Parse parses the datemath expression which can later be evaluated +func Parse(s string) (Expression, error) { + lex := newLexer([]byte(s)) + lexWrapper := newLexerWrapper(lex) + + yyParse(lexWrapper) + + if len(lex.errors) > 0 { + return Expression{}, fmt.Errorf(strings.Join(lex.errors, "\n")) + } + + return Expression{input: s, mathExpression: lexWrapper.expression}, nil +} + +// MustParse is the same as Parse() but panic's on error +func MustParse(s string) Expression { + e, err := Parse(s) + if err != nil { + panic(err) + } + return e +} + +// ParseAndEvaluate is a convience wrapper to parse and return the time that the expression represents +func ParseAndEvaluate(s string, opts ...func(*Options)) (time.Time, error) { + expression, err := Parse(s) + if err != nil { + return time.Time{}, err + } + + return expression.Time(opts...), nil +} + +type anchorDateExpression func(opts Options) time.Time + +func anchorDateNow(opts Options) time.Time { + return opts.Now.In(opts.Location) +} + +func anchorDate(t time.Time) func(opts Options) time.Time { + return func(opts Options) time.Time { + location := t.Location() + if location == missingTimeZone { + location = opts.Location + } + + return time.Date(t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute(), t.Second(), t.Nanosecond(), location) + } +} + +type timeAdjuster func(time.Time, Options) time.Time + +func addUnits(factor int, u timeUnit) func(time.Time, Options) time.Time { + return func(t time.Time, options Options) time.Time { + switch u { + case timeUnitYear: + return t.AddDate(factor, 0, 0) + case timeUnitMonth: + return t.AddDate(0, factor, 0) + case timeUnitWeek: + return t.AddDate(0, 0, 7*factor) + case timeUnitDay: + return t.AddDate(0, 0, factor) + case timeUnitBusinessDay: + + fn := options.BusinessDayFunc + if fn == nil { + fn = isNotWeekend + } + + increment := 1 + if factor < 0 { + increment = -1 + } + + for i := factor; i != 0; i -= increment { + t = t.AddDate(0, 0, increment) + for !fn(t) { + t = t.AddDate(0, 0, increment) + } + } + + return t + + case timeUnitHour: + return t.Add(time.Duration(factor) * time.Hour) + case timeUnitMinute: + return t.Add(time.Duration(factor) * time.Minute) + case timeUnitSecond: + return t.Add(time.Duration(factor) * time.Second) + default: + panic(fmt.Sprintf("unknown time unit: %s", u)) + } + } +} + +func truncateUnits(u timeUnit) func(time.Time, Options) time.Time { + var roundDown = func(t time.Time, options Options) time.Time { + switch u { + case timeUnitYear: + return time.Date(t.Year(), 1, 1, 0, 0, 0, 0, t.Location()) + case timeUnitMonth: + return time.Date(t.Year(), t.Month(), 1, 0, 0, 0, 0, t.Location()) + case timeUnitWeek: + diff := int(t.Weekday() - options.StartOfWeek) + if diff < 0 { + return time.Date(t.Year(), t.Month(), t.Day()+diff-1, 0, 0, 0, 0, t.Location()) + } + return time.Date(t.Year(), t.Month(), t.Day()-diff, 0, 0, 0, 0, t.Location()) + case timeUnitDay: + return time.Date(t.Year(), t.Month(), t.Day(), 0, 0, 0, 0, t.Location()) + case timeUnitHour: + return t.Truncate(time.Hour) + case timeUnitMinute: + return t.Truncate(time.Minute) + case timeUnitSecond: + return t.Truncate(time.Second) + default: + panic(fmt.Sprintf("unknown time unit: %s", u)) + } + } + + return func(t time.Time, options Options) time.Time { + if options.RoundUp { + return addUnits(1, u)(roundDown(t, options), options).Add(-time.Millisecond) + } + return roundDown(t, options) + } +} + +func daysIn(m time.Month, year int) int { + return time.Date(year, m+1, 0, 0, 0, 0, 0, time.UTC).Day() +} + +// lexerWrapper wraps the golex generated wrapper to store the parsed expression for later and provide needed data to +// the parser +type lexerWrapper struct { + lex yyLexer + + expression mathExpression +} + +func newLexerWrapper(lex yyLexer) *lexerWrapper { + return &lexerWrapper{ + lex: lex, + } +} + +func (l *lexerWrapper) Lex(lval *yySymType) int { + return l.lex.Lex(lval) +} + +func (l *lexerWrapper) Error(s string) { + l.lex.Error(s) +} diff --git a/vendor/github.com/timberio/go-datemath/datemath.l b/vendor/github.com/timberio/go-datemath/datemath.l new file mode 100644 index 00000000000..aecb5253970 --- /dev/null +++ b/vendor/github.com/timberio/go-datemath/datemath.l @@ -0,0 +1,144 @@ +/* +This file is used with golex to generate a lexer that has a signature compatible with goyacc. + +Many constants referred to below are defined by goyacc when creating template.y.go + +See https://godoc.org/modernc.org/golex for more about golex +*/ + +%{ + +package datemath + +import ( + "bytes" + "fmt" + "strconv" +) + +const ( + // 0 is expected by the goyacc generated parser to indicate EOF + eofCode = 0 +) + +// lexer holds the state of the lexer +type lexer struct { + src *bytes.Reader + + buf []byte + current byte + + pos int + + errors []string +} + +func newLexer(b []byte) *lexer { + l := &lexer{ + src: bytes.NewReader(b), + } + // queue up a byte + l.next() + return l +} + +func (l *lexer) Error(s string) { + l.errors = append(l.errors, fmt.Sprintf("%s at character %d starting with %q", s, l.pos, string(l.buf))) +} + +func (l *lexer) next() { + if l.current != 0 { + l.buf = append(l.buf, l.current) + } + l.current = 0 + if b, err := l.src.ReadByte(); err == nil { + l.current = b + } + l.pos++ +} +func (l *lexer) Lex(lval *yySymType) int { +%} + +/* give some regular expressions more semantic names for use below */ +eof \0 + +/* tell golex how to determine the current start condition */ +%yyt l.startCondition +/* tell golex how to determine the current byte */ +%yyc l.current +/* tell golex how to advance to the next byte */ +%yyn l.next() + +%% + // runs before each token is parsed + l.buf = l.buf[:0] + +[0-9] + i, err := strconv.ParseInt(string(l.buf), 10, 0) + if err != nil { + panic(fmt.Sprintf("could not parse digit as number: %s", err)) + } + lval.i = int(i) + return tDIGIT + +"now" + return tNOW + +"+" + return tPLUS + +"-" + return tMINUS + +":" + return tCOLON + +"||" + return tPIPES + +"/" + return tBACKSLASH + +[yMwdbhHms] + switch l.buf[0] { + case 'y': + lval.unit = timeUnitYear + case 'M': + lval.unit = timeUnitMonth + case 'w': + lval.unit = timeUnitWeek + case 'b': + lval.unit = timeUnitBusinessDay + case 'd': + lval.unit = timeUnitDay + case 'h', 'H': + lval.unit = timeUnitHour + case 'm': + lval.unit = timeUnitMinute + case 's': + lval.unit = timeUnitSecond + default: + panic(fmt.Sprintf("unknown time unit: %q", l.buf[0])) + } + return tUNIT + +\. + return tDOT + +"T" + return tTIME_DELIMITER + +"Z" + return tUTC + +{eof} + return eofCode + +. + return tINVALID_TOKEN + +%% + +// should never get here +panic("scanner internal error") +} diff --git a/vendor/github.com/timberio/go-datemath/datemath.l.go b/vendor/github.com/timberio/go-datemath/datemath.l.go new file mode 100644 index 00000000000..eb9e8301463 --- /dev/null +++ b/vendor/github.com/timberio/go-datemath/datemath.l.go @@ -0,0 +1,296 @@ +// Code generated by golex. DO NOT EDIT. + +/* +This file is used with golex to generate a lexer that has a signature compatible with goyacc. + +Many constants referred to below are defined by goyacc when creating template.y.go + +See https://godoc.org/modernc.org/golex for more about golex +*/ + +package datemath + +import ( + "bytes" + "fmt" + "strconv" +) + +const ( + // 0 is expected by the goyacc generated parser to indicate EOF + eofCode = 0 +) + +// lexer holds the state of the lexer +type lexer struct { + src *bytes.Reader + + buf []byte + current byte + + pos int + + errors []string +} + +func newLexer(b []byte) *lexer { + l := &lexer{ + src: bytes.NewReader(b), + } + // queue up a byte + l.next() + return l +} + +func (l *lexer) Error(s string) { + l.errors = append(l.errors, fmt.Sprintf("%s at character %d starting with %q", s, l.pos, string(l.buf))) +} + +func (l *lexer) next() { + if l.current != 0 { + l.buf = append(l.buf, l.current) + } + l.current = 0 + if b, err := l.src.ReadByte(); err == nil { + l.current = b + } + l.pos++ +} +func (l *lexer) Lex(lval *yySymType) int { + + /* give some regular expressions more semantic names for use below */ + /* tell golex how to determine the current start condition */ + /* tell golex how to determine the current byte */ + /* tell golex how to advance to the next byte */ + +yystate0: + + // runs before each token is parsed + l.buf = l.buf[:0] + + goto yystart1 + +yystate1: + l.next() +yystart1: + switch { + default: + goto yyabort + case l.current == '+': + goto yystate4 + case l.current == '-': + goto yystate5 + case l.current == '.': + goto yystate6 + case l.current == '/': + goto yystate7 + case l.current == ':': + goto yystate9 + case l.current == 'H' || l.current == 'M' || l.current == 'b' || l.current == 'd' || l.current == 'h' || l.current == 'm' || l.current == 's' || l.current == 'w' || l.current == 'y': + goto yystate10 + case l.current == 'T': + goto yystate11 + case l.current == 'Z': + goto yystate12 + case l.current == '\x00': + goto yystate2 + case l.current == 'n': + goto yystate13 + case l.current == '|': + goto yystate16 + case l.current >= '0' && l.current <= '9': + goto yystate8 + case l.current >= '\x01' && l.current <= '\t' || l.current >= '\v' && l.current <= '*' || l.current == ',' || l.current >= ';' && l.current <= 'G' || l.current >= 'I' && l.current <= 'L' || l.current >= 'N' && l.current <= 'S' || l.current >= 'U' && l.current <= 'Y' || l.current >= '[' && l.current <= 'a' || l.current == 'c' || l.current >= 'e' && l.current <= 'g' || l.current >= 'i' && l.current <= 'l' || l.current >= 'o' && l.current <= 'r' || l.current >= 't' && l.current <= 'v' || l.current == 'x' || l.current == 'z' || l.current == '{' || l.current >= '}' && l.current <= 'ÿ': + goto yystate3 + } + +yystate2: + l.next() + goto yyrule12 + +yystate3: + l.next() + goto yyrule13 + +yystate4: + l.next() + goto yyrule3 + +yystate5: + l.next() + goto yyrule4 + +yystate6: + l.next() + goto yyrule9 + +yystate7: + l.next() + goto yyrule7 + +yystate8: + l.next() + goto yyrule1 + +yystate9: + l.next() + goto yyrule5 + +yystate10: + l.next() + goto yyrule8 + +yystate11: + l.next() + goto yyrule10 + +yystate12: + l.next() + goto yyrule11 + +yystate13: + l.next() + switch { + default: + goto yyrule13 + case l.current == 'o': + goto yystate14 + } + +yystate14: + l.next() + switch { + default: + goto yyabort + case l.current == 'w': + goto yystate15 + } + +yystate15: + l.next() + goto yyrule2 + +yystate16: + l.next() + switch { + default: + goto yyrule13 + case l.current == '|': + goto yystate17 + } + +yystate17: + l.next() + goto yyrule6 + +yyrule1: // [0-9] + { + + i, err := strconv.ParseInt(string(l.buf), 10, 0) + if err != nil { + panic(fmt.Sprintf("could not parse digit as number: %s", err)) + } + lval.i = int(i) + return tDIGIT + } +yyrule2: // "now" + { + + return tNOW + } +yyrule3: // "+" + { + + return tPLUS + } +yyrule4: // "-" + { + + return tMINUS + } +yyrule5: // ":" + { + + return tCOLON + } +yyrule6: // "||" + { + + return tPIPES + } +yyrule7: // "/" + { + + return tBACKSLASH + } +yyrule8: // [yMwdbhHms] + { + + switch l.buf[0] { + case 'y': + lval.unit = timeUnitYear + case 'M': + lval.unit = timeUnitMonth + case 'w': + lval.unit = timeUnitWeek + case 'b': + lval.unit = timeUnitBusinessDay + case 'd': + lval.unit = timeUnitDay + case 'h', 'H': + lval.unit = timeUnitHour + case 'm': + lval.unit = timeUnitMinute + case 's': + lval.unit = timeUnitSecond + default: + panic(fmt.Sprintf("unknown time unit: %q", l.buf[0])) + } + return tUNIT + } +yyrule9: // \. + { + + return tDOT + } +yyrule10: // "T" + { + + return tTIME_DELIMITER + } +yyrule11: // "Z" + { + + return tUTC + } +yyrule12: // {eof} + { + + return eofCode + } +yyrule13: // . + if true { // avoid go vet determining the below panic will not be reached + + return tINVALID_TOKEN + } + panic("unreachable") + +yyabort: // no lexem recognized + // + // silence unused label errors for build and satisfy go vet reachability analysis + // + { + if false { + goto yyabort + } + if false { + goto yystate0 + } + if false { + goto yystate1 + } + } + + // should never get here + panic("scanner internal error") +} diff --git a/vendor/github.com/timberio/go-datemath/datemath.y b/vendor/github.com/timberio/go-datemath/datemath.y new file mode 100644 index 00000000000..bc8e605fa5f --- /dev/null +++ b/vendor/github.com/timberio/go-datemath/datemath.y @@ -0,0 +1,300 @@ +/* +This file is used with goyacc to generate a parser. + +See https://godoc.org/golang.org/x/tools/cmd/goyacc for more about goyacc. +*/ + +%{ +package datemath + +import ( + "fmt" + "math" + "time" +) + +var epoch = time.Unix(0, 0).In(time.UTC) + +// convert a list of significant digits to an integer +// assumes most to least significant +// e.g. 5,2,3 -> 523 +func digitsToInt(digits ...int) int { + n := 0 + for i := range digits { + n += digits[i] * int(math.Pow10(len(digits)-i-1)) + } + return n +} +%} + +/* set of valid tokens; generated constants used by lexer */ +%token tNOW tPLUS tMINUS tPIPES tBACKSLASH tTIME_DELIMITER tCOLON tDOT tUNIT tUTC tDIGIT tINVALID_TOKEN + +/* Go variables to hold the corresponding token values */ +%union { +i64 int64 +i int +unit timeUnit +month time.Month + +expression mathExpression +anchorDateExpression anchorDateExpression +timeAdjuster timeAdjuster +timeAdjusters []timeAdjuster + +location *time.Location +time time.Time +} + +/* associate tokens with Go types */ +%type tUNIT +%type sign factor number year day hour minute second nanoseconds tDIGIT +%type month +%type expression +%type