forked from pool/golang-github-prometheus-prometheus
148a94cc9a
- Update 0003-Add-Uyuni-service-discovery.patch: + Add support for Prometheus exporters proxy OBS-URL: https://build.opensuse.org/request/show/833503 OBS-URL: https://build.opensuse.org/package/show/server:monitoring/golang-github-prometheus-prometheus?expand=0&rev=33
2195 lines
60 KiB
Diff
2195 lines
60 KiB
Diff
From: Joao Cavalheiro <jcavalheiro@suse.de>
|
||
Date: Mon Jul 27 17:42:33 2020 +0200
|
||
Subject: A fork of Prometheus with Uyuni Service discovery
|
||
References: https://github.com/uyuni-project/prometheus-uyuni-sd
|
||
|
||
---
|
||
discovery/config/config.go | 3
|
||
discovery/manager.go | 6
|
||
discovery/uyuni/uyuni.go | 384 +++++++++++++++++
|
||
discovery/uyuni/uyuni_test.go | 33 +
|
||
go.mod | 1
|
||
go.sum | 2
|
||
vendor/github.com/kolo/xmlrpc/LICENSE | 19
|
||
vendor/github.com/kolo/xmlrpc/README.md | 89 ++++
|
||
vendor/github.com/kolo/xmlrpc/client.go | 170 +++++++
|
||
vendor/github.com/kolo/xmlrpc/client_test.go | 141 ++++++
|
||
vendor/github.com/kolo/xmlrpc/decoder.go | 473 ++++++++++++++++++++++
|
||
vendor/github.com/kolo/xmlrpc/decoder_test.go | 234 ++++++++++
|
||
vendor/github.com/kolo/xmlrpc/encoder.go | 171 +++++++
|
||
vendor/github.com/kolo/xmlrpc/encoder_test.go | 58 ++
|
||
vendor/github.com/kolo/xmlrpc/fixtures/cp1251.xml | 6
|
||
vendor/github.com/kolo/xmlrpc/request.go | 57 ++
|
||
vendor/github.com/kolo/xmlrpc/response.go | 52 ++
|
||
vendor/github.com/kolo/xmlrpc/response_test.go | 84 +++
|
||
vendor/github.com/kolo/xmlrpc/test_server.rb | 25 +
|
||
vendor/github.com/kolo/xmlrpc/xmlrpc.go | 19
|
||
20 files changed, 2027 insertions(+)
|
||
|
||
Index: prometheus-2.18.0/discovery/config/config.go
|
||
===================================================================
|
||
--- prometheus-2.18.0.orig/discovery/config/config.go
|
||
+++ prometheus-2.18.0/discovery/config/config.go
|
||
@@ -27,6 +27,7 @@ import (
|
||
"github.com/prometheus/prometheus/discovery/openstack"
|
||
"github.com/prometheus/prometheus/discovery/targetgroup"
|
||
"github.com/prometheus/prometheus/discovery/triton"
|
||
+ "github.com/prometheus/prometheus/discovery/uyuni"
|
||
"github.com/prometheus/prometheus/discovery/zookeeper"
|
||
)
|
||
|
||
@@ -58,6 +59,8 @@ type ServiceDiscoveryConfig struct {
|
||
AzureSDConfigs []*azure.SDConfig `yaml:"azure_sd_configs,omitempty"`
|
||
// List of Triton service discovery configurations.
|
||
TritonSDConfigs []*triton.SDConfig `yaml:"triton_sd_configs,omitempty"`
|
||
+ // List of Uyuni service discovery configurations.
|
||
+ UyuniSDConfigs []*uyuni.SDConfig `yaml:"uyuni_sd_configs,omitempty"`
|
||
}
|
||
|
||
// Validate validates the ServiceDiscoveryConfig.
|
||
Index: prometheus-2.18.0/discovery/manager.go
|
||
===================================================================
|
||
--- prometheus-2.18.0.orig/discovery/manager.go
|
||
+++ prometheus-2.18.0/discovery/manager.go
|
||
@@ -37,6 +37,7 @@ import (
|
||
"github.com/prometheus/prometheus/discovery/marathon"
|
||
"github.com/prometheus/prometheus/discovery/openstack"
|
||
"github.com/prometheus/prometheus/discovery/triton"
|
||
+ "github.com/prometheus/prometheus/discovery/uyuni"
|
||
"github.com/prometheus/prometheus/discovery/zookeeper"
|
||
)
|
||
|
||
@@ -414,6 +415,11 @@ func (m *Manager) registerProviders(cfg
|
||
return triton.New(log.With(m.logger, "discovery", "triton"), c)
|
||
})
|
||
}
|
||
+ for _, c := range cfg.UyuniSDConfigs {
|
||
+ add(c, func() (Discoverer, error) {
|
||
+ return uyuni.NewDiscovery(c, log.With(m.logger, "discovery", "uyuni")), nil
|
||
+ })
|
||
+ }
|
||
if len(cfg.StaticConfigs) > 0 {
|
||
add(setName, func() (Discoverer, error) {
|
||
return &StaticProvider{TargetGroups: cfg.StaticConfigs}, nil
|
||
Index: prometheus-2.18.0/discovery/uyuni/uyuni.go
|
||
===================================================================
|
||
--- /dev/null
|
||
+++ prometheus-2.18.0/discovery/uyuni/uyuni.go
|
||
@@ -0,0 +1,384 @@
|
||
+// Copyright 2019 The Prometheus Authors
|
||
+// Licensed under the Apache License, Version 2.0 (the "License");
|
||
+// you may not use this file except in compliance with the License.
|
||
+// You may obtain a copy of the License at
|
||
+//
|
||
+// http://www.apache.org/licenses/LICENSE-2.0
|
||
+//
|
||
+// Unless required by applicable law or agreed to in writing, software
|
||
+// distributed under the License is distributed on an "AS IS" BASIS,
|
||
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||
+// See the License for the specific language governing permissions and
|
||
+// limitations under the License.
|
||
+
|
||
+package uyuni
|
||
+
|
||
+import (
|
||
+ "context"
|
||
+ "fmt"
|
||
+ "net"
|
||
+ "net/url"
|
||
+ "regexp"
|
||
+ "strings"
|
||
+ "time"
|
||
+
|
||
+ "github.com/go-kit/kit/log"
|
||
+ "github.com/go-kit/kit/log/level"
|
||
+ "github.com/kolo/xmlrpc"
|
||
+ "github.com/pkg/errors"
|
||
+ "github.com/prometheus/common/model"
|
||
+
|
||
+ "github.com/prometheus/prometheus/discovery/refresh"
|
||
+ "github.com/prometheus/prometheus/discovery/targetgroup"
|
||
+)
|
||
+
|
||
+const (
|
||
+ monitoringEntitlementLabel = "monitoring_entitled"
|
||
+ prometheusExporterFormulaName = "prometheus-exporters"
|
||
+ uyuniXMLRPCAPIPath = "/rpc/api"
|
||
+)
|
||
+
|
||
+// DefaultSDConfig is the default Uyuni SD configuration.
|
||
+var DefaultSDConfig = SDConfig{
|
||
+ RefreshInterval: model.Duration(1 * time.Minute),
|
||
+}
|
||
+
|
||
+// Regular expression to extract port from formula data
|
||
+var monFormulaRegex = regexp.MustCompile(`--(?:telemetry\.address|web\.listen-address)=\":([0-9]*)\"`)
|
||
+
|
||
+// SDConfig is the configuration for Uyuni based service discovery.
|
||
+type SDConfig struct {
|
||
+ Host string `yaml:"host"`
|
||
+ User string `yaml:"username"`
|
||
+ Pass string `yaml:"password"`
|
||
+ RefreshInterval model.Duration `yaml:"refresh_interval,omitempty"`
|
||
+}
|
||
+
|
||
+// Uyuni API Response structures
|
||
+type systemGroupID struct {
|
||
+ GroupID int `xmlrpc:"id"`
|
||
+ GroupName string `xmlrpc:"name"`
|
||
+}
|
||
+
|
||
+type networkInfo struct {
|
||
+ SystemID int `xmlrpc:"system_id"`
|
||
+ Hostname string `xmlrpc:"hostname"`
|
||
+ IP string `xmlrpc:"ip"`
|
||
+}
|
||
+
|
||
+type exporterConfig struct {
|
||
+ Address string `xmlrpc:"address"`
|
||
+ Args string `xmlrpc:"args"`
|
||
+ Enabled bool `xmlrpc:"enabled"`
|
||
+}
|
||
+
|
||
+type proxiedExporterConfig struct {
|
||
+ ProxyIsEnabled bool `xmlrpc:"proxy_enabled"`
|
||
+ ProxyPort float32 `xmlrpc:"proxy_port"`
|
||
+ NodeExporter exporterConfig `xmlrpc:"node_exporter"`
|
||
+ ApacheExporter exporterConfig `xmlrpc:"apache_exporter"`
|
||
+ PostgresExporter exporterConfig `xmlrpc:"postgres_exporter"`
|
||
+}
|
||
+
|
||
+// Discovery periodically performs Uyuni API requests. It implements the Discoverer interface.
|
||
+type Discovery struct {
|
||
+ *refresh.Discovery
|
||
+ interval time.Duration
|
||
+ sdConfig *SDConfig
|
||
+ logger log.Logger
|
||
+}
|
||
+
|
||
+// UnmarshalYAML implements the yaml.Unmarshaler interface.
|
||
+func (c *SDConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
||
+ *c = DefaultSDConfig
|
||
+ type plain SDConfig
|
||
+ err := unmarshal((*plain)(c))
|
||
+
|
||
+ if err != nil {
|
||
+ return err
|
||
+ }
|
||
+ if c.Host == "" {
|
||
+ return errors.New("Uyuni SD configuration requires a Host")
|
||
+ }
|
||
+ if c.User == "" {
|
||
+ return errors.New("Uyuni SD configuration requires a Username")
|
||
+ }
|
||
+ if c.Pass == "" {
|
||
+ return errors.New("Uyuni SD configuration requires a Password")
|
||
+ }
|
||
+ if c.RefreshInterval <= 0 {
|
||
+ return errors.New("Uyuni SD configuration requires RefreshInterval to be a positive integer")
|
||
+ }
|
||
+ return nil
|
||
+}
|
||
+
|
||
+// Attempt to login in Uyuni Server and get an auth token
|
||
+func login(rpcclient *xmlrpc.Client, user string, pass string) (string, error) {
|
||
+ var result string
|
||
+ err := rpcclient.Call("auth.login", []interface{}{user, pass}, &result)
|
||
+ return result, err
|
||
+}
|
||
+
|
||
+// Logout from Uyuni API
|
||
+func logout(rpcclient *xmlrpc.Client, token string) error {
|
||
+ err := rpcclient.Call("auth.logout", token, nil)
|
||
+ return err
|
||
+}
|
||
+
|
||
+// Get the system groups information of monitored clients
|
||
+func getSystemGroupsInfoOfMonitoredClients(rpcclient *xmlrpc.Client, token string) (map[int][]systemGroupID, error) {
|
||
+ var systemGroupsInfos []struct {
|
||
+ SystemID int `xmlrpc:"id"`
|
||
+ SystemGroups []systemGroupID `xmlrpc:"system_groups"`
|
||
+ }
|
||
+ err := rpcclient.Call("system.listSystemGroupsForSystemsWithEntitlement", []interface{}{token, monitoringEntitlementLabel}, &systemGroupsInfos)
|
||
+ if err != nil {
|
||
+ return nil, err
|
||
+ }
|
||
+ result := make(map[int][]systemGroupID)
|
||
+ for _, systemGroupsInfo := range systemGroupsInfos {
|
||
+ result[systemGroupsInfo.SystemID] = systemGroupsInfo.SystemGroups
|
||
+ }
|
||
+ return result, nil
|
||
+}
|
||
+
|
||
+// GetSystemNetworkInfo lists client FQDNs
|
||
+func getNetworkInformationForSystems(rpcclient *xmlrpc.Client, token string, systemIDs []int) (map[int]networkInfo, error) {
|
||
+ var networkInfos []networkInfo
|
||
+ err := rpcclient.Call("system.getNetworkForSystems", []interface{}{token, systemIDs}, &networkInfos)
|
||
+ if err != nil {
|
||
+ return nil, err
|
||
+ }
|
||
+ result := make(map[int]networkInfo)
|
||
+ for _, networkInfo := range networkInfos {
|
||
+ result[networkInfo.SystemID] = networkInfo
|
||
+ }
|
||
+ return result, nil
|
||
+}
|
||
+
|
||
+// Get formula data for a given system
|
||
+func getExporterDataForSystems(
|
||
+ rpcclient *xmlrpc.Client,
|
||
+ token string,
|
||
+ systemIDs []int,
|
||
+) (map[int]proxiedExporterConfig, error) {
|
||
+ var combinedFormulaData []struct {
|
||
+ SystemID int `xmlrpc:"system_id"`
|
||
+ ExporterConfigs proxiedExporterConfig `xmlrpc:"formula_values"`
|
||
+ }
|
||
+ err := rpcclient.Call(
|
||
+ "formula.getCombinedFormulaDataByServerIds",
|
||
+ []interface{}{token, prometheusExporterFormulaName, systemIDs},
|
||
+ &combinedFormulaData)
|
||
+ if err != nil {
|
||
+ return nil, err
|
||
+ }
|
||
+ result := make(map[int]proxiedExporterConfig)
|
||
+ for _, combinedFormulaData := range combinedFormulaData {
|
||
+ result[combinedFormulaData.SystemID] = combinedFormulaData.ExporterConfigs
|
||
+ }
|
||
+ return result, nil
|
||
+}
|
||
+
|
||
+// extractPortFromFormulaData gets exporter port configuration from the formula.
|
||
+// args takes precedence over address.
|
||
+func extractPortFromFormulaData(args string, address string) (string, error) {
|
||
+ // first try args
|
||
+ var port string
|
||
+ tokens := monFormulaRegex.FindStringSubmatch(args)
|
||
+ if len(tokens) < 1 {
|
||
+ err := "Unable to find port in args: " + args
|
||
+ // now try address
|
||
+ _, addrPort, addrErr := net.SplitHostPort(address)
|
||
+ if addrErr != nil || len(addrPort) == 0 {
|
||
+ if addrErr != nil {
|
||
+ err = strings.Join([]string{addrErr.Error(), err}, " ")
|
||
+ }
|
||
+ return "", errors.New(err)
|
||
+ }
|
||
+ port = addrPort
|
||
+ } else {
|
||
+ port = tokens[1]
|
||
+ }
|
||
+
|
||
+ return port, nil
|
||
+}
|
||
+
|
||
+// NewDiscovery returns a new file discovery for the given paths.
|
||
+func NewDiscovery(conf *SDConfig, logger log.Logger) *Discovery {
|
||
+ d := &Discovery{
|
||
+ interval: time.Duration(conf.RefreshInterval),
|
||
+ sdConfig: conf,
|
||
+ logger: logger,
|
||
+ }
|
||
+ d.Discovery = refresh.NewDiscovery(
|
||
+ logger,
|
||
+ "uyuni",
|
||
+ time.Duration(conf.RefreshInterval),
|
||
+ d.refresh,
|
||
+ )
|
||
+ return d
|
||
+}
|
||
+
|
||
+func initializeExporterTargets(
|
||
+ targets *[]model.LabelSet,
|
||
+ module string, config exporterConfig,
|
||
+ proxyPort string,
|
||
+ errors *[]error,
|
||
+) {
|
||
+ if !(config.Enabled) {
|
||
+ return
|
||
+ }
|
||
+ var port string
|
||
+ if len(proxyPort) == 0 {
|
||
+ exporterPort, err := extractPortFromFormulaData(config.Args, config.Address)
|
||
+ if err != nil {
|
||
+ *errors = append(*errors, err)
|
||
+ return
|
||
+ }
|
||
+ port = exporterPort
|
||
+ } else {
|
||
+ port = proxyPort
|
||
+ }
|
||
+
|
||
+ labels := model.LabelSet{}
|
||
+ labels["exporter"] = model.LabelValue(module + "_exporter")
|
||
+ // for now set only port number here
|
||
+ labels[model.AddressLabel] = model.LabelValue(port)
|
||
+ if len(proxyPort) > 0 {
|
||
+ labels[model.ParamLabelPrefix+"module"] = model.LabelValue(module)
|
||
+ }
|
||
+ *targets = append(*targets, labels)
|
||
+}
|
||
+
|
||
+func (d *Discovery) getTargetsForSystem(
|
||
+ systemID int,
|
||
+ systemGroupsIDs []systemGroupID,
|
||
+ networkInfo networkInfo,
|
||
+ combinedFormulaData proxiedExporterConfig,
|
||
+) []model.LabelSet {
|
||
+
|
||
+ var labelSets []model.LabelSet
|
||
+ var errors []error
|
||
+ var proxyPortNumber string
|
||
+ if combinedFormulaData.ProxyIsEnabled {
|
||
+ proxyPortNumber = fmt.Sprintf("%d", int(combinedFormulaData.ProxyPort))
|
||
+ }
|
||
+ initializeExporterTargets(&labelSets, "node", combinedFormulaData.NodeExporter, proxyPortNumber, &errors)
|
||
+ initializeExporterTargets(&labelSets, "apache", combinedFormulaData.ApacheExporter, proxyPortNumber, &errors)
|
||
+ initializeExporterTargets(&labelSets, "postgres", combinedFormulaData.PostgresExporter, proxyPortNumber, &errors)
|
||
+ managedGroupNames := getSystemGroupNames(systemGroupsIDs)
|
||
+ for _, labels := range labelSets {
|
||
+ // add hostname to the address label
|
||
+ addr := fmt.Sprintf("%s:%s", networkInfo.IP, labels[model.AddressLabel])
|
||
+ labels[model.AddressLabel] = model.LabelValue(addr)
|
||
+ labels["hostname"] = model.LabelValue(networkInfo.Hostname)
|
||
+ labels["groups"] = model.LabelValue(strings.Join(managedGroupNames, ","))
|
||
+ if combinedFormulaData.ProxyIsEnabled {
|
||
+ labels[model.MetricsPathLabel] = "/proxy"
|
||
+ }
|
||
+ _ = level.Debug(d.logger).Log("msg", "Configured target", "Labels", fmt.Sprintf("%+v", labels))
|
||
+ }
|
||
+ for _, err := range errors {
|
||
+ level.Error(d.logger).Log("msg", "Invalid exporter port", "clientId", systemID, "err", err)
|
||
+ }
|
||
+
|
||
+ return labelSets
|
||
+}
|
||
+
|
||
+func getSystemGroupNames(systemGroupsIDs []systemGroupID) []string {
|
||
+ managedGroupNames := make([]string, 0, len(systemGroupsIDs))
|
||
+ for _, systemGroupInfo := range systemGroupsIDs {
|
||
+ managedGroupNames = append(managedGroupNames, systemGroupInfo.GroupName)
|
||
+ }
|
||
+
|
||
+ if len(managedGroupNames) == 0 {
|
||
+ managedGroupNames = []string{"No group"}
|
||
+ }
|
||
+ return managedGroupNames
|
||
+}
|
||
+
|
||
+func (d *Discovery) getTargetsForSystems(
|
||
+ rpcClient *xmlrpc.Client,
|
||
+ token string,
|
||
+ systemGroupIDsBySystemID map[int][]systemGroupID,
|
||
+) ([]model.LabelSet, error) {
|
||
+
|
||
+ result := make([]model.LabelSet, 0)
|
||
+
|
||
+ systemIDs := make([]int, 0, len(systemGroupIDsBySystemID))
|
||
+ for systemID := range systemGroupIDsBySystemID {
|
||
+ systemIDs = append(systemIDs, systemID)
|
||
+ }
|
||
+
|
||
+ combinedFormulaDataBySystemID, err := getExporterDataForSystems(rpcClient, token, systemIDs)
|
||
+ if err != nil {
|
||
+ return nil, errors.Wrap(err, "Unable to get systems combined formula data")
|
||
+ }
|
||
+ networkInfoBySystemID, err := getNetworkInformationForSystems(rpcClient, token, systemIDs)
|
||
+ if err != nil {
|
||
+ return nil, errors.Wrap(err, "Unable to get the systems network information")
|
||
+ }
|
||
+
|
||
+ for _, systemID := range systemIDs {
|
||
+ targets := d.getTargetsForSystem(
|
||
+ systemID,
|
||
+ systemGroupIDsBySystemID[systemID],
|
||
+ networkInfoBySystemID[systemID],
|
||
+ combinedFormulaDataBySystemID[systemID])
|
||
+ result = append(result, targets...)
|
||
+
|
||
+ // Log debug information
|
||
+ if networkInfoBySystemID[systemID].IP != "" {
|
||
+ level.Debug(d.logger).Log("msg", "Found monitored system",
|
||
+ "Host", networkInfoBySystemID[systemID].Hostname,
|
||
+ "Network", fmt.Sprintf("%+v", networkInfoBySystemID[systemID]),
|
||
+ "Groups", fmt.Sprintf("%+v", systemGroupIDsBySystemID[systemID]),
|
||
+ "Formulas", fmt.Sprintf("%+v", combinedFormulaDataBySystemID[systemID]))
|
||
+ }
|
||
+ }
|
||
+ return result, nil
|
||
+}
|
||
+
|
||
+func (d *Discovery) refresh(ctx context.Context) ([]*targetgroup.Group, error) {
|
||
+ config := d.sdConfig
|
||
+ apiURL := config.Host + uyuniXMLRPCAPIPath
|
||
+
|
||
+ startTime := time.Now()
|
||
+
|
||
+ // Check if the URL is valid and create rpc client
|
||
+ _, err := url.ParseRequestURI(apiURL)
|
||
+ if err != nil {
|
||
+ return nil, errors.Wrap(err, "Uyuni Server URL is not valid")
|
||
+ }
|
||
+
|
||
+ rpcClient, _ := xmlrpc.NewClient(apiURL, nil)
|
||
+
|
||
+ token, err := login(rpcClient, config.User, config.Pass)
|
||
+ if err != nil {
|
||
+ return nil, errors.Wrap(err, "Unable to login to Uyuni API")
|
||
+ }
|
||
+ systemGroupIDsBySystemID, err := getSystemGroupsInfoOfMonitoredClients(rpcClient, token)
|
||
+ if err != nil {
|
||
+ return nil, errors.Wrap(err, "Unable to get the managed system groups information of monitored clients")
|
||
+ }
|
||
+
|
||
+ targets := make([]model.LabelSet, 0)
|
||
+ if len(systemGroupIDsBySystemID) > 0 {
|
||
+ targetsForSystems, err := d.getTargetsForSystems(rpcClient, token, systemGroupIDsBySystemID)
|
||
+ if err != nil {
|
||
+ return nil, err
|
||
+ }
|
||
+ targets = append(targets, targetsForSystems...)
|
||
+ level.Info(d.logger).Log("msg", "Total discovery time", "time", time.Since(startTime))
|
||
+ } else {
|
||
+ fmt.Printf("\tFound 0 systems.\n")
|
||
+ }
|
||
+
|
||
+ err = logout(rpcClient, token)
|
||
+ if err != nil {
|
||
+ level.Warn(d.logger).Log("msg", "Failed to log out from Uyuni API", "err", err)
|
||
+ }
|
||
+ rpcClient.Close()
|
||
+ return []*targetgroup.Group{&targetgroup.Group{Targets: targets, Source: config.Host}}, nil
|
||
+}
|
||
Index: prometheus-2.18.0/discovery/uyuni/uyuni_test.go
|
||
===================================================================
|
||
--- /dev/null
|
||
+++ prometheus-2.18.0/discovery/uyuni/uyuni_test.go
|
||
@@ -0,0 +1,33 @@
|
||
+package uyuni
|
||
+
|
||
+import "testing"
|
||
+
|
||
+func TestExtractPortFromFormulaData(t *testing.T) {
|
||
+ type args struct {
|
||
+ args string
|
||
+ address string
|
||
+ }
|
||
+ tests := []struct {
|
||
+ name string
|
||
+ args args
|
||
+ want string
|
||
+ wantErr bool
|
||
+ }{
|
||
+ {name: `TestArgs`, args: args{args: `--web.listen-address=":9100"`}, want: `9100`},
|
||
+ {name: `TestAddress`, args: args{address: `:9100`}, want: `9100`},
|
||
+ {name: `TestArgsAndAddress`, args: args{args: `--web.listen-address=":9100"`, address: `9999`}, want: `9100`},
|
||
+ {name: `TestMissingPort`, args: args{args: `localhost`}, wantErr: true},
|
||
+ }
|
||
+ for _, tt := range tests {
|
||
+ t.Run(tt.name, func(t *testing.T) {
|
||
+ got, err := extractPortFromFormulaData(tt.args.args, tt.args.address)
|
||
+ if (err != nil) != tt.wantErr {
|
||
+ t.Errorf("extractPortFromFormulaData() error = %v, wantErr %v", err, tt.wantErr)
|
||
+ return
|
||
+ }
|
||
+ if got != tt.want {
|
||
+ t.Errorf("extractPortFromFormulaData() got = %v, want %v", got, tt.want)
|
||
+ }
|
||
+ })
|
||
+ }
|
||
+}
|
||
Index: prometheus-2.18.0/go.mod
|
||
===================================================================
|
||
--- prometheus-2.18.0.orig/go.mod
|
||
+++ prometheus-2.18.0/go.mod
|
||
@@ -41,6 +41,7 @@ require (
|
||
github.com/jpillora/backoff v1.0.0 // indirect
|
||
github.com/json-iterator/go v1.1.9
|
||
github.com/julienschmidt/httprouter v1.3.0 // indirect
|
||
+ github.com/kolo/xmlrpc v0.0.0-20200310150728-e0350524596b
|
||
github.com/mattn/go-colorable v0.1.6 // indirect
|
||
github.com/miekg/dns v1.1.29
|
||
github.com/mitchellh/mapstructure v1.2.2 // indirect
|
||
Index: prometheus-2.18.0/go.sum
|
||
===================================================================
|
||
--- prometheus-2.18.0.orig/go.sum
|
||
+++ prometheus-2.18.0/go.sum
|
||
@@ -505,6 +505,8 @@ github.com/klauspost/compress v1.9.5/go.
|
||
github.com/klauspost/cpuid v0.0.0-20170728055534-ae7887de9fa5/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
|
||
github.com/klauspost/crc32 v0.0.0-20161016154125-cb6bfca970f6/go.mod h1:+ZoRqAPRLkC4NPOvfYeR5KNOrY6TD+/sAC3HXPZgDYg=
|
||
github.com/klauspost/pgzip v1.0.2-0.20170402124221-0bf5dcad4ada/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
|
||
+github.com/kolo/xmlrpc v0.0.0-20200310150728-e0350524596b h1:DzHy0GlWeF0KAglaTMY7Q+khIFoG8toHP+wLFBVBQJc=
|
||
+github.com/kolo/xmlrpc v0.0.0-20200310150728-e0350524596b/go.mod h1:o03bZfuBwAXHetKXuInt4S7omeXUu62/A845kiycsSQ=
|
||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515 h1:T+h1c/A9Gawja4Y9mFVWj2vyii2bbUNDw3kt9VxK2EY=
|
||
Index: prometheus-2.18.0/vendor/github.com/kolo/xmlrpc/LICENSE
|
||
===================================================================
|
||
--- /dev/null
|
||
+++ prometheus-2.18.0/vendor/github.com/kolo/xmlrpc/LICENSE
|
||
@@ -0,0 +1,19 @@
|
||
+Copyright (C) 2012 Dmitry Maksimov
|
||
+
|
||
+Permission is hereby granted, free of charge, to any person obtaining a copy
|
||
+of this software and associated documentation files (the "Software"), to deal
|
||
+in the Software without restriction, including without limitation the rights
|
||
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||
+copies of the Software, and to permit persons to whom the Software is
|
||
+furnished to do so, subject to the following conditions:
|
||
+
|
||
+The above copyright notice and this permission notice shall be included in
|
||
+all copies or substantial portions of the Software.
|
||
+
|
||
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||
+THE SOFTWARE.
|
||
Index: prometheus-2.18.0/vendor/github.com/kolo/xmlrpc/README.md
|
||
===================================================================
|
||
--- /dev/null
|
||
+++ prometheus-2.18.0/vendor/github.com/kolo/xmlrpc/README.md
|
||
@@ -0,0 +1,89 @@
|
||
+[![GoDoc](https://godoc.org/github.com/kolo/xmlrpc?status.svg)](https://godoc.org/github.com/kolo/xmlrpc)
|
||
+
|
||
+## Overview
|
||
+
|
||
+xmlrpc is an implementation of client side part of XMLRPC protocol in Go language.
|
||
+
|
||
+## Status
|
||
+
|
||
+This project is in minimal maintenance mode with no further development. Bug fixes
|
||
+are accepted, but it might take some time until they will be merged.
|
||
+
|
||
+## 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 encoded 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
|
||
+
|
||
+See [project status](#status).
|
||
+
|
||
+## Authors
|
||
+
|
||
+Dmitry Maksimov (dmtmax@gmail.com)
|
||
Index: prometheus-2.18.0/vendor/github.com/kolo/xmlrpc/client.go
|
||
===================================================================
|
||
--- /dev/null
|
||
+++ prometheus-2.18.0/vendor/github.com/kolo/xmlrpc/client.go
|
||
@@ -0,0 +1,170 @@
|
||
+package xmlrpc
|
||
+
|
||
+import (
|
||
+ "errors"
|
||
+ "fmt"
|
||
+ "io/ioutil"
|
||
+ "net/http"
|
||
+ "net/http/cookiejar"
|
||
+ "net/rpc"
|
||
+ "net/url"
|
||
+ "sync"
|
||
+)
|
||
+
|
||
+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
|
||
+ mutex sync.Mutex
|
||
+
|
||
+ response *Response
|
||
+
|
||
+ // ready presents channel, that is used to link request and it`s response.
|
||
+ ready chan uint64
|
||
+
|
||
+ // close notifies codec is closed.
|
||
+ close 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.mutex.Lock()
|
||
+ codec.responses[request.Seq] = httpResponse
|
||
+ codec.mutex.Unlock()
|
||
+
|
||
+ codec.ready <- request.Seq
|
||
+
|
||
+ return nil
|
||
+}
|
||
+
|
||
+func (codec *clientCodec) ReadResponseHeader(response *rpc.Response) (err error) {
|
||
+ var seq uint64
|
||
+
|
||
+ select {
|
||
+ case seq = <-codec.ready:
|
||
+ case <-codec.close:
|
||
+ return errors.New("codec is closed")
|
||
+ }
|
||
+
|
||
+ codec.mutex.Lock()
|
||
+ httpResponse := codec.responses[seq]
|
||
+ codec.mutex.Unlock()
|
||
+
|
||
+ 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
|
||
+
|
||
+ codec.mutex.Lock()
|
||
+ delete(codec.responses, seq)
|
||
+ codec.mutex.Unlock()
|
||
+
|
||
+ 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 {
|
||
+ if transport, ok := codec.httpClient.Transport.(*http.Transport); ok {
|
||
+ transport.CloseIdleConnections()
|
||
+ }
|
||
+
|
||
+ close(codec.close)
|
||
+
|
||
+ 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,
|
||
+ close: make(chan uint64),
|
||
+ ready: make(chan uint64),
|
||
+ responses: make(map[uint64]*http.Response),
|
||
+ cookies: jar,
|
||
+ }
|
||
+
|
||
+ return &Client{rpc.NewClientWithCodec(&codec)}, nil
|
||
+}
|
||
Index: prometheus-2.18.0/vendor/github.com/kolo/xmlrpc/client_test.go
|
||
===================================================================
|
||
--- /dev/null
|
||
+++ prometheus-2.18.0/vendor/github.com/kolo/xmlrpc/client_test.go
|
||
@@ -0,0 +1,141 @@
|
||
+// +build integration
|
||
+
|
||
+package xmlrpc
|
||
+
|
||
+import (
|
||
+ "context"
|
||
+ "runtime"
|
||
+ "sync"
|
||
+ "testing"
|
||
+ "time"
|
||
+)
|
||
+
|
||
+func Test_CallWithoutArgs(t *testing.T) {
|
||
+ client := newClient(t)
|
||
+ defer client.Close()
|
||
+
|
||
+ var result time.Time
|
||
+ if err := client.Call("service.time", nil, &result); err != nil {
|
||
+ t.Fatalf("service.time call error: %v", err)
|
||
+ }
|
||
+}
|
||
+
|
||
+func Test_CallWithOneArg(t *testing.T) {
|
||
+ client := newClient(t)
|
||
+ defer client.Close()
|
||
+
|
||
+ var result string
|
||
+ if err := client.Call("service.upcase", "xmlrpc", &result); err != nil {
|
||
+ t.Fatalf("service.upcase call error: %v", err)
|
||
+ }
|
||
+
|
||
+ if result != "XMLRPC" {
|
||
+ t.Fatalf("Unexpected result of service.upcase: %s != %s", "XMLRPC", result)
|
||
+ }
|
||
+}
|
||
+
|
||
+func Test_CallWithTwoArgs(t *testing.T) {
|
||
+ client := newClient(t)
|
||
+ defer client.Close()
|
||
+
|
||
+ var sum int
|
||
+ if err := client.Call("service.sum", []interface{}{2, 3}, &sum); err != nil {
|
||
+ t.Fatalf("service.sum call error: %v", err)
|
||
+ }
|
||
+
|
||
+ if sum != 5 {
|
||
+ t.Fatalf("Unexpected result of service.sum: %d != %d", 5, sum)
|
||
+ }
|
||
+}
|
||
+
|
||
+func Test_TwoCalls(t *testing.T) {
|
||
+ client := newClient(t)
|
||
+ defer client.Close()
|
||
+
|
||
+ var upcase string
|
||
+ if err := client.Call("service.upcase", "xmlrpc", &upcase); err != nil {
|
||
+ t.Fatalf("service.upcase call error: %v", err)
|
||
+ }
|
||
+
|
||
+ var sum int
|
||
+ if err := client.Call("service.sum", []interface{}{2, 3}, &sum); err != nil {
|
||
+ t.Fatalf("service.sum call error: %v", err)
|
||
+ }
|
||
+
|
||
+}
|
||
+
|
||
+func Test_FailedCall(t *testing.T) {
|
||
+ client := newClient(t)
|
||
+ defer client.Close()
|
||
+
|
||
+ var result int
|
||
+ if err := client.Call("service.error", nil, &result); err == nil {
|
||
+ t.Fatal("expected service.error returns error, but it didn't")
|
||
+ }
|
||
+}
|
||
+
|
||
+func Test_ConcurrentCalls(t *testing.T) {
|
||
+ client := newClient(t)
|
||
+
|
||
+ call := func() {
|
||
+ var result time.Time
|
||
+ client.Call("service.time", nil, &result)
|
||
+ }
|
||
+
|
||
+ var wg sync.WaitGroup
|
||
+ for i := 0; i < 100; i++ {
|
||
+ wg.Add(1)
|
||
+ go func() {
|
||
+ call()
|
||
+ wg.Done()
|
||
+ }()
|
||
+ }
|
||
+
|
||
+ wg.Wait()
|
||
+ client.Close()
|
||
+}
|
||
+
|
||
+func Test_CloseMemoryLeak(t *testing.T) {
|
||
+ expected := runtime.NumGoroutine()
|
||
+
|
||
+ for i := 0; i < 3; i++ {
|
||
+ client := newClient(t)
|
||
+ client.Call("service.time", nil, nil)
|
||
+ client.Close()
|
||
+ }
|
||
+
|
||
+ var actual int
|
||
+
|
||
+ // It takes some time to stop running goroutinges. This function checks number of
|
||
+ // running goroutines. It finishes execution if number is same as expected or timeout
|
||
+ // has been reached.
|
||
+ func() {
|
||
+ ctx, cancel := context.WithTimeout(context.Background(), time.Second)
|
||
+ defer cancel()
|
||
+
|
||
+ for {
|
||
+ select {
|
||
+ case <-ctx.Done():
|
||
+ return
|
||
+ default:
|
||
+ actual = runtime.NumGoroutine()
|
||
+ if actual == expected {
|
||
+ return
|
||
+ }
|
||
+ }
|
||
+ }
|
||
+ }()
|
||
+
|
||
+ if actual != expected {
|
||
+ t.Errorf("expected number of running goroutines to be %d, but got %d", expected, actual)
|
||
+ }
|
||
+}
|
||
+
|
||
+func newClient(t *testing.T) *Client {
|
||
+ client, err := NewClient("http://localhost:5001", nil)
|
||
+ if err != nil {
|
||
+ t.Fatalf("Can't create client: %v", err)
|
||
+ }
|
||
+
|
||
+ return client
|
||
+}
|
||
Index: prometheus-2.18.0/vendor/github.com/kolo/xmlrpc/decoder.go
|
||
===================================================================
|
||
--- /dev/null
|
||
+++ prometheus-2.18.0/vendor/github.com/kolo/xmlrpc/decoder.go
|
||
@@ -0,0 +1,473 @@
|
||
+package xmlrpc
|
||
+
|
||
+import (
|
||
+ "bytes"
|
||
+ "encoding/xml"
|
||
+ "errors"
|
||
+ "fmt"
|
||
+ "io"
|
||
+ "reflect"
|
||
+ "strconv"
|
||
+ "strings"
|
||
+ "time"
|
||
+)
|
||
+
|
||
+const (
|
||
+ iso8601 = "20060102T15:04:05"
|
||
+ iso8601Z = "20060102T15:04:05Z07:00"
|
||
+ iso8601Hyphen = "2006-01-02T15:04:05"
|
||
+ iso8601HyphenZ = "2006-01-02T15:04:05Z07:00"
|
||
+)
|
||
+
|
||
+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)
|
||
+
|
||
+ timeLayouts = []string{iso8601, iso8601Z, iso8601Hyphen, iso8601HyphenZ}
|
||
+ 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{}
|
||
+ valType = reflect.TypeOf(dummy)
|
||
+ pmap = reflect.New(valType).Elem()
|
||
+ val.Set(pmap)
|
||
+ 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":
|
||
+ slice := val
|
||
+ if checkType(val, reflect.Interface) == nil && val.IsNil() {
|
||
+ slice = reflect.ValueOf([]interface{}{})
|
||
+ } 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:
|
||
+ var index int
|
||
+ if t.Name.Local != "data" {
|
||
+ return invalidXmlError
|
||
+ }
|
||
+ 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
|
||
+ }
|
||
+
|
||
+ if index < slice.Len() {
|
||
+ v := slice.Index(index)
|
||
+ if v.Kind() == reflect.Interface {
|
||
+ v = v.Elem()
|
||
+ }
|
||
+ if v.Kind() != reflect.Ptr {
|
||
+ return errors.New("error: cannot write to non-pointer array element")
|
||
+ }
|
||
+ if err = dec.decodeValue(v); err != nil {
|
||
+ return err
|
||
+ }
|
||
+ } else {
|
||
+ v := reflect.New(slice.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
|
||
+ }
|
||
+ index++
|
||
+ case xml.EndElement:
|
||
+ val.Set(slice)
|
||
+ 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":
|
||
+ var t time.Time
|
||
+ var err error
|
||
+
|
||
+ for _, layout := range timeLayouts {
|
||
+ t, err = time.Parse(layout, string(data))
|
||
+ if err == nil {
|
||
+ break
|
||
+ }
|
||
+ }
|
||
+ 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
|
||
+}
|
||
Index: prometheus-2.18.0/vendor/github.com/kolo/xmlrpc/decoder_test.go
|
||
===================================================================
|
||
--- /dev/null
|
||
+++ prometheus-2.18.0/vendor/github.com/kolo/xmlrpc/decoder_test.go
|
||
@@ -0,0 +1,234 @@
|
||
+package xmlrpc
|
||
+
|
||
+import (
|
||
+ "fmt"
|
||
+ "io"
|
||
+ "io/ioutil"
|
||
+ "reflect"
|
||
+ "testing"
|
||
+ "time"
|
||
+
|
||
+ "golang.org/x/text/encoding/charmap"
|
||
+ "golang.org/x/text/transform"
|
||
+)
|
||
+
|
||
+type book struct {
|
||
+ Title string
|
||
+ Amount int
|
||
+}
|
||
+
|
||
+type bookUnexported struct {
|
||
+ title string
|
||
+ amount int
|
||
+}
|
||
+
|
||
+var unmarshalTests = []struct {
|
||
+ value interface{}
|
||
+ ptr interface{}
|
||
+ xml string
|
||
+}{
|
||
+ // int, i4, i8
|
||
+ {0, new(*int), "<value><int></int></value>"},
|
||
+ {100, new(*int), "<value><int>100</int></value>"},
|
||
+ {389451, new(*int), "<value><i4>389451</i4></value>"},
|
||
+ {int64(45659074), new(*int64), "<value><i8>45659074</i8></value>"},
|
||
+
|
||
+ // string
|
||
+ {"Once upon a time", new(*string), "<value><string>Once upon a time</string></value>"},
|
||
+ {"Mike & Mick <London, UK>", new(*string), "<value><string>Mike & Mick <London, UK></string></value>"},
|
||
+ {"Once upon a time", new(*string), "<value>Once upon a time</value>"},
|
||
+
|
||
+ // base64
|
||
+ {"T25jZSB1cG9uIGEgdGltZQ==", new(*string), "<value><base64>T25jZSB1cG9uIGEgdGltZQ==</base64></value>"},
|
||
+
|
||
+ // boolean
|
||
+ {true, new(*bool), "<value><boolean>1</boolean></value>"},
|
||
+ {false, new(*bool), "<value><boolean>0</boolean></value>"},
|
||
+
|
||
+ // double
|
||
+ {12.134, new(*float32), "<value><double>12.134</double></value>"},
|
||
+ {-12.134, new(*float32), "<value><double>-12.134</double></value>"},
|
||
+
|
||
+ // datetime.iso8601
|
||
+ {_time("2013-12-09T21:00:12Z"), new(*time.Time), "<value><dateTime.iso8601>20131209T21:00:12</dateTime.iso8601></value>"},
|
||
+ {_time("2013-12-09T21:00:12Z"), new(*time.Time), "<value><dateTime.iso8601>20131209T21:00:12Z</dateTime.iso8601></value>"},
|
||
+ {_time("2013-12-09T21:00:12-01:00"), new(*time.Time), "<value><dateTime.iso8601>20131209T21:00:12-01:00</dateTime.iso8601></value>"},
|
||
+ {_time("2013-12-09T21:00:12+01:00"), new(*time.Time), "<value><dateTime.iso8601>20131209T21:00:12+01:00</dateTime.iso8601></value>"},
|
||
+ {_time("2013-12-09T21:00:12Z"), new(*time.Time), "<value><dateTime.iso8601>2013-12-09T21:00:12</dateTime.iso8601></value>"},
|
||
+ {_time("2013-12-09T21:00:12Z"), new(*time.Time), "<value><dateTime.iso8601>2013-12-09T21:00:12Z</dateTime.iso8601></value>"},
|
||
+ {_time("2013-12-09T21:00:12-01:00"), new(*time.Time), "<value><dateTime.iso8601>2013-12-09T21:00:12-01:00</dateTime.iso8601></value>"},
|
||
+ {_time("2013-12-09T21:00:12+01:00"), new(*time.Time), "<value><dateTime.iso8601>2013-12-09T21:00:12+01:00</dateTime.iso8601></value>"},
|
||
+
|
||
+ // array
|
||
+ {[]int{1, 5, 7}, new(*[]int), "<value><array><data><value><int>1</int></value><value><int>5</int></value><value><int>7</int></value></data></array></value>"},
|
||
+ {[]interface{}{"A", "5"}, new(interface{}), "<value><array><data><value><string>A</string></value><value><string>5</string></value></data></array></value>"},
|
||
+ {[]interface{}{"A", int64(5)}, new(interface{}), "<value><array><data><value><string>A</string></value><value><int>5</int></value></data></array></value>"},
|
||
+
|
||
+ // struct
|
||
+ {book{"War and Piece", 20}, new(*book), "<value><struct><member><name>Title</name><value><string>War and Piece</string></value></member><member><name>Amount</name><value><int>20</int></value></member></struct></value>"},
|
||
+ {bookUnexported{}, new(*bookUnexported), "<value><struct><member><name>title</name><value><string>War and Piece</string></value></member><member><name>amount</name><value><int>20</int></value></member></struct></value>"},
|
||
+ {map[string]interface{}{"Name": "John Smith"}, new(interface{}), "<value><struct><member><name>Name</name><value><string>John Smith</string></value></member></struct></value>"},
|
||
+ {map[string]interface{}{}, new(interface{}), "<value><struct></struct></value>"},
|
||
+}
|
||
+
|
||
+func _time(s string) time.Time {
|
||
+ t, err := time.Parse(time.RFC3339, s)
|
||
+ if err != nil {
|
||
+ panic(fmt.Sprintf("time parsing error: %v", err))
|
||
+ }
|
||
+ return t
|
||
+}
|
||
+
|
||
+func Test_unmarshal(t *testing.T) {
|
||
+ for _, tt := range unmarshalTests {
|
||
+ v := reflect.New(reflect.TypeOf(tt.value))
|
||
+ if err := unmarshal([]byte(tt.xml), v.Interface()); err != nil {
|
||
+ t.Fatalf("unmarshal error: %v", err)
|
||
+ }
|
||
+
|
||
+ v = v.Elem()
|
||
+
|
||
+ if v.Kind() == reflect.Slice {
|
||
+ vv := reflect.ValueOf(tt.value)
|
||
+ if vv.Len() != v.Len() {
|
||
+ t.Fatalf("unmarshal error:\nexpected: %v\n got: %v", tt.value, v.Interface())
|
||
+ }
|
||
+ for i := 0; i < v.Len(); i++ {
|
||
+ if v.Index(i).Interface() != vv.Index(i).Interface() {
|
||
+ t.Fatalf("unmarshal error:\nexpected: %v\n got: %v", tt.value, v.Interface())
|
||
+ }
|
||
+ }
|
||
+ } else {
|
||
+ a1 := v.Interface()
|
||
+ a2 := interface{}(tt.value)
|
||
+
|
||
+ if !reflect.DeepEqual(a1, a2) {
|
||
+ t.Fatalf("unmarshal error:\nexpected: %v\n got: %v", tt.value, v.Interface())
|
||
+ }
|
||
+ }
|
||
+ }
|
||
+}
|
||
+
|
||
+func Test_unmarshalToNil(t *testing.T) {
|
||
+ for _, tt := range unmarshalTests {
|
||
+ if err := unmarshal([]byte(tt.xml), tt.ptr); err != nil {
|
||
+ t.Fatalf("unmarshal error: %v", err)
|
||
+ }
|
||
+ }
|
||
+}
|
||
+
|
||
+func Test_typeMismatchError(t *testing.T) {
|
||
+ var s string
|
||
+
|
||
+ encoded := "<value><int>100</int></value>"
|
||
+ var err error
|
||
+
|
||
+ if err = unmarshal([]byte(encoded), &s); err == nil {
|
||
+ t.Fatal("unmarshal error: expected error, but didn't get it")
|
||
+ }
|
||
+
|
||
+ if _, ok := err.(TypeMismatchError); !ok {
|
||
+ t.Fatal("unmarshal error: expected type mistmatch error, but didn't get it")
|
||
+ }
|
||
+}
|
||
+
|
||
+func Test_unmarshalEmptyValueTag(t *testing.T) {
|
||
+ var v int
|
||
+
|
||
+ if err := unmarshal([]byte("<value/>"), &v); err != nil {
|
||
+ t.Fatalf("unmarshal error: %v", err)
|
||
+ }
|
||
+}
|
||
+
|
||
+const structEmptyXML = `
|
||
+<value>
|
||
+ <struct>
|
||
+ </struct>
|
||
+</value>
|
||
+`
|
||
+
|
||
+func Test_unmarshalEmptyStruct(t *testing.T) {
|
||
+ var v interface{}
|
||
+ if err := unmarshal([]byte(structEmptyXML), &v); err != nil {
|
||
+ t.Fatal(err)
|
||
+ }
|
||
+ if v == nil {
|
||
+ t.Fatalf("got nil map")
|
||
+ }
|
||
+}
|
||
+
|
||
+const arrayValueXML = `
|
||
+<value>
|
||
+ <array>
|
||
+ <data>
|
||
+ <value><int>234</int></value>
|
||
+ <value><boolean>1</boolean></value>
|
||
+ <value><string>Hello World</string></value>
|
||
+ <value><string>Extra Value</string></value>
|
||
+ </data>
|
||
+ </array>
|
||
+</value>
|
||
+`
|
||
+
|
||
+func Test_unmarshalExistingArray(t *testing.T) {
|
||
+
|
||
+ var (
|
||
+ v1 int
|
||
+ v2 bool
|
||
+ v3 string
|
||
+
|
||
+ v = []interface{}{&v1, &v2, &v3}
|
||
+ )
|
||
+ if err := unmarshal([]byte(arrayValueXML), &v); err != nil {
|
||
+ t.Fatal(err)
|
||
+ }
|
||
+
|
||
+ // check pre-existing values
|
||
+ if want := 234; v1 != want {
|
||
+ t.Fatalf("want %d, got %d", want, v1)
|
||
+ }
|
||
+ if want := true; v2 != want {
|
||
+ t.Fatalf("want %t, got %t", want, v2)
|
||
+ }
|
||
+ if want := "Hello World"; v3 != want {
|
||
+ t.Fatalf("want %s, got %s", want, v3)
|
||
+ }
|
||
+ // check the appended result
|
||
+ if n := len(v); n != 4 {
|
||
+ t.Fatalf("missing appended result")
|
||
+ }
|
||
+ if got, ok := v[3].(string); !ok || got != "Extra Value" {
|
||
+ t.Fatalf("got %s, want %s", got, "Extra Value")
|
||
+ }
|
||
+}
|
||
+
|
||
+func Test_decodeNonUTF8Response(t *testing.T) {
|
||
+ data, err := ioutil.ReadFile("fixtures/cp1251.xml")
|
||
+ if err != nil {
|
||
+ t.Fatal(err)
|
||
+ }
|
||
+
|
||
+ CharsetReader = decode
|
||
+
|
||
+ var s string
|
||
+ if err = unmarshal(data, &s); err != nil {
|
||
+ fmt.Println(err)
|
||
+ t.Fatal("unmarshal error: cannot decode non utf-8 response")
|
||
+ }
|
||
+
|
||
+ expected := "Л.Н. Толстой - Война и Мир"
|
||
+
|
||
+ if s != expected {
|
||
+ t.Fatalf("unmarshal error:\nexpected: %v\n got: %v", expected, s)
|
||
+ }
|
||
+
|
||
+ CharsetReader = nil
|
||
+}
|
||
+
|
||
+func decode(charset string, input io.Reader) (io.Reader, error) {
|
||
+ if charset != "cp1251" {
|
||
+ return nil, fmt.Errorf("unsupported charset")
|
||
+ }
|
||
+
|
||
+ return transform.NewReader(input, charmap.Windows1251.NewDecoder()), nil
|
||
+}
|
||
Index: prometheus-2.18.0/vendor/github.com/kolo/xmlrpc/encoder.go
|
||
===================================================================
|
||
--- /dev/null
|
||
+++ prometheus-2.18.0/vendor/github.com/kolo/xmlrpc/encoder.go
|
||
@@ -0,0 +1,171 @@
|
||
+package xmlrpc
|
||
+
|
||
+import (
|
||
+ "bytes"
|
||
+ "encoding/xml"
|
||
+ "fmt"
|
||
+ "reflect"
|
||
+ "sort"
|
||
+ "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(), 'f', -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
|
||
+}
|
||
+
|
||
+var sortMapKeys bool
|
||
+
|
||
+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()
|
||
+
|
||
+ if sortMapKeys {
|
||
+ sort.Slice(keys, func(i, j int) bool { return keys[i].String() < keys[j].String() })
|
||
+ }
|
||
+
|
||
+ 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
|
||
+}
|
||
Index: prometheus-2.18.0/vendor/github.com/kolo/xmlrpc/encoder_test.go
|
||
===================================================================
|
||
--- /dev/null
|
||
+++ prometheus-2.18.0/vendor/github.com/kolo/xmlrpc/encoder_test.go
|
||
@@ -0,0 +1,58 @@
|
||
+package xmlrpc
|
||
+
|
||
+import (
|
||
+ "testing"
|
||
+ "time"
|
||
+)
|
||
+
|
||
+var marshalTests = []struct {
|
||
+ value interface{}
|
||
+ xml string
|
||
+}{
|
||
+ {100, "<value><int>100</int></value>"},
|
||
+ {"Once upon a time", "<value><string>Once upon a time</string></value>"},
|
||
+ {"Mike & Mick <London, UK>", "<value><string>Mike & Mick <London, UK></string></value>"},
|
||
+ {Base64("T25jZSB1cG9uIGEgdGltZQ=="), "<value><base64>T25jZSB1cG9uIGEgdGltZQ==</base64></value>"},
|
||
+ {true, "<value><boolean>1</boolean></value>"},
|
||
+ {false, "<value><boolean>0</boolean></value>"},
|
||
+ {12.134, "<value><double>12.134</double></value>"},
|
||
+ {-12.134, "<value><double>-12.134</double></value>"},
|
||
+ {738777323.0, "<value><double>738777323</double></value>"},
|
||
+ {time.Unix(1386622812, 0).UTC(), "<value><dateTime.iso8601>20131209T21:00:12</dateTime.iso8601></value>"},
|
||
+ {[]interface{}{1, "one"}, "<value><array><data><value><int>1</int></value><value><string>one</string></value></data></array></value>"},
|
||
+ {&struct {
|
||
+ Title string
|
||
+ Amount int
|
||
+ }{"War and Piece", 20}, "<value><struct><member><name>Title</name><value><string>War and Piece</string></value></member><member><name>Amount</name><value><int>20</int></value></member></struct></value>"},
|
||
+ {&struct {
|
||
+ Value interface{} `xmlrpc:"value"`
|
||
+ }{}, "<value><struct><member><name>value</name><value/></member></struct></value>"},
|
||
+ {
|
||
+ map[string]interface{}{"title": "War and Piece", "amount": 20},
|
||
+ "<value><struct><member><name>amount</name><value><int>20</int></value></member><member><name>title</name><value><string>War and Piece</string></value></member></struct></value>",
|
||
+ },
|
||
+ {
|
||
+ map[string]interface{}{
|
||
+ "Name": "John Smith",
|
||
+ "Age": 6,
|
||
+ "Wight": []float32{66.67, 100.5},
|
||
+ "Dates": map[string]interface{}{"Birth": time.Date(1829, time.November, 10, 23, 0, 0, 0, time.UTC), "Death": time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC)}},
|
||
+ "<value><struct><member><name>Age</name><value><int>6</int></value></member><member><name>Dates</name><value><struct><member><name>Birth</name><value><dateTime.iso8601>18291110T23:00:00</dateTime.iso8601></value></member><member><name>Death</name><value><dateTime.iso8601>20091110T23:00:00</dateTime.iso8601></value></member></struct></value></member><member><name>Name</name><value><string>John Smith</string></value></member><member><name>Wight</name><value><array><data><value><double>66.67</double></value><value><double>100.5</double></value></data></array></value></member></struct></value>",
|
||
+ },
|
||
+}
|
||
+
|
||
+func Test_marshal(t *testing.T) {
|
||
+ sortMapKeys = true
|
||
+
|
||
+ for _, tt := range marshalTests {
|
||
+ b, err := marshal(tt.value)
|
||
+ if err != nil {
|
||
+ t.Fatalf("unexpected marshal error: %v", err)
|
||
+ }
|
||
+
|
||
+ if string(b) != tt.xml {
|
||
+ t.Fatalf("marshal error:\nexpected: %s\n got: %s", tt.xml, string(b))
|
||
+ }
|
||
+
|
||
+ }
|
||
+}
|
||
Index: prometheus-2.18.0/vendor/github.com/kolo/xmlrpc/fixtures/cp1251.xml
|
||
===================================================================
|
||
--- /dev/null
|
||
+++ prometheus-2.18.0/vendor/github.com/kolo/xmlrpc/fixtures/cp1251.xml
|
||
@@ -0,0 +1,6 @@
|
||
+<?xml version="1.0" encoding="cp1251" ?>
|
||
+<methodResponse>
|
||
+ <params>
|
||
+ <param><value><string><3E>.<2E>. <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> - <20><><EFBFBD><EFBFBD><EFBFBD> <20> <20><><EFBFBD></string></value></param>
|
||
+ </params>
|
||
+</methodResponse>
|
||
\ No newline at end of file
|
||
Index: prometheus-2.18.0/vendor/github.com/kolo/xmlrpc/request.go
|
||
===================================================================
|
||
--- /dev/null
|
||
+++ prometheus-2.18.0/vendor/github.com/kolo/xmlrpc/request.go
|
||
@@ -0,0 +1,57 @@
|
||
+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
|
||
+}
|
||
Index: prometheus-2.18.0/vendor/github.com/kolo/xmlrpc/response.go
|
||
===================================================================
|
||
--- /dev/null
|
||
+++ prometheus-2.18.0/vendor/github.com/kolo/xmlrpc/response.go
|
||
@@ -0,0 +1,52 @@
|
||
+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
|
||
+}
|
||
Index: prometheus-2.18.0/vendor/github.com/kolo/xmlrpc/response_test.go
|
||
===================================================================
|
||
--- /dev/null
|
||
+++ prometheus-2.18.0/vendor/github.com/kolo/xmlrpc/response_test.go
|
||
@@ -0,0 +1,84 @@
|
||
+package xmlrpc
|
||
+
|
||
+import (
|
||
+ "testing"
|
||
+)
|
||
+
|
||
+const faultRespXml = `
|
||
+<?xml version="1.0" encoding="UTF-8"?>
|
||
+<methodResponse>
|
||
+ <fault>
|
||
+ <value>
|
||
+ <struct>
|
||
+ <member>
|
||
+ <name>faultString</name>
|
||
+ <value>
|
||
+ <string>You must log in before using this part of Bugzilla.</string>
|
||
+ </value>
|
||
+ </member>
|
||
+ <member>
|
||
+ <name>faultCode</name>
|
||
+ <value>
|
||
+ <int>410</int>
|
||
+ </value>
|
||
+ </member>
|
||
+ </struct>
|
||
+ </value>
|
||
+ </fault>
|
||
+</methodResponse>`
|
||
+
|
||
+func Test_failedResponse(t *testing.T) {
|
||
+ resp := NewResponse([]byte(faultRespXml))
|
||
+
|
||
+ if !resp.Failed() {
|
||
+ t.Fatal("Failed() error: expected true, got false")
|
||
+ }
|
||
+
|
||
+ if resp.Err() == nil {
|
||
+ t.Fatal("Err() error: expected error, got nil")
|
||
+ }
|
||
+
|
||
+ err := resp.Err().(*xmlrpcError)
|
||
+ if err.code != 410 && err.err != "You must log in before using this part of Bugzilla." {
|
||
+ t.Fatal("Err() error: got wrong error")
|
||
+ }
|
||
+}
|
||
+
|
||
+const emptyValResp = `
|
||
+<?xml version="1.0" encoding="UTF-8"?>
|
||
+<methodResponse>
|
||
+ <params>
|
||
+ <param>
|
||
+ <value>
|
||
+ <struct>
|
||
+ <member>
|
||
+ <name>user</name>
|
||
+ <value><string>Joe Smith</string></value>
|
||
+ </member>
|
||
+ <member>
|
||
+ <name>token</name>
|
||
+ <value/>
|
||
+ </member>
|
||
+ </struct>
|
||
+ </value>
|
||
+ </param>
|
||
+ </params>
|
||
+</methodResponse>`
|
||
+
|
||
+
|
||
+func Test_responseWithEmptyValue(t *testing.T) {
|
||
+ resp := NewResponse([]byte(emptyValResp))
|
||
+
|
||
+ result := struct{
|
||
+ User string `xmlrpc:"user"`
|
||
+ Token string `xmlrpc:"token"`
|
||
+ }{}
|
||
+
|
||
+ if err := resp.Unmarshal(&result); err != nil {
|
||
+ t.Fatalf("unmarshal error: %v", err)
|
||
+ }
|
||
+
|
||
+ if result.User != "Joe Smith" || result.Token != "" {
|
||
+ t.Fatalf("unexpected result: %v", result)
|
||
+ }
|
||
+}
|
||
Index: prometheus-2.18.0/vendor/github.com/kolo/xmlrpc/test_server.rb
|
||
===================================================================
|
||
--- /dev/null
|
||
+++ prometheus-2.18.0/vendor/github.com/kolo/xmlrpc/test_server.rb
|
||
@@ -0,0 +1,25 @@
|
||
+# 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
|
||
Index: prometheus-2.18.0/vendor/github.com/kolo/xmlrpc/xmlrpc.go
|
||
===================================================================
|
||
--- /dev/null
|
||
+++ prometheus-2.18.0/vendor/github.com/kolo/xmlrpc/xmlrpc.go
|
||
@@ -0,0 +1,19 @@
|
||
+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
|