added checkOverflow to config (#179)
authorConor Broderick <conor.broderick@robustperception.io>
Mon, 3 Jul 2017 13:27:09 +0000 (14:27 +0100)
committerBrian Brazil <brian.brazil@robustperception.io>
Mon, 3 Jul 2017 13:27:09 +0000 (14:27 +0100)
config.go [new file with mode: 0644]
config_test.go [new file with mode: 0644]
main.go
testdata/blackbox-bad.yml [new file with mode: 0644]
testdata/blackbox-good.yml [new file with mode: 0644]

diff --git a/config.go b/config.go
new file mode 100644 (file)
index 0000000..1a5ebc0
--- /dev/null
+++ b/config.go
@@ -0,0 +1,206 @@
+package main
+
+import (
+       "fmt"
+       "strings"
+       "sync"
+       "time"
+
+       "github.com/prometheus/common/config"
+)
+
+type Config struct {
+       Modules map[string]Module `yaml:"modules"`
+
+       // Catches all undefined fields and must be empty after parsing.
+       XXX map[string]interface{} `yaml:",inline"`
+}
+
+type SafeConfig struct {
+       sync.RWMutex
+       C *Config
+}
+
+type Module struct {
+       Prober  string        `yaml:"prober"`
+       Timeout time.Duration `yaml:"timeout"`
+       HTTP    HTTPProbe     `yaml:"http"`
+       TCP     TCPProbe      `yaml:"tcp"`
+       ICMP    ICMPProbe     `yaml:"icmp"`
+       DNS     DNSProbe      `yaml:"dns"`
+
+       // Catches all undefined fields and must be empty after parsing.
+       XXX map[string]interface{} `yaml:",inline"`
+}
+
+type HTTPProbe struct {
+       // Defaults to 2xx.
+       ValidStatusCodes       []int                   `yaml:"valid_status_codes"`
+       PreferredIPProtocol    string                  `yaml:"preferred_ip_protocol"`
+       NoFollowRedirects      bool                    `yaml:"no_follow_redirects"`
+       FailIfSSL              bool                    `yaml:"fail_if_ssl"`
+       FailIfNotSSL           bool                    `yaml:"fail_if_not_ssl"`
+       Method                 string                  `yaml:"method"`
+       Headers                map[string]string       `yaml:"headers"`
+       FailIfMatchesRegexp    []string                `yaml:"fail_if_matches_regexp"`
+       FailIfNotMatchesRegexp []string                `yaml:"fail_if_not_matches_regexp"`
+       Body                   string                  `yaml:"body"`
+       HTTPClientConfig       config.HTTPClientConfig `yaml:"http_client_config,inline"`
+
+       // Catches all undefined fields and must be empty after parsing.
+       XXX map[string]interface{} `yaml:",inline"`
+}
+
+type QueryResponse struct {
+       Expect string `yaml:"expect"`
+       Send   string `yaml:"send"`
+
+       // Catches all undefined fields and must be empty after parsing.
+       XXX map[string]interface{} `yaml:",inline"`
+}
+
+type TCPProbe struct {
+       PreferredIPProtocol string           `yaml:"preferred_ip_protocol"`
+       QueryResponse       []QueryResponse  `yaml:"query_response"`
+       TLS                 bool             `yaml:"tls"`
+       TLSConfig           config.TLSConfig `yaml:"tls_config"`
+
+       // Catches all undefined fields and must be empty after parsing.
+       XXX map[string]interface{} `yaml:",inline"`
+}
+
+type ICMPProbe struct {
+       PreferredIPProtocol string `yaml:"preferred_ip_protocol"` // Defaults to "ip6".
+
+       // Catches all undefined fields and must be empty after parsing.
+       XXX map[string]interface{} `yaml:",inline"`
+}
+
+type DNSProbe struct {
+       PreferredIPProtocol string         `yaml:"preferred_ip_protocol"`
+       TransportProtocol   string         `yaml:"transport_protocol"`
+       QueryName           string         `yaml:"query_name"`
+       QueryType           string         `yaml:"query_type"`   // Defaults to ANY.
+       ValidRcodes         []string       `yaml:"valid_rcodes"` // Defaults to NOERROR.
+       ValidateAnswer      DNSRRValidator `yaml:"validate_answer_rrs"`
+       ValidateAuthority   DNSRRValidator `yaml:"validate_authority_rrs"`
+       ValidateAdditional  DNSRRValidator `yaml:"validate_additional_rrs"`
+
+       // Catches all undefined fields and must be empty after parsing.
+       XXX map[string]interface{} `yaml:",inline"`
+}
+
+type DNSRRValidator struct {
+       FailIfMatchesRegexp    []string `yaml:"fail_if_matches_regexp"`
+       FailIfNotMatchesRegexp []string `yaml:"fail_if_not_matches_regexp"`
+
+       // Catches all undefined fields and must be empty after parsing.
+       XXX map[string]interface{} `yaml:",inline"`
+}
+
+func checkOverflow(m map[string]interface{}, ctx string) error {
+       if len(m) > 0 {
+               var keys []string
+               for k := range m {
+                       keys = append(keys, k)
+               }
+               return fmt.Errorf("unknown fields in %s: %s", ctx, strings.Join(keys, ", "))
+       }
+       return nil
+}
+
+// UnmarshalYAML implements the yaml.Unmarshaler interface.
+func (s *Config) UnmarshalYAML(unmarshal func(interface{}) error) error {
+       type plain Config
+       if err := unmarshal((*plain)(s)); err != nil {
+               return err
+       }
+       if err := checkOverflow(s.XXX, "config"); err != nil {
+               return err
+       }
+       return nil
+}
+
+// UnmarshalYAML implements the yaml.Unmarshaler interface.
+func (s *Module) UnmarshalYAML(unmarshal func(interface{}) error) error {
+       type plain Module
+       if err := unmarshal((*plain)(s)); err != nil {
+               return err
+       }
+       if err := checkOverflow(s.XXX, "module"); err != nil {
+               return err
+       }
+       return nil
+}
+
+// UnmarshalYAML implements the yaml.Unmarshaler interface.
+func (s *HTTPProbe) UnmarshalYAML(unmarshal func(interface{}) error) error {
+       type plain HTTPProbe
+       if err := unmarshal((*plain)(s)); err != nil {
+               return err
+       }
+       if err := checkOverflow(s.XXX, "http probe"); err != nil {
+               return err
+       }
+       return nil
+}
+
+// UnmarshalYAML implements the yaml.Unmarshaler interface.
+func (s *DNSProbe) UnmarshalYAML(unmarshal func(interface{}) error) error {
+       type plain DNSProbe
+       if err := unmarshal((*plain)(s)); err != nil {
+               return err
+       }
+       if err := checkOverflow(s.XXX, "dns probe"); err != nil {
+               return err
+       }
+       return nil
+}
+
+// UnmarshalYAML implements the yaml.Unmarshaler interface.
+func (s *TCPProbe) UnmarshalYAML(unmarshal func(interface{}) error) error {
+       type plain TCPProbe
+       if err := unmarshal((*plain)(s)); err != nil {
+               return err
+       }
+       if err := checkOverflow(s.XXX, "tcp probe"); err != nil {
+               return err
+       }
+       return nil
+}
+
+// UnmarshalYAML implements the yaml.Unmarshaler interface.
+func (s *DNSRRValidator) UnmarshalYAML(unmarshal func(interface{}) error) error {
+       type plain DNSRRValidator
+       if err := unmarshal((*plain)(s)); err != nil {
+               return err
+       }
+       if err := checkOverflow(s.XXX, "dns rr validator"); err != nil {
+               return err
+       }
+       return nil
+}
+
+// UnmarshalYAML implements the yaml.Unmarshaler interface.
+func (s *ICMPProbe) UnmarshalYAML(unmarshal func(interface{}) error) error {
+       type plain ICMPProbe
+       if err := unmarshal((*plain)(s)); err != nil {
+               return err
+       }
+       if err := checkOverflow(s.XXX, "icmp probe"); err != nil {
+               return err
+       }
+       return nil
+}
+
+// UnmarshalYAML implements the yaml.Unmarshaler interface.
+func (s *QueryResponse) UnmarshalYAML(unmarshal func(interface{}) error) error {
+       type plain QueryResponse
+       if err := unmarshal((*plain)(s)); err != nil {
+               return err
+       }
+       if err := checkOverflow(s.XXX, "query response"); err != nil {
+               return err
+       }
+       return nil
+}
diff --git a/config_test.go b/config_test.go
new file mode 100644 (file)
index 0000000..bf42847
--- /dev/null
@@ -0,0 +1,27 @@
+package main
+
+import "testing"
+
+func TestLoadConfig(t *testing.T) {
+       sc := &SafeConfig{
+               C: &Config{},
+       }
+
+       err := sc.reloadConfig("testdata/blackbox-good.yml")
+       if err != nil {
+               t.Errorf("Error loading config %v: %v", "blackbox.yml", err)
+       }
+}
+
+func TestLoadBadConfig(t *testing.T) {
+       sc := &SafeConfig{
+               C: &Config{},
+       }
+
+       expected := "unknown fields in dns probe: invalid_extra_field"
+
+       err := sc.reloadConfig("testdata/blackbox-bad.yml")
+       if err.Error() != expected {
+               t.Errorf("\nexpected:\n%v\ngot:\n%v", expected, err.Error())
+       }
+}
diff --git a/main.go b/main.go
index b6dc8ff6d2f92006f06f94700977f7c88a3cc505..fef6c159b46f202cd48b508c3b87703384110184 100644 (file)
--- a/main.go
+++ b/main.go
@@ -23,82 +23,14 @@ import (
        "syscall"
        "time"
 
-       "sync"
-
        "gopkg.in/yaml.v2"
 
        "github.com/prometheus/client_golang/prometheus"
        "github.com/prometheus/client_golang/prometheus/promhttp"
-       "github.com/prometheus/common/config"
        "github.com/prometheus/common/log"
        "github.com/prometheus/common/version"
 )
 
-type Config struct {
-       Modules map[string]Module `yaml:"modules"`
-}
-
-type SafeConfig struct {
-       sync.RWMutex
-       C *Config
-}
-
-type Module struct {
-       Prober  string        `yaml:"prober"`
-       Timeout time.Duration `yaml:"timeout"`
-       HTTP    HTTPProbe     `yaml:"http"`
-       TCP     TCPProbe      `yaml:"tcp"`
-       ICMP    ICMPProbe     `yaml:"icmp"`
-       DNS     DNSProbe      `yaml:"dns"`
-}
-
-type HTTPProbe struct {
-       // Defaults to 2xx.
-       ValidStatusCodes       []int                   `yaml:"valid_status_codes"`
-       PreferredIPProtocol    string                  `yaml:"preferred_ip_protocol"`
-       NoFollowRedirects      bool                    `yaml:"no_follow_redirects"`
-       FailIfSSL              bool                    `yaml:"fail_if_ssl"`
-       FailIfNotSSL           bool                    `yaml:"fail_if_not_ssl"`
-       Method                 string                  `yaml:"method"`
-       Headers                map[string]string       `yaml:"headers"`
-       FailIfMatchesRegexp    []string                `yaml:"fail_if_matches_regexp"`
-       FailIfNotMatchesRegexp []string                `yaml:"fail_if_not_matches_regexp"`
-       Body                   string                  `yaml:"body"`
-       HTTPClientConfig       config.HTTPClientConfig `yaml:"http_client_config,inline"`
-}
-
-type QueryResponse struct {
-       Expect string `yaml:"expect"`
-       Send   string `yaml:"send"`
-}
-
-type TCPProbe struct {
-       PreferredIPProtocol string           `yaml:"preferred_ip_protocol"`
-       QueryResponse       []QueryResponse  `yaml:"query_response"`
-       TLS                 bool             `yaml:"tls"`
-       TLSConfig           config.TLSConfig `yaml:"tls_config"`
-}
-
-type ICMPProbe struct {
-       PreferredIPProtocol string `yaml:"preferred_ip_protocol"` // Defaults to "ip6".
-}
-
-type DNSProbe struct {
-       PreferredIPProtocol string         `yaml:"preferred_ip_protocol"`
-       TransportProtocol   string         `yaml:"transport_protocol"`
-       QueryName           string         `yaml:"query_name"`
-       QueryType           string         `yaml:"query_type"`   // Defaults to ANY.
-       ValidRcodes         []string       `yaml:"valid_rcodes"` // Defaults to NOERROR.
-       ValidateAnswer      DNSRRValidator `yaml:"validate_answer_rrs"`
-       ValidateAuthority   DNSRRValidator `yaml:"validate_authority_rrs"`
-       ValidateAdditional  DNSRRValidator `yaml:"validate_additional_rrs"`
-}
-
-type DNSRRValidator struct {
-       FailIfMatchesRegexp    []string `yaml:"fail_if_matches_regexp"`
-       FailIfNotMatchesRegexp []string `yaml:"fail_if_not_matches_regexp"`
-}
-
 var Probers = map[string]func(string, Module, *prometheus.Registry) bool{
        "http": probeHTTP,
        "tcp":  probeTCP,
diff --git a/testdata/blackbox-bad.yml b/testdata/blackbox-bad.yml
new file mode 100644 (file)
index 0000000..933ec54
--- /dev/null
@@ -0,0 +1,50 @@
+modules:
+  http_2xx:
+    prober: http
+    timeout: 5s
+    http:
+  http_post_2xx:
+    prober: http
+    timeout: 5s
+    http:
+      method: POST
+  tcp_connect:
+    prober: tcp
+    timeout: 5s
+  pop3s_banner:
+    prober: tcp
+    tcp:
+      query_response:
+      - expect: "^+OK"
+      tls: true
+      tls_config:
+        insecure_skip_verify: false
+  ssh_banner:
+    prober: tcp
+    timeout: 5s
+    tcp:
+      query_response:
+      - expect: "^SSH-2.0-"
+  irc_banner:
+    prober: tcp
+    timeout: 5s
+    tcp:
+      query_response:
+      - send: "NICK prober"
+      - send: "USER prober prober prober :prober"
+      - expect: "PING :([^ ]+)"
+        send: "PONG ${1}"
+      - expect: "^:[^ ]+ 001"
+  icmp_test:
+    prober: icmp
+    timeout: 5s
+    icmp:
+      preferred_ip_protocol: ip4
+  dns_test:
+    prober: dns
+    timeout: 5s
+    dns:
+      preferred_ip_protocol: ip6
+      validate_answer_rrs:
+        fail_if_matches_regexp: [test]
+      invalid_extra_field: value
diff --git a/testdata/blackbox-good.yml b/testdata/blackbox-good.yml
new file mode 100644 (file)
index 0000000..c5c8076
--- /dev/null
@@ -0,0 +1,49 @@
+modules:
+  http_2xx:
+    prober: http
+    timeout: 5s
+    http:
+  http_post_2xx:
+    prober: http
+    timeout: 5s
+    http:
+      method: POST
+  tcp_connect:
+    prober: tcp
+    timeout: 5s
+  pop3s_banner:
+    prober: tcp
+    tcp:
+      query_response:
+      - expect: "^+OK"
+      tls: true
+      tls_config:
+        insecure_skip_verify: false
+  ssh_banner:
+    prober: tcp
+    timeout: 5s
+    tcp:
+      query_response:
+      - expect: "^SSH-2.0-"
+  irc_banner:
+    prober: tcp
+    timeout: 5s
+    tcp:
+      query_response:
+      - send: "NICK prober"
+      - send: "USER prober prober prober :prober"
+      - expect: "PING :([^ ]+)"
+        send: "PONG ${1}"
+      - expect: "^:[^ ]+ 001"
+  icmp_test:
+    prober: icmp
+    timeout: 5s
+    icmp:
+      preferred_ip_protocol: ip4
+  dns_test:
+    prober: dns
+    timeout: 5s
+    dns:
+      preferred_ip_protocol: ip6
+      validate_answer_rrs:
+        fail_if_matches_regexp: [test]