package relabel
import (
"fmt"
"github.com/grafana/regexp"
"github.com/prometheus/common/model"
"github.com/prometheus/prometheus/model/relabel"
)
type Action string
const (
Replace Action = "replace"
Keep Action = "keep"
Drop Action = "drop"
HashMod Action = "hashmod"
LabelMap Action = "labelmap"
LabelDrop Action = "labeldrop"
LabelKeep Action = "labelkeep"
Lowercase Action = "lowercase"
Uppercase Action = "uppercase"
KeepEqual Action = "keepequal"
DropEqual Action = "dropequal"
)
var actions = map[Action]struct{}{
Replace: {},
Keep: {},
Drop: {},
HashMod: {},
LabelMap: {},
LabelDrop: {},
LabelKeep: {},
Lowercase: {},
Uppercase: {},
KeepEqual: {},
DropEqual: {},
}
func (a Action) String() string {
if _, exists := actions[a]; exists {
return string(a)
}
return "Action:" + string(a)
}
func (a Action) MarshalText() (text []byte, err error) {
return []byte(a.String()), nil
}
func (a *Action) UnmarshalText(text []byte) error {
if _, exists := actions[Action(text)]; exists {
*a = Action(text)
return nil
}
return fmt.Errorf("unrecognized action type %q", string(text))
}
type Regexp struct {
*regexp.Regexp
}
func newRegexp(s string) (Regexp, error) {
re, err := regexp.Compile("^(?:" + s + ")$")
return Regexp{re}, err
}
func mustNewRegexp(s string) Regexp {
re, err := newRegexp(s)
if err != nil {
panic(err)
}
return re
}
func (re Regexp) MarshalText() (text []byte, err error) {
if re.String() != "" {
return []byte(re.String()), nil
}
return nil, nil
}
func (re *Regexp) UnmarshalText(text []byte) error {
regex, err := regexp.Compile("^(?:" + string(text) + ")$")
if err != nil {
return err
}
*re = Regexp{regex}
return nil
}
type Config struct {
SourceLabels []string `river:"source_labels,attr,optional"`
Separator string `river:"separator,attr,optional"`
Regex Regexp `river:"regex,attr,optional"`
Modulus uint64 `river:"modulus,attr,optional"`
TargetLabel string `river:"target_label,attr,optional"`
Replacement string `river:"replacement,attr,optional"`
Action Action `river:"action,attr,optional"`
}
var DefaultRelabelConfig = Config{
Action: Replace,
Separator: ";",
Regex: mustNewRegexp("(.*)"),
Replacement: "$1",
}
var relabelTarget = regexp.MustCompile(`^(?:(?:[a-zA-Z_]|\$(?:\{\w+\}|\w+))+\w*)+$`)
func (rc *Config) UnmarshalRiver(f func(interface{}) error) error {
*rc = DefaultRelabelConfig
type relabelConfig Config
if err := f((*relabelConfig)(rc)); err != nil {
return err
}
if rc.Action == "" {
return fmt.Errorf("relabel action cannot be empty")
}
if rc.Modulus == 0 && rc.Action == HashMod {
return fmt.Errorf("relabel configuration for hashmod requires non-zero modulus")
}
if (rc.Action == Replace || rc.Action == KeepEqual || rc.Action == DropEqual || rc.Action == HashMod || rc.Action == Lowercase || rc.Action == Uppercase) && rc.TargetLabel == "" {
return fmt.Errorf("relabel configuration for %s action requires 'target_label' value", rc.Action)
}
if (rc.Action == KeepEqual || rc.Action == DropEqual) && rc.SourceLabels == nil {
return fmt.Errorf("relabel configuration for %s action requires 'source_labels' value", rc.Action)
}
if (rc.Action == Replace || rc.Action == Lowercase || rc.Action == Uppercase) && !relabelTarget.MatchString(rc.TargetLabel) {
return fmt.Errorf("%q is invalid 'target_label' for %s action", rc.TargetLabel, rc.Action)
}
if (rc.Action == Lowercase || rc.Action == Uppercase) && rc.Replacement != DefaultRelabelConfig.Replacement {
return fmt.Errorf("'replacement' can not be set for %s action", rc.Action)
}
if rc.Action == LabelMap && !relabelTarget.MatchString(rc.Replacement) {
return fmt.Errorf("%q is invalid 'replacement' for %s action", rc.Replacement, rc.Action)
}
if rc.Action == HashMod && !model.LabelName(rc.TargetLabel).IsValid() {
return fmt.Errorf("%q is invalid 'target_label' for %s action", rc.TargetLabel, rc.Action)
}
if rc.Action == LabelDrop || rc.Action == LabelKeep {
if rc.SourceLabels != nil ||
rc.TargetLabel != DefaultRelabelConfig.TargetLabel ||
rc.Modulus != DefaultRelabelConfig.Modulus ||
rc.Separator != DefaultRelabelConfig.Separator ||
rc.Replacement != DefaultRelabelConfig.Replacement {
return fmt.Errorf("%s action requires only 'regex', and no other fields", rc.Action)
}
}
if rc.Action == KeepEqual || rc.Action == DropEqual {
if rc.Regex != DefaultRelabelConfig.Regex ||
rc.Modulus != DefaultRelabelConfig.Modulus ||
rc.Separator != DefaultRelabelConfig.Separator ||
rc.Replacement != DefaultRelabelConfig.Replacement {
return fmt.Errorf("%s action requires only 'source_labels' and 'target_label', and no other fields", rc.Action)
}
}
return nil
}
func ComponentToPromRelabelConfigs(rcs []*Config) []*relabel.Config {
res := make([]*relabel.Config, len(rcs))
for i, rc := range rcs {
sourceLabels := make([]model.LabelName, len(rc.SourceLabels))
for i, sl := range rc.SourceLabels {
sourceLabels[i] = model.LabelName(sl)
}
res[i] = &relabel.Config{
SourceLabels: sourceLabels,
Separator: rc.Separator,
Modulus: rc.Modulus,
TargetLabel: rc.TargetLabel,
Replacement: rc.Replacement,
Action: relabel.Action(rc.Action),
Regex: relabel.Regexp{Regexp: rc.Regex.Regexp},
}
}
return res
}
type Rules []*Config
func (r Rules) RiverCapsule() {}