// NewTelemetry configures a new prometheus Telemetry server func NewTelemetry(raw interface{}) (*Telemetry, error) { t := &Telemetry{ Port: 9090, ServiceName: "containerpilot", URL: "/metrics", TTL: 15, Poll: 5, lock: sync.RWMutex{}, } if err := utils.DecodeRaw(raw, t); err != nil { return nil, fmt.Errorf("Telemetry configuration error: %v", err) } ipAddress, err := utils.IPFromInterfaces(t.Interfaces) if err != nil { return nil, err } ip := net.ParseIP(ipAddress) t.addr = net.TCPAddr{IP: ip, Port: t.Port} t.mux = http.NewServeMux() t.mux.Handle(t.URL, prometheus.Handler()) // note that we don't return an error if there are no sensors // because the prometheus handler will still pick up metrics // internal to ContainerPilot (i.e. the golang runtime) if t.SensorConfigs != nil { sensors, err := NewSensors(t.SensorConfigs) if err != nil { return nil, err } t.Sensors = sensors } return t, nil }
// NewBackends creates a new backend from a raw config structure func NewBackends(raw []interface{}, disc discovery.ServiceBackend) ([]*Backend, error) { if raw == nil { return []*Backend{}, nil } var backends []*Backend if err := utils.DecodeRaw(raw, &backends); err != nil { return nil, fmt.Errorf("Backend configuration error: %v", err) } for _, b := range backends { if err := utils.ValidateServiceName(b.Name); err != nil { return nil, err } if b.OnChangeExec == nil { return nil, fmt.Errorf("`onChange` is required in backend %s", b.Name) } cmd, err := commands.NewCommand(b.OnChangeExec, b.Timeout) if err != nil { return nil, fmt.Errorf("Could not parse `onChange` in backend %s: %s", b.Name, err) } cmd.Name = fmt.Sprintf("%s.health", b.Name) b.onChangeCmd = cmd if b.Poll < 1 { return nil, fmt.Errorf("`poll` must be > 0 in backend %s", b.Name) } b.onChangeCmd = cmd b.discoveryService = disc } return backends, nil }
// NewSensors creates new sensors from a raw config func NewSensors(raw []interface{}) ([]*Sensor, error) { var sensors []*Sensor if err := utils.DecodeRaw(raw, &sensors); err != nil { return nil, fmt.Errorf("Sensor configuration error: %v", err) } for _, s := range sensors { check, err := commands.NewCommand(s.CheckExec, s.Timeout) if err != nil { return nil, fmt.Errorf("could not parse check in sensor %s: %s", s.Name, err) } check.Name = fmt.Sprintf("%s.sensor", s.Name) s.checkCmd = check // the prometheus client lib's API here is baffling... they don't expose // an interface or embed their Opts type in each of the Opts "subtypes", // so we can't share the initialization. switch { case s.Type == "counter": s.collector = prometheus.NewCounter(prometheus.CounterOpts{ Namespace: s.Namespace, Subsystem: s.Subsystem, Name: s.Name, Help: s.Help, }) case s.Type == "gauge": s.collector = prometheus.NewGauge(prometheus.GaugeOpts{ Namespace: s.Namespace, Subsystem: s.Subsystem, Name: s.Name, Help: s.Help, }) case s.Type == "histogram": s.collector = prometheus.NewHistogram(prometheus.HistogramOpts{ Namespace: s.Namespace, Subsystem: s.Subsystem, Name: s.Name, Help: s.Help, }) case s.Type == "summary": s.collector = prometheus.NewSummary(prometheus.SummaryOpts{ Namespace: s.Namespace, Subsystem: s.Subsystem, Name: s.Name, Help: s.Help, }) default: return nil, fmt.Errorf("invalid sensor type: %s", s.Type) } // we're going to unregister before every attempt to register // so that we can reload config prometheus.Unregister(s.collector) if err := prometheus.Register(s.collector); err != nil { return nil, err } } return sensors, nil }
// We can't use mapstructure to decode our config map since we want the values // to also be raw interface{} types. mapstructure can only decode // into concrete structs and primitives func decodeConfig(configMap map[string]interface{}, result *rawConfig) error { var logConfig LogConfig var stopTimeout int if err := utils.DecodeRaw(configMap["logging"], &logConfig); err != nil { return err } if err := utils.DecodeRaw(configMap["stopTimeout"], &stopTimeout); err != nil { return err } result.stopTimeout = stopTimeout result.logConfig = &logConfig result.onStart = configMap["onStart"] result.preStart = configMap["preStart"] result.preStop = configMap["preStop"] result.postStop = configMap["postStop"] result.servicesConfig = decodeArray(configMap["services"]) result.backendsConfig = decodeArray(configMap["backends"]) result.tasksConfig = decodeArray(configMap["tasks"]) result.coprocessesConfig = decodeArray(configMap["coprocesses"]) result.telemetryConfig = configMap["telemetry"] delete(configMap, "logging") delete(configMap, "onStart") delete(configMap, "preStart") delete(configMap, "preStop") delete(configMap, "postStop") delete(configMap, "stopTimeout") delete(configMap, "services") delete(configMap, "backends") delete(configMap, "tasks") delete(configMap, "coprocesses") delete(configMap, "telemetry") var unused []string for key := range configMap { unused = append(unused, key) } if len(unused) > 0 { return fmt.Errorf("Unknown config keys: %v", unused) } return nil }
func configFromMap(raw map[string]interface{}) (*consul.Config, error) { config := &struct { Address string `mapstructure:"address"` Scheme string `mapstructure:"scheme"` Token string `mapstructure:"token"` }{} if err := utils.DecodeRaw(raw, config); err != nil { return nil, err } return &consul.Config{ Address: config.Address, Scheme: config.Scheme, Token: config.Token, }, nil }
// NewServices new services from a raw config func NewServices(raw []interface{}, disc discovery.ServiceBackend) ([]*Service, error) { if raw == nil { return []*Service{}, nil } var services []*Service if err := utils.DecodeRaw(raw, &services); err != nil { return nil, fmt.Errorf("Service configuration error: %v", err) } for _, s := range services { if err := parseService(s, disc); err != nil { return nil, err } } return services, nil }
// NewCoprocesses parses json config into an array of Coprocesses func NewCoprocesses(raw []interface{}) ([]*Coprocess, error) { var coprocesses []*Coprocess if raw == nil { return coprocesses, nil } var configs []*Coprocess if err := utils.DecodeRaw(raw, &configs); err != nil { return nil, fmt.Errorf("Coprocess configuration error: %v", err) } for _, t := range configs { if err := parseCoprocess(t); err != nil { return nil, err } coprocesses = append(coprocesses, t) } return coprocesses, nil }
// NewTasks parses json config into an array of Tasks func NewTasks(raw []interface{}) ([]*Task, error) { var tasks []*Task if raw == nil { return tasks, nil } var configs []*Task if err := utils.DecodeRaw(raw, &configs); err != nil { return nil, fmt.Errorf("Task configuration error: %v", err) } for _, t := range configs { if err := parseTask(t); err != nil { return nil, err } tasks = append(tasks, t) } return tasks, nil }
// NewEtcdConfig creates a new service discovery backend for etcd func NewEtcdConfig(raw interface{}) (*Etcd, error) { etcd := &Etcd{ Prefix: "/containerpilot", } var config etcdRawConfig etcdConfig := client.Config{} if err := utils.DecodeRaw(raw, &config); err != nil { return nil, err } etcdConfig.Endpoints = parseEndpoints(config.Endpoints) if config.Prefix != "" { etcd.Prefix = config.Prefix } etcdClient, err := client.New(etcdConfig) if err != nil { return nil, err } etcd.Client = etcdClient etcd.API = client.NewKeysAPI(etcdClient) return etcd, nil }