pkg/config/config.go (150 lines of code) (raw):
// Licensed to Elasticsearch B.V. under one or more contributor
// license agreements. See the NOTICE.txt file distributed with
// this work for additional information regarding copyright
// ownership. Elasticsearch B.V. licenses this file to you 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 config
import (
"fmt"
"os"
"regexp"
"text/template"
"github.com/itchyny/gojq"
"gopkg.in/yaml.v3"
)
const configPath = "config/config.yml"
// ObjectSelector defines a reference to a Kubernetes object.
type ObjectSelector struct {
// Name of the Kubernetes object.
Name string `json:"name"`
// Namespace of the Kubernetes object. If empty, defaults to the current namespace.
Namespace string `json:"namespace,omitempty"`
}
// IsDefined checks if the object selector is not nil and has a name.
// Namespace is not mandatory as it may be inherited by the parent object.
func (o *ObjectSelector) IsDefined() bool {
return o != nil && (o.Name != "" || o.Namespace != "")
}
func (o *HTTPClientConfig) IsDefined() bool {
return o != nil
}
type MetricServer struct {
Name string `yaml:"name"`
ServerType string `yaml:"serverType"`
MetricTypes *MetricTypes `yaml:"metricTypes"`
ClientConfig HTTPClientConfig `yaml:"clientConfig,omitempty"`
MetricSets MetricSets `yaml:"metricSets,omitempty"` // only valid if type is elasticsearch
Rename *Matches `yaml:"rename,omitempty"`
Priority int `yaml:"-"`
}
type Matches struct {
Matches string `yaml:"matches"`
As string `yaml:"as"`
}
type ReadinessProbe struct {
FailureThreshold int `yaml:"failureThreshold"`
}
type MetricServers []MetricServer
type Config struct {
ReadinessProbe ReadinessProbe `yaml:"failureThreshold"`
MetricServers []MetricServer `yaml:"metricServers"`
}
type MetricSets []MetricSet
type MetricSet struct {
// Comma separated set of Indices
Indices []string `yaml:"indices"`
// Fields exposed within the Indices
Fields FieldsSet `yaml:"fields"`
}
type FieldsSet []Fields
type Fields struct {
// Filter which fields are exposed, for example { "^prometheus\.metrics\." }
Patterns []string `yaml:"patterns"`
compiledPatterns []regexp.Regexp `yaml:"-"`
// Name is the name of a static field
Name string `yaml:"name"`
// Search is the search associated to the static field
Search Search `yaml:"search"`
// Help to determine which fields are labels, for example ^prometheus\.labels\.(.*)
Labels []string `yaml:"labels"`
// Resource associated with the metrics, default is {group: "", resource: "pod"}
Resources GroupResource
}
type Search struct {
// MetricPath is the path to be used to get the result from the search
MetricPath string `yaml:"metricPath"`
// TimestampPath is the path to be used to get the result timestamp
TimestampPath string `yaml:"timestampPath"`
// Body is the body to be used to search the metric.
Body string `json:"body"`
// Template is the template version of the body
Template *template.Template `yaml:"-"`
// MetricResultQuery is the template version of metricPath
MetricResultQuery *gojq.Query `yaml:"-"`
// TimestampResultQuery is the template version of metricPath
TimestampResultQuery *gojq.Query `yaml:"-"`
}
var defaultFieldSet = Fields{
Patterns: []string{"^.*$"},
}
func (f FieldsSet) FindMetadata(fieldName string) *Fields {
for _, fieldSet := range f {
for _, pattern := range fieldSet.compiledPatterns {
if pattern.MatchString(fieldName) {
return &fieldSet
}
}
}
return nil
}
type GroupResource struct {
Group string `yaml:"group"`
Resource string `yaml:"resource"`
}
func Parse() (*Config, error) {
config, err := os.ReadFile(configPath)
if err != nil {
return nil, err
}
return From(config)
}
func From(source []byte) (*Config, error) {
config := &Config{}
// Read file as yaml
err := yaml.Unmarshal(source, config)
if err != nil {
return nil, err
}
// Set priority given the position in the array
for i := range config.MetricServers {
config.MetricServers[i].Priority = i
}
// Ensure that configuration is valid and compile the patterns.
if err := validate(config); err != nil {
return nil, err
}
return config, nil
}
func validate(config *Config) error {
for i := range config.MetricServers {
server := config.MetricServers[i]
if server.Rename != nil {
if len(server.Rename.Matches) == 0 || len(server.Rename.As) == 0 {
return fmt.Errorf("%s: rename directive must contain both \"matches\" and \"as\" fields", server.Name)
}
}
if server.ServerType == "" {
return fmt.Errorf("%s: server type is not set", server.Name)
}
switch server.ServerType {
case "custom":
if !server.ClientConfig.IsDefined() {
return fmt.Errorf("%s: HTTP client configuration is not defined in upstream custom metric server", server.Name)
}
if len(server.MetricSets) > 0 {
return fmt.Errorf("%s: metricSets is not allowed in upstream custom metric server", server.Name)
}
case "elasticsearch":
if len(server.MetricSets) == 0 {
return fmt.Errorf("%s: no metricSets defined", server.Name)
}
if !server.ClientConfig.IsDefined() {
return fmt.Errorf("%s: Elasticsearch requires clientConfig to be set", server.Name)
}
// Compile the regular expressions
for i := range server.MetricSets {
if len(server.MetricSets[i].Fields) == 0 {
// User did not provide a field pattern, we assume the user wants all numeric fields in this index to be available.
server.MetricSets[i].Fields = append(server.MetricSets[i].Fields, defaultFieldSet)
}
metricSet := server.MetricSets[i]
for j := range metricSet.Fields {
field := metricSet.Fields[j]
metricSet.Fields[j].compiledPatterns = make([]regexp.Regexp, len(field.Patterns))
for k, pattern := range field.Patterns {
compiledPattern, err := regexp.Compile(pattern)
if err != nil {
return fmt.Errorf("%s: error while compiling regular expression %s: %v", server.Name, pattern, err)
}
metricSet.Fields[j].compiledPatterns[k] = *compiledPattern
}
}
}
default:
return fmt.Errorf("%s: unknown metric server type: %s", server.Name, server.ServerType)
}
}
return nil
}