mirror of
https://github.com/prometheus/node_exporter.git
synced 2025-03-05 21:00:12 -08:00
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 <superq@gmail.com>
This commit is contained in:
parent
2c52b8c761
commit
5d23ad0ca7
|
@ -2,6 +2,8 @@
|
||||||
|
|
||||||
**Breaking changes**
|
**Breaking changes**
|
||||||
|
|
||||||
|
supvervisord collector reports "start_time_seconds" rather than "uptime"
|
||||||
|
|
||||||
* [CHANGE] Filter out non-installed units when collecting all systemd units #1011
|
* [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 NRefused property for systemd socket units (available as of systemd v239)
|
||||||
* [FEATURE] Collect NRestarts property for systemd service units
|
* [FEATURE] Collect NRestarts property for systemd service units
|
||||||
|
@ -10,6 +12,8 @@
|
||||||
* [ENHANCEMENT]
|
* [ENHANCEMENT]
|
||||||
* [BUGFIX]
|
* [BUGFIX]
|
||||||
|
|
||||||
|
* [BUGFIX] Fix goroutine leak in supervisord collector
|
||||||
|
|
||||||
## 0.16.0 / 2018-05-15
|
## 0.16.0 / 2018-05-15
|
||||||
|
|
||||||
**Breaking changes**
|
**Breaking changes**
|
||||||
|
|
|
@ -16,7 +16,9 @@
|
||||||
package collector
|
package collector
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/kolo/xmlrpc"
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/mattn/go-xmlrpc"
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
"github.com/prometheus/common/log"
|
"github.com/prometheus/common/log"
|
||||||
"gopkg.in/alecthomas/kingpin.v2"
|
"gopkg.in/alecthomas/kingpin.v2"
|
||||||
|
@ -27,11 +29,10 @@ var (
|
||||||
)
|
)
|
||||||
|
|
||||||
type supervisordCollector struct {
|
type supervisordCollector struct {
|
||||||
client *xmlrpc.Client
|
|
||||||
upDesc *prometheus.Desc
|
upDesc *prometheus.Desc
|
||||||
stateDesc *prometheus.Desc
|
stateDesc *prometheus.Desc
|
||||||
exitStatusDesc *prometheus.Desc
|
exitStatusDesc *prometheus.Desc
|
||||||
uptimeDesc *prometheus.Desc
|
startTimeDesc *prometheus.Desc
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
@ -40,17 +41,11 @@ func init() {
|
||||||
|
|
||||||
// NewSupervisordCollector returns a new Collector exposing supervisord statistics.
|
// NewSupervisordCollector returns a new Collector exposing supervisord statistics.
|
||||||
func NewSupervisordCollector() (Collector, error) {
|
func NewSupervisordCollector() (Collector, error) {
|
||||||
client, err := xmlrpc.NewClient(*supervisordURL, nil)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
subsystem = "supervisord"
|
subsystem = "supervisord"
|
||||||
labelNames = []string{"name", "group"}
|
labelNames = []string{"name", "group"}
|
||||||
)
|
)
|
||||||
return &supervisordCollector{
|
return &supervisordCollector{
|
||||||
client: client,
|
|
||||||
upDesc: prometheus.NewDesc(
|
upDesc: prometheus.NewDesc(
|
||||||
prometheus.BuildFQName(namespace, subsystem, "up"),
|
prometheus.BuildFQName(namespace, subsystem, "up"),
|
||||||
"Process Up",
|
"Process Up",
|
||||||
|
@ -69,9 +64,9 @@ func NewSupervisordCollector() (Collector, error) {
|
||||||
labelNames,
|
labelNames,
|
||||||
nil,
|
nil,
|
||||||
),
|
),
|
||||||
uptimeDesc: prometheus.NewDesc(
|
startTimeDesc: prometheus.NewDesc(
|
||||||
prometheus.BuildFQName(namespace, subsystem, "uptime"),
|
prometheus.BuildFQName(namespace, subsystem, "start_time_seconds"),
|
||||||
"Process Uptime",
|
"Process start time",
|
||||||
labelNames,
|
labelNames,
|
||||||
nil,
|
nil,
|
||||||
),
|
),
|
||||||
|
@ -98,7 +93,7 @@ func (c *supervisordCollector) isRunning(state int) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *supervisordCollector) Update(ch chan<- prometheus.Metric) error {
|
func (c *supervisordCollector) Update(ch chan<- prometheus.Metric) error {
|
||||||
var infos []struct {
|
var info struct {
|
||||||
Name string `xmlrpc:"name"`
|
Name string `xmlrpc:"name"`
|
||||||
Group string `xmlrpc:"group"`
|
Group string `xmlrpc:"group"`
|
||||||
Start int `xmlrpc:"start"`
|
Start int `xmlrpc:"start"`
|
||||||
|
@ -112,10 +107,35 @@ func (c *supervisordCollector) Update(ch chan<- prometheus.Metric) error {
|
||||||
StderrLogfile string `xmlrcp:"stderr_logfile"`
|
StderrLogfile string `xmlrcp:"stderr_logfile"`
|
||||||
PID int `xmlrpc:"pid"`
|
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}
|
labels := []string{info.Name, info.Group}
|
||||||
|
|
||||||
ch <- prometheus.MustNewConstMetric(c.stateDesc, prometheus.GaugeValue, float64(info.State), labels...)
|
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) {
|
if c.isRunning(info.State) {
|
||||||
ch <- prometheus.MustNewConstMetric(c.upDesc, prometheus.GaugeValue, 1, labels...)
|
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 {
|
} else {
|
||||||
ch <- prometheus.MustNewConstMetric(c.upDesc, prometheus.GaugeValue, 0, labels...)
|
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)
|
log.Debugf("%s:%s is %s on pid %d", info.Group, info.Name, info.StateName, info.PID)
|
||||||
}
|
}
|
||||||
|
|
5
docs/example-17-compatibility-rules-new-to-old.yml
Normal file
5
docs/example-17-compatibility-rules-new-to-old.yml
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
groups:
|
||||||
|
- name: node_exporter-17-supervisord
|
||||||
|
rules:
|
||||||
|
- record: node_supervisord_start_time_seconds
|
||||||
|
expr: node_supervisord_uptime + time()
|
5
docs/example-17-compatibility-rules.yml
Normal file
5
docs/example-17-compatibility-rules.yml
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
groups:
|
||||||
|
- name: node_exporter-17-supervisord
|
||||||
|
rules:
|
||||||
|
- record: node_supervisord_uptime
|
||||||
|
expr: time() - node_supervisord_start_time_seconds
|
79
vendor/github.com/kolo/xmlrpc/README.md
generated
vendored
79
vendor/github.com/kolo/xmlrpc/README.md
generated
vendored
|
@ -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)
|
|
144
vendor/github.com/kolo/xmlrpc/client.go
generated
vendored
144
vendor/github.com/kolo/xmlrpc/client.go
generated
vendored
|
@ -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
|
|
||||||
}
|
|
449
vendor/github.com/kolo/xmlrpc/decoder.go
generated
vendored
449
vendor/github.com/kolo/xmlrpc/decoder.go
generated
vendored
|
@ -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
|
|
||||||
}
|
|
||||||
|
|
||||||
// </value>
|
|
||||||
if err = dec.Skip(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// </member>
|
|
||||||
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())
|
|
||||||
|
|
||||||
// </value>
|
|
||||||
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")
|
|
||||||
}
|
|
||||||
|
|
||||||
// </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
|
|
||||||
}
|
|
164
vendor/github.com/kolo/xmlrpc/encoder.go
generated
vendored
164
vendor/github.com/kolo/xmlrpc/encoder.go
generated
vendored
|
@ -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("<value/>"), 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("<dateTime.iso8601>%s</dateTime.iso8601>", 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("<int>%s</int>", strconv.FormatInt(val.Int(), 10)))
|
|
||||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
|
||||||
b = []byte(fmt.Sprintf("<i4>%s</i4>", strconv.FormatUint(val.Uint(), 10)))
|
|
||||||
case reflect.Float32, reflect.Float64:
|
|
||||||
b = []byte(fmt.Sprintf("<double>%s</double>",
|
|
||||||
strconv.FormatFloat(val.Float(), 'g', -1, val.Type().Bits())))
|
|
||||||
case reflect.Bool:
|
|
||||||
if val.Bool() {
|
|
||||||
b = []byte("<boolean>1</boolean>")
|
|
||||||
} else {
|
|
||||||
b = []byte("<boolean>0</boolean>")
|
|
||||||
}
|
|
||||||
case reflect.String:
|
|
||||||
var buf bytes.Buffer
|
|
||||||
|
|
||||||
xml.Escape(&buf, []byte(val.String()))
|
|
||||||
|
|
||||||
if _, ok := val.Interface().(Base64); ok {
|
|
||||||
b = []byte(fmt.Sprintf("<base64>%s</base64>", buf.String()))
|
|
||||||
} else {
|
|
||||||
b = []byte(fmt.Sprintf("<string>%s</string>", buf.String()))
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return nil, fmt.Errorf("xmlrpc encode error: unsupported type")
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return []byte(fmt.Sprintf("<value>%s</value>", string(b))), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func encodeStruct(val reflect.Value) ([]byte, error) {
|
|
||||||
var b bytes.Buffer
|
|
||||||
|
|
||||||
b.WriteString("<struct>")
|
|
||||||
|
|
||||||
t := val.Type()
|
|
||||||
for i := 0; i < t.NumField(); i++ {
|
|
||||||
b.WriteString("<member>")
|
|
||||||
f := t.Field(i)
|
|
||||||
|
|
||||||
name := f.Tag.Get("xmlrpc")
|
|
||||||
if name == "" {
|
|
||||||
name = f.Name
|
|
||||||
}
|
|
||||||
b.WriteString(fmt.Sprintf("<name>%s</name>", name))
|
|
||||||
|
|
||||||
p, err := encodeValue(val.FieldByName(f.Name))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
b.Write(p)
|
|
||||||
|
|
||||||
b.WriteString("</member>")
|
|
||||||
}
|
|
||||||
|
|
||||||
b.WriteString("</struct>")
|
|
||||||
|
|
||||||
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("<struct>")
|
|
||||||
|
|
||||||
keys := val.MapKeys()
|
|
||||||
|
|
||||||
for i := 0; i < val.Len(); i++ {
|
|
||||||
key := keys[i]
|
|
||||||
kval := val.MapIndex(key)
|
|
||||||
|
|
||||||
b.WriteString("<member>")
|
|
||||||
b.WriteString(fmt.Sprintf("<name>%s</name>", key.String()))
|
|
||||||
|
|
||||||
p, err := encodeValue(kval)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
b.Write(p)
|
|
||||||
b.WriteString("</member>")
|
|
||||||
}
|
|
||||||
|
|
||||||
b.WriteString("</struct>")
|
|
||||||
|
|
||||||
return b.Bytes(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func encodeSlice(val reflect.Value) ([]byte, error) {
|
|
||||||
var b bytes.Buffer
|
|
||||||
|
|
||||||
b.WriteString("<array><data>")
|
|
||||||
|
|
||||||
for i := 0; i < val.Len(); i++ {
|
|
||||||
p, err := encodeValue(val.Index(i))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
b.Write(p)
|
|
||||||
}
|
|
||||||
|
|
||||||
b.WriteString("</data></array>")
|
|
||||||
|
|
||||||
return b.Bytes(), nil
|
|
||||||
}
|
|
57
vendor/github.com/kolo/xmlrpc/request.go
generated
vendored
57
vendor/github.com/kolo/xmlrpc/request.go
generated
vendored
|
@ -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(`<?xml version="1.0" encoding="UTF-8"?>`)
|
|
||||||
b.WriteString(fmt.Sprintf("<methodCall><methodName>%s</methodName>", method))
|
|
||||||
|
|
||||||
if args != nil {
|
|
||||||
b.WriteString("<params>")
|
|
||||||
|
|
||||||
for _, arg := range args {
|
|
||||||
p, err := marshal(arg)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
b.WriteString(fmt.Sprintf("<param>%s</param>", string(p)))
|
|
||||||
}
|
|
||||||
|
|
||||||
b.WriteString("</params>")
|
|
||||||
}
|
|
||||||
|
|
||||||
b.WriteString("</methodCall>")
|
|
||||||
|
|
||||||
return b.Bytes(), nil
|
|
||||||
}
|
|
52
vendor/github.com/kolo/xmlrpc/response.go
generated
vendored
52
vendor/github.com/kolo/xmlrpc/response.go
generated
vendored
|
@ -1,52 +0,0 @@
|
||||||
package xmlrpc
|
|
||||||
|
|
||||||
import (
|
|
||||||
"regexp"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
faultRx = regexp.MustCompile(`<fault>(\s|\S)+</fault>`)
|
|
||||||
)
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
25
vendor/github.com/kolo/xmlrpc/test_server.rb
generated
vendored
25
vendor/github.com/kolo/xmlrpc/test_server.rb
generated
vendored
|
@ -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
|
|
19
vendor/github.com/kolo/xmlrpc/xmlrpc.go
generated
vendored
19
vendor/github.com/kolo/xmlrpc/xmlrpc.go
generated
vendored
|
@ -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
|
|
12
vendor/github.com/kolo/xmlrpc/LICENSE → vendor/github.com/mattn/go-xmlrpc/LICENSE
generated
vendored
12
vendor/github.com/kolo/xmlrpc/LICENSE → vendor/github.com/mattn/go-xmlrpc/LICENSE
generated
vendored
|
@ -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
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
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
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
furnished to do so, subject to the following conditions:
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in
|
The above copyright notice and this permission notice shall be included in all
|
||||||
all copies or substantial portions of the Software.
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
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
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
THE SOFTWARE.
|
SOFTWARE.
|
48
vendor/github.com/mattn/go-xmlrpc/README.md
generated
vendored
Normal file
48
vendor/github.com/mattn/go-xmlrpc/README.md
generated
vendored
Normal file
|
@ -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)
|
365
vendor/github.com/mattn/go-xmlrpc/xmlrpc.go
generated
vendored
Normal file
365
vendor/github.com/mattn/go-xmlrpc/xmlrpc.go
generated
vendored
Normal file
|
@ -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 "<nil/>"
|
||||||
|
}
|
||||||
|
r := reflect.ValueOf(v)
|
||||||
|
t := r.Type()
|
||||||
|
k := t.Kind()
|
||||||
|
|
||||||
|
if b, ok := v.([]byte); ok {
|
||||||
|
return "<base64>" + base64.StdEncoding.EncodeToString(b) + "</base64>"
|
||||||
|
}
|
||||||
|
|
||||||
|
switch k {
|
||||||
|
case reflect.Invalid:
|
||||||
|
panic("unsupported type")
|
||||||
|
case reflect.Bool:
|
||||||
|
return fmt.Sprintf("<boolean>%v</boolean>", 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("<int>%v</int>", v)
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%v", v)
|
||||||
|
case reflect.Uintptr:
|
||||||
|
panic("unsupported type")
|
||||||
|
case reflect.Float32, reflect.Float64:
|
||||||
|
if typ {
|
||||||
|
return fmt.Sprintf("<double>%v</double>", v)
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%v", v)
|
||||||
|
case reflect.Complex64, reflect.Complex128:
|
||||||
|
panic("unsupported type")
|
||||||
|
case reflect.Array:
|
||||||
|
s = "<array><data>"
|
||||||
|
for n := 0; n < r.Len(); n++ {
|
||||||
|
s += "<value>"
|
||||||
|
s += toXml(r.Index(n).Interface(), typ)
|
||||||
|
s += "</value>"
|
||||||
|
}
|
||||||
|
s += "</data></array>"
|
||||||
|
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 = "<struct>"
|
||||||
|
for _, key := range r.MapKeys() {
|
||||||
|
s += "<member>"
|
||||||
|
s += "<name>" + xmlEscape(key.Interface().(string)) + "</name>"
|
||||||
|
s += "<value>" + toXml(r.MapIndex(key).Interface(), typ) + "</value>"
|
||||||
|
s += "</member>"
|
||||||
|
}
|
||||||
|
s += "</struct>"
|
||||||
|
return s
|
||||||
|
case reflect.Ptr:
|
||||||
|
panic("unsupported type")
|
||||||
|
case reflect.Slice:
|
||||||
|
s = "<array><data>"
|
||||||
|
for n := 0; n < r.Len(); n++ {
|
||||||
|
s += "<value>"
|
||||||
|
s += toXml(r.Index(n).Interface(), typ)
|
||||||
|
s += "</value>"
|
||||||
|
}
|
||||||
|
s += "</data></array>"
|
||||||
|
return s
|
||||||
|
case reflect.String:
|
||||||
|
if typ {
|
||||||
|
return fmt.Sprintf("<string>%v</string>", xmlEscape(v.(string)))
|
||||||
|
}
|
||||||
|
return xmlEscape(v.(string))
|
||||||
|
case reflect.Struct:
|
||||||
|
s = "<struct>"
|
||||||
|
for n := 0; n < r.NumField(); n++ {
|
||||||
|
s += "<member>"
|
||||||
|
s += "<name>" + t.Field(n).Name + "</name>"
|
||||||
|
s += "<value>" + toXml(r.FieldByIndex([]int{n}).Interface(), true) + "</value>"
|
||||||
|
s += "</member>"
|
||||||
|
}
|
||||||
|
s += "</struct>"
|
||||||
|
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(`<?xml version="1.0"?><methodCall>`)
|
||||||
|
buf.WriteString("<methodName>" + xmlEscape(name) + "</methodName>")
|
||||||
|
buf.WriteString("<params>")
|
||||||
|
for _, arg := range args {
|
||||||
|
buf.WriteString("<param><value>")
|
||||||
|
buf.WriteString(toXml(arg, true))
|
||||||
|
buf.WriteString("</value></param>")
|
||||||
|
}
|
||||||
|
buf.WriteString("</params></methodCall>")
|
||||||
|
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...)
|
||||||
|
}
|
12
vendor/vendor.json
vendored
12
vendor/vendor.json
vendored
|
@ -64,18 +64,18 @@
|
||||||
"version": "v1.0.0",
|
"version": "v1.0.0",
|
||||||
"versionExact": "v1.0.0"
|
"versionExact": "v1.0.0"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"checksumSHA1": "tw3ocqSpa9ikzUV6qhcKBAAO6WU=",
|
|
||||||
"path": "github.com/kolo/xmlrpc",
|
|
||||||
"revision": "0826b98aaa29c0766956cb40d45cf7482a597671",
|
|
||||||
"revisionTime": "2015-04-13T19:18:30Z"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"checksumSHA1": "IdBAvtVSv0sbi8sEsLovnZubims=",
|
"checksumSHA1": "IdBAvtVSv0sbi8sEsLovnZubims=",
|
||||||
"path": "github.com/lufia/iostat",
|
"path": "github.com/lufia/iostat",
|
||||||
"revision": "9f7362b77ad333b26c01c99de52a11bdb650ded2",
|
"revision": "9f7362b77ad333b26c01c99de52a11bdb650ded2",
|
||||||
"revisionTime": "2017-06-05T15:08:45Z"
|
"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=",
|
"checksumSHA1": "aodj/cITRyuaZSh84DDhrZjh76U=",
|
||||||
"path": "github.com/matttproud/golang_protobuf_extensions/pbutil",
|
"path": "github.com/matttproud/golang_protobuf_extensions/pbutil",
|
||||||
|
|
Loading…
Reference in a new issue