influxdb/services/httpd/config.go

202 lines
6.5 KiB
Go

package httpd
import (
"bytes"
"crypto/tls"
"errors"
"fmt"
"regexp"
"strconv"
"time"
"github.com/influxdata/influxdb/monitor/diagnostics"
"github.com/influxdata/influxdb/toml"
)
const (
// DefaultBindAddress is the default address to bind to.
DefaultBindAddress = ":8086"
// DefaultRealm is the default realm sent back when issuing a basic auth challenge.
DefaultRealm = "InfluxDB"
// DefaultBindSocket is the default unix socket to bind to.
DefaultBindSocket = "/var/run/influxdb.sock"
// DefaultMaxBodySize is the default maximum size of a client request body, in bytes. Specify 0 for no limit.
DefaultMaxBodySize = 25e6
// DefaultEnqueuedWriteTimeout is the maximum time a write request can wait to be processed.
DefaultEnqueuedWriteTimeout = 30 * time.Second
)
// Config represents a configuration for a HTTP service.
type Config struct {
Enabled bool `toml:"enabled"`
BindAddress string `toml:"bind-address"`
AuthEnabled bool `toml:"auth-enabled"`
LogEnabled bool `toml:"log-enabled"`
SuppressWriteLog bool `toml:"suppress-write-log"`
WriteTracing bool `toml:"write-tracing"`
FluxEnabled bool `toml:"flux-enabled"`
FluxLogEnabled bool `toml:"flux-log-enabled"`
PprofEnabled bool `toml:"pprof-enabled"`
PprofAuthEnabled bool `toml:"pprof-auth-enabled"`
DebugPprofEnabled bool `toml:"debug-pprof-enabled"`
PingAuthEnabled bool `toml:"ping-auth-enabled"`
PromReadAuthEnabled bool `toml:"prom-read-auth-enabled"`
HTTPHeaders map[string]string `toml:"headers"`
HTTPSEnabled bool `toml:"https-enabled"`
HTTPSCertificate string `toml:"https-certificate"`
HTTPSPrivateKey string `toml:"https-private-key"`
MaxRowLimit int `toml:"max-row-limit"`
MaxConnectionLimit int `toml:"max-connection-limit"`
SharedSecret string `toml:"shared-secret"`
Realm string `toml:"realm"`
UnixSocketEnabled bool `toml:"unix-socket-enabled"`
UnixSocketGroup *toml.Group `toml:"unix-socket-group"`
UnixSocketPermissions toml.FileMode `toml:"unix-socket-permissions"`
BindSocket string `toml:"bind-socket"`
MaxBodySize int `toml:"max-body-size"`
AccessLogPath string `toml:"access-log-path"`
AccessLogStatusFilters []StatusFilter `toml:"access-log-status-filters"`
MaxConcurrentWriteLimit int `toml:"max-concurrent-write-limit"`
MaxEnqueuedWriteLimit int `toml:"max-enqueued-write-limit"`
EnqueuedWriteTimeout time.Duration `toml:"enqueued-write-timeout"`
TLS *tls.Config `toml:"-"`
}
// NewConfig returns a new Config with default settings.
func NewConfig() Config {
return Config{
Enabled: true,
FluxEnabled: false,
FluxLogEnabled: false,
BindAddress: DefaultBindAddress,
LogEnabled: true,
PprofEnabled: true,
PprofAuthEnabled: false,
DebugPprofEnabled: false,
PingAuthEnabled: false,
PromReadAuthEnabled: false,
HTTPSEnabled: false,
HTTPSCertificate: "/etc/ssl/influxdb.pem",
MaxRowLimit: 0,
Realm: DefaultRealm,
UnixSocketEnabled: false,
UnixSocketPermissions: 0777,
BindSocket: DefaultBindSocket,
MaxBodySize: DefaultMaxBodySize,
EnqueuedWriteTimeout: DefaultEnqueuedWriteTimeout,
}
}
// Diagnostics returns a diagnostics representation of a subset of the Config.
func (c Config) Diagnostics() (*diagnostics.Diagnostics, error) {
if !c.Enabled {
return diagnostics.RowFromMap(map[string]interface{}{
"enabled": false,
}), nil
}
return diagnostics.RowFromMap(map[string]interface{}{
"enabled": true,
"bind-address": c.BindAddress,
"https-enabled": c.HTTPSEnabled,
"max-row-limit": c.MaxRowLimit,
"max-connection-limit": c.MaxConnectionLimit,
"access-log-path": c.AccessLogPath,
}), nil
}
// StatusFilter will check if an http status code matches a certain pattern.
type StatusFilter struct {
base int
divisor int
}
// reStatusFilter ensures that the format is digits optionally followed by X values.
var reStatusFilter = regexp.MustCompile(`^([1-5]\d*)([xX]*)$`)
// ParseStatusFilter will create a new status filter from the string.
func ParseStatusFilter(s string) (StatusFilter, error) {
m := reStatusFilter.FindStringSubmatch(s)
if m == nil {
return StatusFilter{}, fmt.Errorf("status filter must be a digit that starts with 1-5 optionally followed by X characters")
} else if len(s) != 3 {
return StatusFilter{}, fmt.Errorf("status filter must be exactly 3 characters long")
}
// Compute the divisor and the expected value. If we have one X, we divide by 10 so we are only comparing
// the first two numbers. If we have two Xs, we divide by 100 so we only compare the first number. We
// then check if the result is equal to the remaining number.
base, err := strconv.Atoi(m[1])
if err != nil {
return StatusFilter{}, err
}
divisor := 1
switch len(m[2]) {
case 1:
divisor = 10
case 2:
divisor = 100
}
return StatusFilter{
base: base,
divisor: divisor,
}, nil
}
// Match will check if the status code matches this filter.
func (sf StatusFilter) Match(statusCode int) bool {
if sf.divisor == 0 {
return false
}
return statusCode/sf.divisor == sf.base
}
// UnmarshalText parses a TOML value into a duration value.
func (sf *StatusFilter) UnmarshalText(text []byte) error {
f, err := ParseStatusFilter(string(text))
if err != nil {
return err
}
*sf = f
return nil
}
// MarshalText converts a duration to a string for decoding toml
func (sf StatusFilter) MarshalText() (text []byte, err error) {
var buf bytes.Buffer
if sf.base != 0 {
buf.WriteString(strconv.Itoa(sf.base))
}
switch sf.divisor {
case 1:
case 10:
buf.WriteString("X")
case 100:
buf.WriteString("XX")
default:
return nil, errors.New("invalid status filter")
}
return buf.Bytes(), nil
}
type StatusFilters []StatusFilter
func (filters StatusFilters) Match(statusCode int) bool {
if len(filters) == 0 {
return true
}
for _, sf := range filters {
if sf.Match(statusCode) {
return true
}
}
return false
}