Skip to content

Commit

Permalink
prepare for new config file version
Browse files Browse the repository at this point in the history
  • Loading branch information
fstab committed Dec 29, 2016
1 parent d88153f commit c985268
Show file tree
Hide file tree
Showing 13 changed files with 491 additions and 64 deletions.
78 changes: 78 additions & 0 deletions config/config.go
@@ -0,0 +1,78 @@
// Copyright 2016 The grok_exporter 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 config

import (
"fmt"
"github.com/fstab/grok_exporter/config/v1"
"github.com/fstab/grok_exporter/config/v2"
"io/ioutil"
"regexp"
"strconv"
"strings"
)

// Example config: See ./example/config.yml

func LoadConfigFile(filename string) (*v2.Config, string, error) {
content, err := ioutil.ReadFile(filename)
if err != nil {
return nil, "", fmt.Errorf("Failed to load %v: %v", filename, err.Error())
}
cfg, warn, err := LoadConfigString(content)
if err != nil {
return nil, "", fmt.Errorf("Failed to load %v: %v", filename, err.Error())
}
return cfg, warn, nil
}

func LoadConfigString(content []byte) (*v2.Config, string, error) {
version, warn, err := findVersion(content)
if err != nil {
return nil, "", err
}
cfg, err := unmarshal(content, version)
return cfg, warn, err
}

func findVersion(content []byte) (int, string, error) {
versionExpr := regexp.MustCompile(`general:\s*config_version:[\t\f ]*(\S+)`)
versionInfo := versionExpr.FindStringSubmatch(string(content))
if len(versionInfo) == 2 {
version, err := strconv.Atoi(strings.TrimSpace(versionInfo[1]))
if err != nil {
return 0, "", fmt.Errorf("invalid 'general' configuration: '%v' is not a valid 'config_version'.", versionInfo[1])
}
return version, "", nil
} else { // no version found
warn := "No config_version found in config file. " +
"Assuming it is a config file for grok_exporter <= 0.1.4, using \"config_version: 1\". " +
"grok_exporter still supports \"config_version 1\", " +
"but you should consider updating your configuration. " +
"Use the '-showconfig' command line option to view your configuration in the current format."
return 1, warn, nil
}
}

func unmarshal(content []byte, version int) (*v2.Config, error) {
switch version {
case 1:
return v1.Unmarshal(content)
case 2:
return v2.Unmarshal(content)
default:
return nil, fmt.Errorf("config_version %v is not supported.", version)
}
}
71 changes: 71 additions & 0 deletions config/config_test.go
@@ -0,0 +1,71 @@
// Copyright 2016 The grok_exporter 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 config

import (
"strings"
"testing"
)

const exampleConfig = `
general:
config_version: 2
input:
type: file
path: x/x/x
readall: true
grok:
patterns_dir: b/c
metrics:
- type: counter
name: test_count_total
help: Dummy help message.
match: Some text here, then a %{DATE}.
labels:
- grok_field_name: a
prometheus_label: b
- grok_field_name: c
prometheus_label: d
server:
protocol: https
port: 1111
`

func TestVersionDetection(t *testing.T) {
expectVersion(t, exampleConfig, 2, false)
expectVersion(t, strings.Replace(exampleConfig, "config_version: 2", "config_version: 1", 1), 1, false)
expectVersion(t, strings.Replace(exampleConfig, "config_version: 2", "config_version:", 1), 1, true)
expectVersion(t, strings.Replace(exampleConfig, "config_version: 2", "", 1), 1, true)
_, _, err := findVersion([]byte(strings.Replace(exampleConfig, "config_version: 2", "config_version: a", 1)))
if err == nil {
t.Fatalf("Expected error, because 'a' is not a number.")
}
}

func expectVersion(t *testing.T, config string, expectedVersion int, warningExpected bool) {
version, warn, err := findVersion([]byte(config))
if err != nil {
t.Fatalf("unexpected error while getting version info: %v", err.Error())
}
if warningExpected && len(warn) == 0 {
t.Fatalf("didn't get warning for unversioned config file")
}
if !warningExpected && len(warn) > 0 {
t.Fatalf("unexpected warning: %v", warn)
}
if version != expectedVersion {
t.Fatalf("expected version %v, but found %v", expectedVersion, version)
}
}
98 changes: 98 additions & 0 deletions config/v1/configV1.go
@@ -0,0 +1,98 @@
// Copyright 2016 The grok_exporter 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 v1

import (
"fmt"
"github.com/fstab/grok_exporter/config/v2"
"gopkg.in/yaml.v2"
)

func Unmarshal(config []byte) (*v2.Config, error) {
v1cfg := &Config{}
err := yaml.Unmarshal(config, v1cfg)
if err != nil {
return nil, fmt.Errorf("invalid configuration: %v", err.Error())
}
v2cfg := &v2.Config{
Input: v1cfg.Input,
Grok: v1cfg.Grok,
Metrics: convertMetrics(*v1cfg.Metrics),
Server: v1cfg.Server,
}
v2cfg.AddDefaults()
err = v2cfg.Validate()
if err != nil {
return nil, err
}
return v2cfg, nil
}

func convertMetrics(v1metrics []*MetricConfig) *v2.MetricsConfig {
if len(v1metrics) == 0 {
return nil
}
v2metrics := make([]*v2.MetricConfig, len(v1metrics))
for i, v1metric := range v1metrics {
v2metrics[i] = &v2.MetricConfig{
Type: v1metric.Type,
Name: v1metric.Name,
Help: v1metric.Help,
Match: v1metric.Match,
Value: v1metric.Value,
Cumulative: v1metric.Cumulative,
Buckets: v1metric.Buckets,
Quantiles: v1metric.Quantiles,
}
if len(v1metric.Labels) > 0 {
v2metrics[i].Labels = make([]v2.Label, len(v1metric.Labels))
for j, v1label := range v1metric.Labels {
v2metrics[i].Labels[j] = v2.Label{
GrokFieldName: v1label.GrokFieldName,
PrometheusLabel: v1label.PrometheusLabel,
}
}
}
}
result := v2.MetricsConfig(v2metrics)
return &result
}

type Config struct {
// For sections that don't differ between v1 and v2, we reference v2 directly here.
Input *v2.InputConfig `yaml:",omitempty"`
Grok *v2.GrokConfig `yaml:",omitempty"`
Metrics *MetricsConfig `yaml:",omitempty"`
Server *v2.ServerConfig `yaml:",omitempty"`
}

type MetricsConfig []*MetricConfig

type MetricConfig struct {
Type string `yaml:",omitempty"`
Name string `yaml:",omitempty"`
Help string `yaml:",omitempty"`
Match string `yaml:",omitempty"`
Value string `yaml:",omitempty"`
Cumulative bool `yaml:",omitempty"`
Buckets []float64 `yaml:",flow,omitempty"`
Quantiles map[float64]float64 `yaml:",flow,omitempty"`
Labels []Label `yaml:",omitempty"`
}

type Label struct {
GrokFieldName string `yaml:"grok_field_name,omitempty"`
PrometheusLabel string `yaml:"prometheus_label,omitempty"`
}

0 comments on commit c985268

Please sign in to comment.