From 5d23ad0ca7aea9efb4115af73490da6f8e9d7175 Mon Sep 17 00:00:00 2001 From: Ben Kochie Date: Mon, 6 Aug 2018 16:54:46 +0200 Subject: [PATCH] Fix supervisord collector (#978) * Replace supervisord xmlrpc library * Use `github.com/mattn/go-xmlrpc` that doesn't leak goroutines. * Fix uptime metric * Use Prometheus best practices for uptime metric. * Use "start time" rather than "uptime". * Don't emit a start time if the process is down. * Add changelog entry. * Add example compatibility rules. Signed-off-by: Ben Kochie --- CHANGELOG.md | 4 + collector/supervisord.go | 55 ++- ...mple-17-compatibility-rules-new-to-old.yml | 5 + docs/example-17-compatibility-rules.yml | 5 + vendor/github.com/kolo/xmlrpc/README.md | 79 --- vendor/github.com/kolo/xmlrpc/client.go | 144 ------ vendor/github.com/kolo/xmlrpc/decoder.go | 449 ------------------ vendor/github.com/kolo/xmlrpc/encoder.go | 164 ------- vendor/github.com/kolo/xmlrpc/request.go | 57 --- vendor/github.com/kolo/xmlrpc/response.go | 52 -- vendor/github.com/kolo/xmlrpc/test_server.rb | 25 - vendor/github.com/kolo/xmlrpc/xmlrpc.go | 19 - .../{kolo/xmlrpc => mattn/go-xmlrpc}/LICENSE | 12 +- vendor/github.com/mattn/go-xmlrpc/README.md | 48 ++ vendor/github.com/mattn/go-xmlrpc/xmlrpc.go | 365 ++++++++++++++ vendor/vendor.json | 12 +- 16 files changed, 477 insertions(+), 1018 deletions(-) create mode 100644 docs/example-17-compatibility-rules-new-to-old.yml create mode 100644 docs/example-17-compatibility-rules.yml delete mode 100644 vendor/github.com/kolo/xmlrpc/README.md delete mode 100644 vendor/github.com/kolo/xmlrpc/client.go delete mode 100644 vendor/github.com/kolo/xmlrpc/decoder.go delete mode 100644 vendor/github.com/kolo/xmlrpc/encoder.go delete mode 100644 vendor/github.com/kolo/xmlrpc/request.go delete mode 100644 vendor/github.com/kolo/xmlrpc/response.go delete mode 100644 vendor/github.com/kolo/xmlrpc/test_server.rb delete mode 100644 vendor/github.com/kolo/xmlrpc/xmlrpc.go rename vendor/github.com/{kolo/xmlrpc => mattn/go-xmlrpc}/LICENSE (86%) create mode 100644 vendor/github.com/mattn/go-xmlrpc/README.md create mode 100644 vendor/github.com/mattn/go-xmlrpc/xmlrpc.go diff --git a/CHANGELOG.md b/CHANGELOG.md index 45f7e262a3..78f4d9570e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ **Breaking changes** +supvervisord collector reports "start_time_seconds" rather than "uptime" + * [CHANGE] Filter out non-installed units when collecting all systemd units #1011 * [FEATURE] Collect NRefused property for systemd socket units (available as of systemd v239) * [FEATURE] Collect NRestarts property for systemd service units @@ -10,6 +12,8 @@ * [ENHANCEMENT] * [BUGFIX] +* [BUGFIX] Fix goroutine leak in supervisord collector + ## 0.16.0 / 2018-05-15 **Breaking changes** diff --git a/collector/supervisord.go b/collector/supervisord.go index 20fa36f407..eb6720b87f 100644 --- a/collector/supervisord.go +++ b/collector/supervisord.go @@ -16,7 +16,9 @@ package collector import ( - "github.com/kolo/xmlrpc" + "fmt" + + "github.com/mattn/go-xmlrpc" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/common/log" "gopkg.in/alecthomas/kingpin.v2" @@ -27,11 +29,10 @@ var ( ) type supervisordCollector struct { - client *xmlrpc.Client upDesc *prometheus.Desc stateDesc *prometheus.Desc exitStatusDesc *prometheus.Desc - uptimeDesc *prometheus.Desc + startTimeDesc *prometheus.Desc } func init() { @@ -40,17 +41,11 @@ func init() { // NewSupervisordCollector returns a new Collector exposing supervisord statistics. func NewSupervisordCollector() (Collector, error) { - client, err := xmlrpc.NewClient(*supervisordURL, nil) - if err != nil { - return nil, err - } - var ( subsystem = "supervisord" labelNames = []string{"name", "group"} ) return &supervisordCollector{ - client: client, upDesc: prometheus.NewDesc( prometheus.BuildFQName(namespace, subsystem, "up"), "Process Up", @@ -69,9 +64,9 @@ func NewSupervisordCollector() (Collector, error) { labelNames, nil, ), - uptimeDesc: prometheus.NewDesc( - prometheus.BuildFQName(namespace, subsystem, "uptime"), - "Process Uptime", + startTimeDesc: prometheus.NewDesc( + prometheus.BuildFQName(namespace, subsystem, "start_time_seconds"), + "Process start time", labelNames, nil, ), @@ -98,7 +93,7 @@ func (c *supervisordCollector) isRunning(state int) bool { } func (c *supervisordCollector) Update(ch chan<- prometheus.Metric) error { - var infos []struct { + var info struct { Name string `xmlrpc:"name"` Group string `xmlrpc:"group"` Start int `xmlrpc:"start"` @@ -112,10 +107,35 @@ func (c *supervisordCollector) Update(ch chan<- prometheus.Metric) error { StderrLogfile string `xmlrcp:"stderr_logfile"` PID int `xmlrpc:"pid"` } - if err := c.client.Call("supervisor.getAllProcessInfo", nil, &infos); err != nil { - return err + + res, err := xmlrpc.Call(*supervisordURL, "supervisor.getAllProcessInfo") + if err != nil { + return fmt.Errorf("unable to call supervisord: %s", err) } - for _, info := range infos { + + for _, p := range res.(xmlrpc.Array) { + for k, v := range p.(xmlrpc.Struct) { + switch k { + case "name": + info.Name = v.(string) + case "group": + info.Group = v.(string) + case "start": + info.Start = v.(int) + case "stop": + info.Stop = v.(int) + case "now": + info.Now = v.(int) + case "state": + info.State = v.(int) + case "statename": + info.StateName = v.(string) + case "exitstatus": + info.ExitStatus = v.(int) + case "pid": + info.PID = v.(int) + } + } labels := []string{info.Name, info.Group} ch <- prometheus.MustNewConstMetric(c.stateDesc, prometheus.GaugeValue, float64(info.State), labels...) @@ -123,10 +143,9 @@ func (c *supervisordCollector) Update(ch chan<- prometheus.Metric) error { if c.isRunning(info.State) { ch <- prometheus.MustNewConstMetric(c.upDesc, prometheus.GaugeValue, 1, labels...) - ch <- prometheus.MustNewConstMetric(c.uptimeDesc, prometheus.CounterValue, float64(info.Now-info.Start), labels...) + ch <- prometheus.MustNewConstMetric(c.startTimeDesc, prometheus.CounterValue, float64(info.Start), labels...) } else { ch <- prometheus.MustNewConstMetric(c.upDesc, prometheus.GaugeValue, 0, labels...) - ch <- prometheus.MustNewConstMetric(c.uptimeDesc, prometheus.CounterValue, 0, labels...) } log.Debugf("%s:%s is %s on pid %d", info.Group, info.Name, info.StateName, info.PID) } diff --git a/docs/example-17-compatibility-rules-new-to-old.yml b/docs/example-17-compatibility-rules-new-to-old.yml new file mode 100644 index 0000000000..c6db522b3a --- /dev/null +++ b/docs/example-17-compatibility-rules-new-to-old.yml @@ -0,0 +1,5 @@ +groups: +- name: node_exporter-17-supervisord + rules: + - record: node_supervisord_start_time_seconds + expr: node_supervisord_uptime + time() diff --git a/docs/example-17-compatibility-rules.yml b/docs/example-17-compatibility-rules.yml new file mode 100644 index 0000000000..6fbeaa9122 --- /dev/null +++ b/docs/example-17-compatibility-rules.yml @@ -0,0 +1,5 @@ +groups: +- name: node_exporter-17-supervisord + rules: + - record: node_supervisord_uptime + expr: time() - node_supervisord_start_time_seconds diff --git a/vendor/github.com/kolo/xmlrpc/README.md b/vendor/github.com/kolo/xmlrpc/README.md deleted file mode 100644 index 12b7692e90..0000000000 --- a/vendor/github.com/kolo/xmlrpc/README.md +++ /dev/null @@ -1,79 +0,0 @@ -## Overview - -xmlrpc is an implementation of client side part of XMLRPC protocol in Go language. - -## Installation - -To install xmlrpc package run `go get github.com/kolo/xmlrpc`. To use -it in application add `"github.com/kolo/xmlrpc"` string to `import` -statement. - -## Usage - - client, _ := xmlrpc.NewClient("https://bugzilla.mozilla.org/xmlrpc.cgi", nil) - result := struct{ - Version string `xmlrpc:"version"` - }{} - client.Call("Bugzilla.version", nil, &result) - fmt.Printf("Version: %s\n", result.Version) // Version: 4.2.7+ - -Second argument of NewClient function is an object that implements -[http.RoundTripper](http://golang.org/pkg/net/http/#RoundTripper) -interface, it can be used to get more control over connection options. -By default it initialized by http.DefaultTransport object. - -### Arguments encoding - -xmlrpc package supports encoding of native Go data types to method -arguments. - -Data types encoding rules: -* int, int8, int16, int32, int64 encoded to int; -* float32, float64 encoded to double; -* bool encoded to boolean; -* string encoded to string; -* time.Time encoded to datetime.iso8601; -* xmlrpc.Base64 encoded to base64; -* slice decoded to array; - -Structs decoded to struct by following rules: -* all public field become struct members; -* field name become member name; -* if field has xmlrpc tag, its value become member name. - -Server method can accept few arguments, to handle this case there is -special approach to handle slice of empty interfaces (`[]interface{}`). -Each value of such slice encoded as separate argument. - -### Result decoding - -Result of remote function is decoded to native Go data type. - -Data types decoding rules: -* int, i4 decoded to int, int8, int16, int32, int64; -* double decoded to float32, float64; -* boolean decoded to bool; -* string decoded to string; -* array decoded to slice; -* structs decoded following the rules described in previous section; -* datetime.iso8601 decoded as time.Time data type; -* base64 decoded to string. - -## Implementation details - -xmlrpc package contains clientCodec type, that implements [rpc.ClientCodec](http://golang.org/pkg/net/rpc/#ClientCodec) -interface of [net/rpc](http://golang.org/pkg/net/rpc) package. - -xmlrpc package works over HTTP protocol, but some internal functions -and data type were made public to make it easier to create another -implementation of xmlrpc that works over another protocol. To encode -request body there is EncodeMethodCall function. To decode server -response Response data type can be used. - -## Contribution - -Feel free to fork the project, submit pull requests, ask questions. - -## Authors - -Dmitry Maksimov (dmtmax@gmail.com) diff --git a/vendor/github.com/kolo/xmlrpc/client.go b/vendor/github.com/kolo/xmlrpc/client.go deleted file mode 100644 index fb66b65fbc..0000000000 --- a/vendor/github.com/kolo/xmlrpc/client.go +++ /dev/null @@ -1,144 +0,0 @@ -package xmlrpc - -import ( - "fmt" - "io/ioutil" - "net/http" - "net/http/cookiejar" - "net/rpc" - "net/url" -) - -type Client struct { - *rpc.Client -} - -// clientCodec is rpc.ClientCodec interface implementation. -type clientCodec struct { - // url presents url of xmlrpc service - url *url.URL - - // httpClient works with HTTP protocol - httpClient *http.Client - - // cookies stores cookies received on last request - cookies http.CookieJar - - // responses presents map of active requests. It is required to return request id, that - // rpc.Client can mark them as done. - responses map[uint64]*http.Response - - response *Response - - // ready presents channel, that is used to link request and it`s response. - ready chan uint64 -} - -func (codec *clientCodec) WriteRequest(request *rpc.Request, args interface{}) (err error) { - httpRequest, err := NewRequest(codec.url.String(), request.ServiceMethod, args) - - if codec.cookies != nil { - for _, cookie := range codec.cookies.Cookies(codec.url) { - httpRequest.AddCookie(cookie) - } - } - - if err != nil { - return err - } - - var httpResponse *http.Response - httpResponse, err = codec.httpClient.Do(httpRequest) - - if err != nil { - return err - } - - if codec.cookies != nil { - codec.cookies.SetCookies(codec.url, httpResponse.Cookies()) - } - - codec.responses[request.Seq] = httpResponse - codec.ready <- request.Seq - - return nil -} - -func (codec *clientCodec) ReadResponseHeader(response *rpc.Response) (err error) { - seq := <-codec.ready - httpResponse := codec.responses[seq] - - if httpResponse.StatusCode < 200 || httpResponse.StatusCode >= 300 { - return fmt.Errorf("request error: bad status code - %d", httpResponse.StatusCode) - } - - respData, err := ioutil.ReadAll(httpResponse.Body) - - if err != nil { - return err - } - - httpResponse.Body.Close() - - resp := NewResponse(respData) - - if resp.Failed() { - response.Error = fmt.Sprintf("%v", resp.Err()) - } - - codec.response = resp - - response.Seq = seq - delete(codec.responses, seq) - - return nil -} - -func (codec *clientCodec) ReadResponseBody(v interface{}) (err error) { - if v == nil { - return nil - } - - if err = codec.response.Unmarshal(v); err != nil { - return err - } - - return nil -} - -func (codec *clientCodec) Close() error { - transport := codec.httpClient.Transport.(*http.Transport) - transport.CloseIdleConnections() - return nil -} - -// NewClient returns instance of rpc.Client object, that is used to send request to xmlrpc service. -func NewClient(requrl string, transport http.RoundTripper) (*Client, error) { - if transport == nil { - transport = http.DefaultTransport - } - - httpClient := &http.Client{Transport: transport} - - jar, err := cookiejar.New(nil) - - if err != nil { - return nil, err - } - - u, err := url.Parse(requrl) - - if err != nil { - return nil, err - } - - codec := clientCodec{ - url: u, - httpClient: httpClient, - ready: make(chan uint64), - responses: make(map[uint64]*http.Response), - cookies: jar, - } - - return &Client{rpc.NewClientWithCodec(&codec)}, nil -} diff --git a/vendor/github.com/kolo/xmlrpc/decoder.go b/vendor/github.com/kolo/xmlrpc/decoder.go deleted file mode 100644 index b73955978e..0000000000 --- a/vendor/github.com/kolo/xmlrpc/decoder.go +++ /dev/null @@ -1,449 +0,0 @@ -package xmlrpc - -import ( - "bytes" - "encoding/xml" - "errors" - "fmt" - "io" - "reflect" - "strconv" - "strings" - "time" -) - -const iso8601 = "20060102T15:04:05" - -var ( - // CharsetReader is a function to generate reader which converts a non UTF-8 - // charset into UTF-8. - CharsetReader func(string, io.Reader) (io.Reader, error) - - invalidXmlError = errors.New("invalid xml") -) - -type TypeMismatchError string - -func (e TypeMismatchError) Error() string { return string(e) } - -type decoder struct { - *xml.Decoder -} - -func unmarshal(data []byte, v interface{}) (err error) { - dec := &decoder{xml.NewDecoder(bytes.NewBuffer(data))} - - if CharsetReader != nil { - dec.CharsetReader = CharsetReader - } - - var tok xml.Token - for { - if tok, err = dec.Token(); err != nil { - return err - } - - if t, ok := tok.(xml.StartElement); ok { - if t.Name.Local == "value" { - val := reflect.ValueOf(v) - if val.Kind() != reflect.Ptr { - return errors.New("non-pointer value passed to unmarshal") - } - if err = dec.decodeValue(val.Elem()); err != nil { - return err - } - - break - } - } - } - - // read until end of document - err = dec.Skip() - if err != nil && err != io.EOF { - return err - } - - return nil -} - -func (dec *decoder) decodeValue(val reflect.Value) error { - var tok xml.Token - var err error - - if val.Kind() == reflect.Ptr { - if val.IsNil() { - val.Set(reflect.New(val.Type().Elem())) - } - val = val.Elem() - } - - var typeName string - for { - if tok, err = dec.Token(); err != nil { - return err - } - - if t, ok := tok.(xml.EndElement); ok { - if t.Name.Local == "value" { - return nil - } else { - return invalidXmlError - } - } - - if t, ok := tok.(xml.StartElement); ok { - typeName = t.Name.Local - break - } - - // Treat value data without type identifier as string - if t, ok := tok.(xml.CharData); ok { - if value := strings.TrimSpace(string(t)); value != "" { - if err = checkType(val, reflect.String); err != nil { - return err - } - - val.SetString(value) - return nil - } - } - } - - switch typeName { - case "struct": - ismap := false - pmap := val - valType := val.Type() - - if err = checkType(val, reflect.Struct); err != nil { - if checkType(val, reflect.Map) == nil { - if valType.Key().Kind() != reflect.String { - return fmt.Errorf("only maps with string key type can be unmarshalled") - } - ismap = true - } else if checkType(val, reflect.Interface) == nil && val.IsNil() { - var dummy map[string]interface{} - pmap = reflect.New(reflect.TypeOf(dummy)).Elem() - valType = pmap.Type() - ismap = true - } else { - return err - } - } - - var fields map[string]reflect.Value - - if !ismap { - fields = make(map[string]reflect.Value) - - for i := 0; i < valType.NumField(); i++ { - field := valType.Field(i) - fieldVal := val.FieldByName(field.Name) - - if fieldVal.CanSet() { - if fn := field.Tag.Get("xmlrpc"); fn != "" { - fields[fn] = fieldVal - } else { - fields[field.Name] = fieldVal - } - } - } - } else { - // Create initial empty map - pmap.Set(reflect.MakeMap(valType)) - } - - // Process struct members. - StructLoop: - for { - if tok, err = dec.Token(); err != nil { - return err - } - switch t := tok.(type) { - case xml.StartElement: - if t.Name.Local != "member" { - return invalidXmlError - } - - tagName, fieldName, err := dec.readTag() - if err != nil { - return err - } - if tagName != "name" { - return invalidXmlError - } - - var fv reflect.Value - ok := true - - if !ismap { - fv, ok = fields[string(fieldName)] - } else { - fv = reflect.New(valType.Elem()) - } - - if ok { - for { - if tok, err = dec.Token(); err != nil { - return err - } - if t, ok := tok.(xml.StartElement); ok && t.Name.Local == "value" { - if err = dec.decodeValue(fv); err != nil { - return err - } - - // - if err = dec.Skip(); err != nil { - return err - } - - break - } - } - } - - // - if err = dec.Skip(); err != nil { - return err - } - - if ismap { - pmap.SetMapIndex(reflect.ValueOf(string(fieldName)), reflect.Indirect(fv)) - val.Set(pmap) - } - case xml.EndElement: - break StructLoop - } - } - case "array": - pslice := val - if checkType(val, reflect.Interface) == nil && val.IsNil() { - var dummy []interface{} - pslice = reflect.New(reflect.TypeOf(dummy)).Elem() - } else if err = checkType(val, reflect.Slice); err != nil { - return err - } - - ArrayLoop: - for { - if tok, err = dec.Token(); err != nil { - return err - } - - switch t := tok.(type) { - case xml.StartElement: - if t.Name.Local != "data" { - return invalidXmlError - } - - slice := reflect.MakeSlice(pslice.Type(), 0, 0) - - DataLoop: - for { - if tok, err = dec.Token(); err != nil { - return err - } - - switch tt := tok.(type) { - case xml.StartElement: - if tt.Name.Local != "value" { - return invalidXmlError - } - - v := reflect.New(pslice.Type().Elem()) - if err = dec.decodeValue(v); err != nil { - return err - } - - slice = reflect.Append(slice, v.Elem()) - - // - if err = dec.Skip(); err != nil { - return err - } - case xml.EndElement: - pslice.Set(slice) - val.Set(pslice) - break DataLoop - } - } - case xml.EndElement: - break ArrayLoop - } - } - default: - if tok, err = dec.Token(); err != nil { - return err - } - - var data []byte - - switch t := tok.(type) { - case xml.EndElement: - return nil - case xml.CharData: - data = []byte(t.Copy()) - default: - return invalidXmlError - } - - switch typeName { - case "int", "i4", "i8": - if checkType(val, reflect.Interface) == nil && val.IsNil() { - i, err := strconv.ParseInt(string(data), 10, 64) - if err != nil { - return err - } - - pi := reflect.New(reflect.TypeOf(i)).Elem() - pi.SetInt(i) - val.Set(pi) - } else if err = checkType(val, reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64); err != nil { - return err - } else { - i, err := strconv.ParseInt(string(data), 10, val.Type().Bits()) - if err != nil { - return err - } - - val.SetInt(i) - } - case "string", "base64": - str := string(data) - if checkType(val, reflect.Interface) == nil && val.IsNil() { - pstr := reflect.New(reflect.TypeOf(str)).Elem() - pstr.SetString(str) - val.Set(pstr) - } else if err = checkType(val, reflect.String); err != nil { - return err - } else { - val.SetString(str) - } - case "dateTime.iso8601": - t, err := time.Parse(iso8601, string(data)) - if err != nil { - return err - } - - if checkType(val, reflect.Interface) == nil && val.IsNil() { - ptime := reflect.New(reflect.TypeOf(t)).Elem() - ptime.Set(reflect.ValueOf(t)) - val.Set(ptime) - } else if _, ok := val.Interface().(time.Time); !ok { - return TypeMismatchError(fmt.Sprintf("error: type mismatch error - can't decode %v to time", val.Kind())) - } else { - val.Set(reflect.ValueOf(t)) - } - case "boolean": - v, err := strconv.ParseBool(string(data)) - if err != nil { - return err - } - - if checkType(val, reflect.Interface) == nil && val.IsNil() { - pv := reflect.New(reflect.TypeOf(v)).Elem() - pv.SetBool(v) - val.Set(pv) - } else if err = checkType(val, reflect.Bool); err != nil { - return err - } else { - val.SetBool(v) - } - case "double": - if checkType(val, reflect.Interface) == nil && val.IsNil() { - i, err := strconv.ParseFloat(string(data), 64) - if err != nil { - return err - } - - pdouble := reflect.New(reflect.TypeOf(i)).Elem() - pdouble.SetFloat(i) - val.Set(pdouble) - } else if err = checkType(val, reflect.Float32, reflect.Float64); err != nil { - return err - } else { - i, err := strconv.ParseFloat(string(data), val.Type().Bits()) - if err != nil { - return err - } - - val.SetFloat(i) - } - default: - return errors.New("unsupported type") - } - - // - if err = dec.Skip(); err != nil { - return err - } - } - - return nil -} - -func (dec *decoder) readTag() (string, []byte, error) { - var tok xml.Token - var err error - - var name string - for { - if tok, err = dec.Token(); err != nil { - return "", nil, err - } - - if t, ok := tok.(xml.StartElement); ok { - name = t.Name.Local - break - } - } - - value, err := dec.readCharData() - if err != nil { - return "", nil, err - } - - return name, value, dec.Skip() -} - -func (dec *decoder) readCharData() ([]byte, error) { - var tok xml.Token - var err error - - if tok, err = dec.Token(); err != nil { - return nil, err - } - - if t, ok := tok.(xml.CharData); ok { - return []byte(t.Copy()), nil - } else { - return nil, invalidXmlError - } -} - -func checkType(val reflect.Value, kinds ...reflect.Kind) error { - if len(kinds) == 0 { - return nil - } - - if val.Kind() == reflect.Ptr { - val = val.Elem() - } - - match := false - - for _, kind := range kinds { - if val.Kind() == kind { - match = true - break - } - } - - if !match { - return TypeMismatchError(fmt.Sprintf("error: type mismatch - can't unmarshal %v to %v", - val.Kind(), kinds[0])) - } - - return nil -} diff --git a/vendor/github.com/kolo/xmlrpc/encoder.go b/vendor/github.com/kolo/xmlrpc/encoder.go deleted file mode 100644 index bb1285ff7a..0000000000 --- a/vendor/github.com/kolo/xmlrpc/encoder.go +++ /dev/null @@ -1,164 +0,0 @@ -package xmlrpc - -import ( - "bytes" - "encoding/xml" - "fmt" - "reflect" - "strconv" - "time" -) - -type encodeFunc func(reflect.Value) ([]byte, error) - -func marshal(v interface{}) ([]byte, error) { - if v == nil { - return []byte{}, nil - } - - val := reflect.ValueOf(v) - return encodeValue(val) -} - -func encodeValue(val reflect.Value) ([]byte, error) { - var b []byte - var err error - - if val.Kind() == reflect.Ptr || val.Kind() == reflect.Interface { - if val.IsNil() { - return []byte(""), nil - } - - val = val.Elem() - } - - switch val.Kind() { - case reflect.Struct: - switch val.Interface().(type) { - case time.Time: - t := val.Interface().(time.Time) - b = []byte(fmt.Sprintf("%s", t.Format(iso8601))) - default: - b, err = encodeStruct(val) - } - case reflect.Map: - b, err = encodeMap(val) - case reflect.Slice: - b, err = encodeSlice(val) - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - b = []byte(fmt.Sprintf("%s", strconv.FormatInt(val.Int(), 10))) - case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: - b = []byte(fmt.Sprintf("%s", strconv.FormatUint(val.Uint(), 10))) - case reflect.Float32, reflect.Float64: - b = []byte(fmt.Sprintf("%s", - strconv.FormatFloat(val.Float(), 'g', -1, val.Type().Bits()))) - case reflect.Bool: - if val.Bool() { - b = []byte("1") - } else { - b = []byte("0") - } - case reflect.String: - var buf bytes.Buffer - - xml.Escape(&buf, []byte(val.String())) - - if _, ok := val.Interface().(Base64); ok { - b = []byte(fmt.Sprintf("%s", buf.String())) - } else { - b = []byte(fmt.Sprintf("%s", buf.String())) - } - default: - return nil, fmt.Errorf("xmlrpc encode error: unsupported type") - } - - if err != nil { - return nil, err - } - - return []byte(fmt.Sprintf("%s", string(b))), nil -} - -func encodeStruct(val reflect.Value) ([]byte, error) { - var b bytes.Buffer - - b.WriteString("") - - t := val.Type() - for i := 0; i < t.NumField(); i++ { - b.WriteString("") - f := t.Field(i) - - name := f.Tag.Get("xmlrpc") - if name == "" { - name = f.Name - } - b.WriteString(fmt.Sprintf("%s", name)) - - p, err := encodeValue(val.FieldByName(f.Name)) - if err != nil { - return nil, err - } - b.Write(p) - - b.WriteString("") - } - - b.WriteString("") - - return b.Bytes(), nil -} - -func encodeMap(val reflect.Value) ([]byte, error) { - var t = val.Type() - - if t.Key().Kind() != reflect.String { - return nil, fmt.Errorf("xmlrpc encode error: only maps with string keys are supported") - } - - var b bytes.Buffer - - b.WriteString("") - - keys := val.MapKeys() - - for i := 0; i < val.Len(); i++ { - key := keys[i] - kval := val.MapIndex(key) - - b.WriteString("") - b.WriteString(fmt.Sprintf("%s", key.String())) - - p, err := encodeValue(kval) - - if err != nil { - return nil, err - } - - b.Write(p) - b.WriteString("") - } - - b.WriteString("") - - return b.Bytes(), nil -} - -func encodeSlice(val reflect.Value) ([]byte, error) { - var b bytes.Buffer - - b.WriteString("") - - for i := 0; i < val.Len(); i++ { - p, err := encodeValue(val.Index(i)) - if err != nil { - return nil, err - } - - b.Write(p) - } - - b.WriteString("") - - return b.Bytes(), nil -} diff --git a/vendor/github.com/kolo/xmlrpc/request.go b/vendor/github.com/kolo/xmlrpc/request.go deleted file mode 100644 index acb8251b2b..0000000000 --- a/vendor/github.com/kolo/xmlrpc/request.go +++ /dev/null @@ -1,57 +0,0 @@ -package xmlrpc - -import ( - "bytes" - "fmt" - "net/http" -) - -func NewRequest(url string, method string, args interface{}) (*http.Request, error) { - var t []interface{} - var ok bool - if t, ok = args.([]interface{}); !ok { - if args != nil { - t = []interface{}{args} - } - } - - body, err := EncodeMethodCall(method, t...) - if err != nil { - return nil, err - } - - request, err := http.NewRequest("POST", url, bytes.NewReader(body)) - if err != nil { - return nil, err - } - - request.Header.Set("Content-Type", "text/xml") - request.Header.Set("Content-Length", fmt.Sprintf("%d", len(body))) - - return request, nil -} - -func EncodeMethodCall(method string, args ...interface{}) ([]byte, error) { - var b bytes.Buffer - b.WriteString(``) - b.WriteString(fmt.Sprintf("%s", method)) - - if args != nil { - b.WriteString("") - - for _, arg := range args { - p, err := marshal(arg) - if err != nil { - return nil, err - } - - b.WriteString(fmt.Sprintf("%s", string(p))) - } - - b.WriteString("") - } - - b.WriteString("") - - return b.Bytes(), nil -} diff --git a/vendor/github.com/kolo/xmlrpc/response.go b/vendor/github.com/kolo/xmlrpc/response.go deleted file mode 100644 index 6742a1c748..0000000000 --- a/vendor/github.com/kolo/xmlrpc/response.go +++ /dev/null @@ -1,52 +0,0 @@ -package xmlrpc - -import ( - "regexp" -) - -var ( - faultRx = regexp.MustCompile(`(\s|\S)+`) -) - -type failedResponse struct { - Code int `xmlrpc:"faultCode"` - Error string `xmlrpc:"faultString"` -} - -func (r *failedResponse) err() error { - return &xmlrpcError{ - code: r.Code, - err: r.Error, - } -} - -type Response struct { - data []byte -} - -func NewResponse(data []byte) *Response { - return &Response{ - data: data, - } -} - -func (r *Response) Failed() bool { - return faultRx.Match(r.data) -} - -func (r *Response) Err() error { - failedResp := new(failedResponse) - if err := unmarshal(r.data, failedResp); err != nil { - return err - } - - return failedResp.err() -} - -func (r *Response) Unmarshal(v interface{}) error { - if err := unmarshal(r.data, v); err != nil { - return err - } - - return nil -} diff --git a/vendor/github.com/kolo/xmlrpc/test_server.rb b/vendor/github.com/kolo/xmlrpc/test_server.rb deleted file mode 100644 index 1b1ff8760f..0000000000 --- a/vendor/github.com/kolo/xmlrpc/test_server.rb +++ /dev/null @@ -1,25 +0,0 @@ -# encoding: utf-8 - -require "xmlrpc/server" - -class Service - def time - Time.now - end - - def upcase(s) - s.upcase - end - - def sum(x, y) - x + y - end - - def error - raise XMLRPC::FaultException.new(500, "Server error") - end -end - -server = XMLRPC::Server.new 5001, 'localhost' -server.add_handler "service", Service.new -server.serve diff --git a/vendor/github.com/kolo/xmlrpc/xmlrpc.go b/vendor/github.com/kolo/xmlrpc/xmlrpc.go deleted file mode 100644 index 8766403afe..0000000000 --- a/vendor/github.com/kolo/xmlrpc/xmlrpc.go +++ /dev/null @@ -1,19 +0,0 @@ -package xmlrpc - -import ( - "fmt" -) - -// xmlrpcError represents errors returned on xmlrpc request. -type xmlrpcError struct { - code int - err string -} - -// Error() method implements Error interface -func (e *xmlrpcError) Error() string { - return fmt.Sprintf("error: \"%s\" code: %d", e.err, e.code) -} - -// Base64 represents value in base64 encoding -type Base64 string diff --git a/vendor/github.com/kolo/xmlrpc/LICENSE b/vendor/github.com/mattn/go-xmlrpc/LICENSE similarity index 86% rename from vendor/github.com/kolo/xmlrpc/LICENSE rename to vendor/github.com/mattn/go-xmlrpc/LICENSE index 8103dd1391..740fa93132 100644 --- a/vendor/github.com/kolo/xmlrpc/LICENSE +++ b/vendor/github.com/mattn/go-xmlrpc/LICENSE @@ -1,4 +1,6 @@ -Copyright (C) 2012 Dmitry Maksimov +The MIT License (MIT) + +Copyright (c) 2017 Yasuhiro Matsumoto Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -7,13 +9,13 @@ 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 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. +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/github.com/mattn/go-xmlrpc/README.md b/vendor/github.com/mattn/go-xmlrpc/README.md new file mode 100644 index 0000000000..de6cb1d36f --- /dev/null +++ b/vendor/github.com/mattn/go-xmlrpc/README.md @@ -0,0 +1,48 @@ +# go-xmlrpc + +xmlrpc interface for go + +## Usage + +```go +package main + +import ( + "github.com/mattn/go-xmlrpc" + "fmt" + "log" +) + +func main() { + res, e := xmlrpc.Call( + "http://your-blog.example.com/xmlrpc.php", + "metaWeblog.getRecentPosts", + "blog-id", + "user-id", + "password", + 10) + if e != nil { + log.Fatal(e) + } + for _, p := range res.(xmlrpc.Array) { + for k, v := range p.(xmlrpc.Struct) { + fmt.Printf("%s=%v\n", k, v) + } + fmt.Println() + } +} +``` + +## Installation + +``` +$ go get github.com/mattn/go-xmlrpc +``` + +## License + +MIT + +## Author + +Yasuhiro Matsumoto (a.k.a. mattn) diff --git a/vendor/github.com/mattn/go-xmlrpc/xmlrpc.go b/vendor/github.com/mattn/go-xmlrpc/xmlrpc.go new file mode 100644 index 0000000000..cfbe4d85db --- /dev/null +++ b/vendor/github.com/mattn/go-xmlrpc/xmlrpc.go @@ -0,0 +1,365 @@ +package xmlrpc + +import ( + "bytes" + "encoding/base64" + "encoding/xml" + "errors" + "fmt" + "io" + "io/ioutil" + "net/http" + "reflect" + "strconv" + "strings" + "time" +) + +type Array []interface{} +type Struct map[string]interface{} + +var xmlSpecial = map[byte]string{ + '<': "<", + '>': ">", + '"': """, + '\'': "'", + '&': "&", +} + +func xmlEscape(s string) string { + var b bytes.Buffer + for i := 0; i < len(s); i++ { + c := s[i] + if s, ok := xmlSpecial[c]; ok { + b.WriteString(s) + } else { + b.WriteByte(c) + } + } + return b.String() +} + +type valueNode struct { + Type string `xml:"attr"` + Body string `xml:"chardata"` +} + +func next(p *xml.Decoder) (xml.Name, interface{}, error) { + se, e := nextStart(p) + if e != nil { + return xml.Name{}, nil, e + } + + var nv interface{} + switch se.Name.Local { + case "string": + var s string + if e = p.DecodeElement(&s, &se); e != nil { + return xml.Name{}, nil, e + } + return xml.Name{}, s, nil + case "boolean": + var s string + if e = p.DecodeElement(&s, &se); e != nil { + return xml.Name{}, nil, e + } + s = strings.TrimSpace(s) + var b bool + switch s { + case "true", "1": + b = true + case "false", "0": + b = false + default: + e = errors.New("invalid boolean value") + } + return xml.Name{}, b, e + case "int", "i1", "i2", "i4", "i8": + var s string + var i int + if e = p.DecodeElement(&s, &se); e != nil { + return xml.Name{}, nil, e + } + i, e = strconv.Atoi(strings.TrimSpace(s)) + return xml.Name{}, i, e + case "double": + var s string + var f float64 + if e = p.DecodeElement(&s, &se); e != nil { + return xml.Name{}, nil, e + } + f, e = strconv.ParseFloat(strings.TrimSpace(s), 64) + return xml.Name{}, f, e + case "dateTime.iso8601": + var s string + if e = p.DecodeElement(&s, &se); e != nil { + return xml.Name{}, nil, e + } + t, e := time.Parse("20060102T15:04:05", s) + if e != nil { + t, e = time.Parse("2006-01-02T15:04:05-07:00", s) + if e != nil { + t, e = time.Parse("2006-01-02T15:04:05", s) + } + } + return xml.Name{}, t, e + case "base64": + var s string + if e = p.DecodeElement(&s, &se); e != nil { + return xml.Name{}, nil, e + } + if b, e := base64.StdEncoding.DecodeString(s); e != nil { + return xml.Name{}, nil, e + } else { + return xml.Name{}, b, nil + } + case "member": + nextStart(p) + return next(p) + case "value": + nextStart(p) + return next(p) + case "name": + nextStart(p) + return next(p) + case "struct": + st := Struct{} + + se, e = nextStart(p) + for e == nil && se.Name.Local == "member" { + // name + se, e = nextStart(p) + if se.Name.Local != "name" { + return xml.Name{}, nil, errors.New("invalid response") + } + if e != nil { + break + } + var name string + if e = p.DecodeElement(&name, &se); e != nil { + return xml.Name{}, nil, e + } + se, e = nextStart(p) + if e != nil { + break + } + + // value + _, value, e := next(p) + if se.Name.Local != "value" { + return xml.Name{}, nil, errors.New("invalid response") + } + if e != nil { + break + } + st[name] = value + + se, e = nextStart(p) + if e != nil { + break + } + } + return xml.Name{}, st, nil + case "array": + var ar Array + nextStart(p) // data + for { + nextStart(p) // top of value + _, value, e := next(p) + if e != nil { + break + } + ar = append(ar, value) + } + return xml.Name{}, ar, nil + case "nil": + return xml.Name{}, nil, nil + } + + if e = p.DecodeElement(nv, &se); e != nil { + return xml.Name{}, nil, e + } + return se.Name, nv, e +} +func nextStart(p *xml.Decoder) (xml.StartElement, error) { + for { + t, e := p.Token() + if e != nil { + return xml.StartElement{}, e + } + switch t := t.(type) { + case xml.StartElement: + return t, nil + } + } + panic("unreachable") +} + +func toXml(v interface{}, typ bool) (s string) { + if v == nil { + return "" + } + r := reflect.ValueOf(v) + t := r.Type() + k := t.Kind() + + if b, ok := v.([]byte); ok { + return "" + base64.StdEncoding.EncodeToString(b) + "" + } + + switch k { + case reflect.Invalid: + panic("unsupported type") + case reflect.Bool: + return fmt.Sprintf("%v", v) + case reflect.Int, + reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, + reflect.Uint, + reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + if typ { + return fmt.Sprintf("%v", v) + } + return fmt.Sprintf("%v", v) + case reflect.Uintptr: + panic("unsupported type") + case reflect.Float32, reflect.Float64: + if typ { + return fmt.Sprintf("%v", v) + } + return fmt.Sprintf("%v", v) + case reflect.Complex64, reflect.Complex128: + panic("unsupported type") + case reflect.Array: + s = "" + for n := 0; n < r.Len(); n++ { + s += "" + s += toXml(r.Index(n).Interface(), typ) + s += "" + } + s += "" + return s + case reflect.Chan: + panic("unsupported type") + case reflect.Func: + panic("unsupported type") + case reflect.Interface: + return toXml(r.Elem(), typ) + case reflect.Map: + s = "" + for _, key := range r.MapKeys() { + s += "" + s += "" + xmlEscape(key.Interface().(string)) + "" + s += "" + toXml(r.MapIndex(key).Interface(), typ) + "" + s += "" + } + s += "" + return s + case reflect.Ptr: + panic("unsupported type") + case reflect.Slice: + s = "" + for n := 0; n < r.Len(); n++ { + s += "" + s += toXml(r.Index(n).Interface(), typ) + s += "" + } + s += "" + return s + case reflect.String: + if typ { + return fmt.Sprintf("%v", xmlEscape(v.(string))) + } + return xmlEscape(v.(string)) + case reflect.Struct: + s = "" + for n := 0; n < r.NumField(); n++ { + s += "" + s += "" + t.Field(n).Name + "" + s += "" + toXml(r.FieldByIndex([]int{n}).Interface(), true) + "" + s += "" + } + s += "" + return s + case reflect.UnsafePointer: + return toXml(r.Elem(), typ) + } + return +} + +// Client is client of XMLRPC +type Client struct { + HttpClient *http.Client + url string +} + +// NewClient create new Client +func NewClient(url string) *Client { + return &Client{ + HttpClient: &http.Client{Transport: http.DefaultTransport, Timeout: 10 * time.Second}, + url: url, + } +} + +func makeRequest(name string, args ...interface{}) *bytes.Buffer { + buf := new(bytes.Buffer) + buf.WriteString(``) + buf.WriteString("" + xmlEscape(name) + "") + buf.WriteString("") + for _, arg := range args { + buf.WriteString("") + buf.WriteString(toXml(arg, true)) + buf.WriteString("") + } + buf.WriteString("") + return buf +} + +func call(client *http.Client, url, name string, args ...interface{}) (v interface{}, e error) { + r, e := httpClient.Post(url, "text/xml", makeRequest(name, args...)) + if e != nil { + return nil, e + } + + // Since we do not always read the entire body, discard the rest, which + // allows the http transport to reuse the connection. + defer io.Copy(ioutil.Discard, r.Body) + defer r.Body.Close() + + if r.StatusCode/100 != 2 { + return nil, errors.New(http.StatusText(http.StatusBadRequest)) + } + + p := xml.NewDecoder(r.Body) + se, e := nextStart(p) // methodResponse + if se.Name.Local != "methodResponse" { + return nil, errors.New("invalid response: missing methodResponse") + } + se, e = nextStart(p) // params + if se.Name.Local != "params" { + return nil, errors.New("invalid response: missing params") + } + se, e = nextStart(p) // param + if se.Name.Local != "param" { + return nil, errors.New("invalid response: missing param") + } + se, e = nextStart(p) // value + if se.Name.Local != "value" { + return nil, errors.New("invalid response: missing value") + } + _, v, e = next(p) + return v, e +} + +// Call call remote procedures function name with args +func (c *Client) Call(name string, args ...interface{}) (v interface{}, e error) { + return call(c.HttpClient, c.url, name, args...) +} + +// Global httpClient allows us to pool/reuse connections and not wastefully +// re-create transports for each request. +var httpClient = &http.Client{Transport: http.DefaultTransport, Timeout: 10 * time.Second} + +// Call call remote procedures function name with args +func Call(url, name string, args ...interface{}) (v interface{}, e error) { + return call(httpClient, url, name, args...) +} diff --git a/vendor/vendor.json b/vendor/vendor.json index 8dbff89df8..c8cad5211e 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -64,18 +64,18 @@ "version": "v1.0.0", "versionExact": "v1.0.0" }, - { - "checksumSHA1": "tw3ocqSpa9ikzUV6qhcKBAAO6WU=", - "path": "github.com/kolo/xmlrpc", - "revision": "0826b98aaa29c0766956cb40d45cf7482a597671", - "revisionTime": "2015-04-13T19:18:30Z" - }, { "checksumSHA1": "IdBAvtVSv0sbi8sEsLovnZubims=", "path": "github.com/lufia/iostat", "revision": "9f7362b77ad333b26c01c99de52a11bdb650ded2", "revisionTime": "2017-06-05T15:08:45Z" }, + { + "checksumSHA1": "8uAFFK5p8F39X1MOLsHrgTI0hzw=", + "path": "github.com/mattn/go-xmlrpc", + "revision": "b7a1b57d9142f44a3a8f5a80aadf8d2d6ea2ca22", + "revisionTime": "2018-04-20T00:08:13Z" + }, { "checksumSHA1": "aodj/cITRyuaZSh84DDhrZjh76U=", "path": "github.com/matttproud/golang_protobuf_extensions/pbutil",