2016-12-28 10:04:59 -08:00
// Copyright 2015 The Prometheus 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 relabel
import (
"crypto/md5"
2023-02-28 09:36:58 -08:00
"encoding/binary"
2024-11-24 10:46:24 -08:00
"encoding/json"
2024-11-03 04:15:51 -08:00
"errors"
2016-12-28 10:04:59 -08:00
"fmt"
2024-02-28 17:33:17 -08:00
"strconv"
2016-12-28 10:04:59 -08:00
"strings"
2022-02-12 15:58:27 -08:00
"github.com/grafana/regexp"
2016-12-28 10:04:59 -08:00
"github.com/prometheus/common/model"
2021-11-08 06:23:17 -08:00
"github.com/prometheus/prometheus/model/labels"
2016-12-28 10:04:59 -08:00
)
2018-12-18 03:26:36 -08:00
var (
2025-01-27 01:49:50 -08:00
// relabelTargetLegacy allows targeting labels with legacy Prometheus character set, plus ${<var>} variables for dynamic characters from source the metrics.
2025-01-23 00:53:59 -08:00
relabelTargetLegacy = regexp . MustCompile ( ` ^(?:(?:[a-zA-Z_]|\$(?:\ { \w+\}|\w+))+\w*)+$ ` )
2018-12-18 03:26:36 -08:00
DefaultRelabelConfig = Config {
Action : Replace ,
Separator : ";" ,
Regex : MustNewRegexp ( "(.*)" ) ,
Replacement : "$1" ,
}
)
// Action is the action to be performed on relabeling.
type Action string
const (
// Replace performs a regex replacement.
Replace Action = "replace"
// Keep drops targets for which the input does not match the regex.
Keep Action = "keep"
// Drop drops targets for which the input does match the regex.
Drop Action = "drop"
2022-11-10 05:17:47 -08:00
// KeepEqual drops targets for which the input does not match the target.
KeepEqual Action = "keepequal"
2024-05-08 08:57:09 -07:00
// DropEqual drops targets for which the input does match the target.
2022-11-10 05:17:47 -08:00
DropEqual Action = "dropequal"
2018-12-18 03:26:36 -08:00
// HashMod sets a label to the modulus of a hash of labels.
HashMod Action = "hashmod"
// LabelMap copies labels to other labelnames based on a regex.
LabelMap Action = "labelmap"
// LabelDrop drops any label matching the regex.
LabelDrop Action = "labeldrop"
// LabelKeep drops any label not matching the regex.
LabelKeep Action = "labelkeep"
2022-05-03 01:09:53 -07:00
// Lowercase maps input letters to their lower case.
Lowercase Action = "lowercase"
// Uppercase maps input letters to their upper case.
Uppercase Action = "uppercase"
2018-12-18 03:26:36 -08:00
)
// UnmarshalYAML implements the yaml.Unmarshaler interface.
func ( a * Action ) UnmarshalYAML ( unmarshal func ( interface { } ) error ) error {
var s string
if err := unmarshal ( & s ) ; err != nil {
return err
}
switch act := Action ( strings . ToLower ( s ) ) ; act {
2022-11-10 05:17:47 -08:00
case Replace , Keep , Drop , HashMod , LabelMap , LabelDrop , LabelKeep , Lowercase , Uppercase , KeepEqual , DropEqual :
2018-12-18 03:26:36 -08:00
* a = act
return nil
}
2022-06-27 12:29:19 -07:00
return fmt . Errorf ( "unknown relabel action %q" , s )
2018-12-18 03:26:36 -08:00
}
// Config is the configuration for relabeling of target label sets.
type Config struct {
// A list of labels from which values are taken and concatenated
// with the configured separator in order.
2024-11-24 10:46:24 -08:00
SourceLabels model . LabelNames ` yaml:"source_labels,flow,omitempty" json:"sourceLabels,omitempty" `
2018-12-18 03:26:36 -08:00
// Separator is the string between concatenated values from the source labels.
2024-11-24 10:46:24 -08:00
Separator string ` yaml:"separator,omitempty" json:"separator,omitempty" `
2018-12-18 03:26:36 -08:00
// Regex against which the concatenation is matched.
2024-11-24 10:46:24 -08:00
Regex Regexp ` yaml:"regex,omitempty" json:"regex,omitempty" `
2018-12-18 03:26:36 -08:00
// Modulus to take of the hash of concatenated values from the source labels.
2024-11-24 10:46:24 -08:00
Modulus uint64 ` yaml:"modulus,omitempty" json:"modulus,omitempty" `
2018-12-18 03:26:36 -08:00
// TargetLabel is the label to which the resulting string is written in a replacement.
// Regexp interpolation is allowed for the replace action.
2024-11-24 10:46:24 -08:00
TargetLabel string ` yaml:"target_label,omitempty" json:"targetLabel,omitempty" `
2018-12-18 03:26:36 -08:00
// Replacement is the regex replacement pattern to be used.
2024-11-24 10:46:24 -08:00
Replacement string ` yaml:"replacement,omitempty" json:"replacement,omitempty" `
2018-12-18 03:26:36 -08:00
// Action is the action to be performed for the relabeling.
2024-11-24 10:46:24 -08:00
Action Action ` yaml:"action,omitempty" json:"action,omitempty" `
2018-12-18 03:26:36 -08:00
}
// UnmarshalYAML implements the yaml.Unmarshaler interface.
func ( c * Config ) UnmarshalYAML ( unmarshal func ( interface { } ) error ) error {
* c = DefaultRelabelConfig
type plain Config
if err := unmarshal ( ( * plain ) ( c ) ) ; err != nil {
return err
}
if c . Regex . Regexp == nil {
c . Regex = MustNewRegexp ( "" )
}
2023-12-18 06:52:42 -08:00
return c . Validate ( )
}
func ( c * Config ) Validate ( ) error {
2021-08-31 08:52:57 -07:00
if c . Action == "" {
2024-11-03 04:15:51 -08:00
return errors . New ( "relabel action cannot be empty" )
2021-08-31 08:52:57 -07:00
}
2018-12-18 03:26:36 -08:00
if c . Modulus == 0 && c . Action == HashMod {
2024-11-03 04:15:51 -08:00
return errors . New ( "relabel configuration for hashmod requires non-zero modulus" )
2018-12-18 03:26:36 -08:00
}
2022-11-10 05:17:47 -08:00
if ( c . Action == Replace || c . Action == HashMod || c . Action == Lowercase || c . Action == Uppercase || c . Action == KeepEqual || c . Action == DropEqual ) && c . TargetLabel == "" {
2022-06-27 12:29:19 -07:00
return fmt . Errorf ( "relabel configuration for %s action requires 'target_label' value" , c . Action )
2018-12-18 03:26:36 -08:00
}
2025-01-23 00:53:59 -08:00
if c . Action == Replace && ! varInRegexTemplate ( c . TargetLabel ) && ! model . LabelName ( c . TargetLabel ) . IsValid ( ) {
2023-12-18 06:58:56 -08:00
return fmt . Errorf ( "%q is invalid 'target_label' for %s action" , c . TargetLabel , c . Action )
}
2025-01-23 00:53:59 -08:00
2025-01-27 01:49:50 -08:00
isValidLabelNameWithRegexVarFn := func ( value string ) bool {
// UTF-8 allows ${} characters, so standard validation allow $variables by default.
// TODO(bwplotka): Relabelling users cannot put $ and ${<...>} characters in metric names or values.
// Design escaping mechanism to allow that, once valid use case appears.
return model . LabelName ( value ) . IsValid ( )
}
2025-01-23 00:53:59 -08:00
if model . NameValidationScheme == model . LegacyValidation {
2025-01-27 01:49:50 -08:00
isValidLabelNameWithRegexVarFn = func ( value string ) bool {
return relabelTargetLegacy . MatchString ( value )
}
2025-01-23 00:53:59 -08:00
}
2025-01-27 01:49:50 -08:00
if c . Action == Replace && varInRegexTemplate ( c . TargetLabel ) && ! isValidLabelNameWithRegexVarFn ( c . TargetLabel ) {
2023-12-18 06:58:56 -08:00
return fmt . Errorf ( "%q is invalid 'target_label' for %s action" , c . TargetLabel , c . Action )
}
if ( c . Action == Lowercase || c . Action == Uppercase || c . Action == KeepEqual || c . Action == DropEqual ) && ! model . LabelName ( c . TargetLabel ) . IsValid ( ) {
2022-06-27 12:29:19 -07:00
return fmt . Errorf ( "%q is invalid 'target_label' for %s action" , c . TargetLabel , c . Action )
2018-12-18 03:26:36 -08:00
}
2022-11-10 05:17:47 -08:00
if ( c . Action == Lowercase || c . Action == Uppercase || c . Action == KeepEqual || c . Action == DropEqual ) && c . Replacement != DefaultRelabelConfig . Replacement {
2022-06-27 12:29:19 -07:00
return fmt . Errorf ( "'replacement' can not be set for %s action" , c . Action )
2022-05-03 01:09:53 -07:00
}
2025-01-27 01:49:50 -08:00
if c . Action == LabelMap && ! isValidLabelNameWithRegexVarFn ( c . Replacement ) {
2022-06-27 12:29:19 -07:00
return fmt . Errorf ( "%q is invalid 'replacement' for %s action" , c . Replacement , c . Action )
2018-12-18 03:26:36 -08:00
}
if c . Action == HashMod && ! model . LabelName ( c . TargetLabel ) . IsValid ( ) {
2022-06-27 12:29:19 -07:00
return fmt . Errorf ( "%q is invalid 'target_label' for %s action" , c . TargetLabel , c . Action )
2018-12-18 03:26:36 -08:00
}
2022-11-10 05:17:47 -08:00
if c . Action == DropEqual || c . Action == KeepEqual {
if c . Regex != DefaultRelabelConfig . Regex ||
c . Modulus != DefaultRelabelConfig . Modulus ||
c . Separator != DefaultRelabelConfig . Separator ||
c . Replacement != DefaultRelabelConfig . Replacement {
return fmt . Errorf ( "%s action requires only 'source_labels' and `target_label`, and no other fields" , c . Action )
}
}
2018-12-18 03:26:36 -08:00
if c . Action == LabelDrop || c . Action == LabelKeep {
if c . SourceLabels != nil ||
c . TargetLabel != DefaultRelabelConfig . TargetLabel ||
c . Modulus != DefaultRelabelConfig . Modulus ||
c . Separator != DefaultRelabelConfig . Separator ||
c . Replacement != DefaultRelabelConfig . Replacement {
2022-06-27 12:29:19 -07:00
return fmt . Errorf ( "%s action requires only 'regex', and no other fields" , c . Action )
2018-12-18 03:26:36 -08:00
}
}
return nil
}
// Regexp encapsulates a regexp.Regexp and makes it YAML marshalable.
type Regexp struct {
* regexp . Regexp
}
// NewRegexp creates a new anchored Regexp and returns an error if the
// passed-in regular expression does not compile.
func NewRegexp ( s string ) ( Regexp , error ) {
2024-07-24 02:49:10 -07:00
regex , err := regexp . Compile ( "^(?s:" + s + ")$" )
2022-06-13 06:00:48 -07:00
return Regexp { Regexp : regex } , err
2018-12-18 03:26:36 -08:00
}
// MustNewRegexp works like NewRegexp, but panics if the regular expression does not compile.
func MustNewRegexp ( s string ) Regexp {
re , err := NewRegexp ( s )
if err != nil {
panic ( err )
}
return re
}
// UnmarshalYAML implements the yaml.Unmarshaler interface.
func ( re * Regexp ) UnmarshalYAML ( unmarshal func ( interface { } ) error ) error {
var s string
if err := unmarshal ( & s ) ; err != nil {
return err
}
r , err := NewRegexp ( s )
if err != nil {
return err
}
* re = r
return nil
}
// MarshalYAML implements the yaml.Marshaler interface.
func ( re Regexp ) MarshalYAML ( ) ( interface { } , error ) {
2022-06-13 06:00:48 -07:00
if re . String ( ) != "" {
return re . String ( ) , nil
2018-12-18 03:26:36 -08:00
}
return nil , nil
}
2024-11-24 10:46:24 -08:00
// UnmarshalJSON implements the json.Unmarshaler interface.
func ( re * Regexp ) UnmarshalJSON ( b [ ] byte ) error {
var s string
if err := json . Unmarshal ( b , & s ) ; err != nil {
return err
}
r , err := NewRegexp ( s )
if err != nil {
return err
}
* re = r
return nil
}
// MarshalJSON implements the json.Marshaler interface.
func ( re Regexp ) MarshalJSON ( ) ( [ ] byte , error ) {
return json . Marshal ( re . String ( ) )
}
2024-04-30 07:45:56 -07:00
// IsZero implements the yaml.IsZeroer interface.
func ( re Regexp ) IsZero ( ) bool {
return re . Regexp == DefaultRelabelConfig . Regex . Regexp
}
2022-06-13 06:00:48 -07:00
// String returns the original string used to compile the regular expression.
func ( re Regexp ) String ( ) string {
2024-07-26 05:55:39 -07:00
if re . Regexp == nil {
return ""
}
2022-06-13 06:00:48 -07:00
str := re . Regexp . String ( )
2024-07-24 02:49:10 -07:00
// Trim the anchor `^(?s:` prefix and `)$` suffix.
return str [ 5 : len ( str ) - 2 ]
2022-06-13 06:00:48 -07:00
}
2022-10-07 04:15:33 -07:00
// Process returns a relabeled version of the given label set. The relabel configurations
2016-12-28 10:04:59 -08:00
// are applied in order of input.
2022-10-07 04:15:33 -07:00
// There are circumstances where Process will modify the input label.
// If you want to avoid issues with the input label set being modified, at the cost of
// higher memory usage, you can use lbls.Copy().
2022-03-09 14:13:03 -08:00
// If a label set is dropped, EmptyLabels and false is returned.
func Process ( lbls labels . Labels , cfgs ... * Config ) ( ret labels . Labels , keep bool ) {
2023-02-28 11:09:58 -08:00
lb := labels . NewBuilder ( lbls )
if ! ProcessBuilder ( lb , cfgs ... ) {
return labels . EmptyLabels ( ) , false
}
2023-03-22 08:46:02 -07:00
return lb . Labels ( ) , true
2023-02-28 11:09:58 -08:00
}
// ProcessBuilder is like Process, but the caller passes a labels.Builder
// containing the initial set of labels, which is mutated by the rules.
func ProcessBuilder ( lb * labels . Builder , cfgs ... * Config ) ( keep bool ) {
2016-12-28 10:04:59 -08:00
for _ , cfg := range cfgs {
2023-02-28 11:09:58 -08:00
keep = relabel ( cfg , lb )
2022-03-09 14:13:03 -08:00
if ! keep {
2023-02-28 11:09:58 -08:00
return false
2016-12-28 10:04:59 -08:00
}
}
2023-02-28 11:09:58 -08:00
return true
2016-12-28 10:04:59 -08:00
}
2023-02-28 11:09:58 -08:00
func relabel ( cfg * Config , lb * labels . Builder ) ( keep bool ) {
2022-08-19 02:57:52 -07:00
var va [ 16 ] string
values := va [ : 0 ]
if len ( cfg . SourceLabels ) > cap ( values ) {
values = make ( [ ] string , 0 , len ( cfg . SourceLabels ) )
}
2016-12-28 10:04:59 -08:00
for _ , ln := range cfg . SourceLabels {
2023-02-28 11:09:58 -08:00
values = append ( values , lb . Get ( string ( ln ) ) )
2016-12-28 10:04:59 -08:00
}
val := strings . Join ( values , cfg . Separator )
switch cfg . Action {
2018-12-18 03:26:36 -08:00
case Drop :
2016-12-28 10:04:59 -08:00
if cfg . Regex . MatchString ( val ) {
2023-02-28 11:09:58 -08:00
return false
2016-12-28 10:04:59 -08:00
}
2018-12-18 03:26:36 -08:00
case Keep :
2016-12-28 10:04:59 -08:00
if ! cfg . Regex . MatchString ( val ) {
2023-02-28 11:09:58 -08:00
return false
2016-12-28 10:04:59 -08:00
}
2022-11-10 05:17:47 -08:00
case DropEqual :
2023-02-28 11:09:58 -08:00
if lb . Get ( cfg . TargetLabel ) == val {
return false
2022-11-10 05:17:47 -08:00
}
case KeepEqual :
2023-02-28 11:09:58 -08:00
if lb . Get ( cfg . TargetLabel ) != val {
return false
2016-12-28 10:04:59 -08:00
}
2018-12-18 03:26:36 -08:00
case Replace :
2023-03-24 02:18:24 -07:00
// Fast path to add or delete label pair.
if val == "" && cfg . Regex == DefaultRelabelConfig . Regex &&
! varInRegexTemplate ( cfg . TargetLabel ) && ! varInRegexTemplate ( cfg . Replacement ) {
2023-12-24 23:14:25 -08:00
lb . Set ( cfg . TargetLabel , cfg . Replacement )
2023-03-24 02:18:24 -07:00
break
}
2016-12-28 10:04:59 -08:00
indexes := cfg . Regex . FindStringSubmatchIndex ( val )
// If there is no match no replacement must take place.
if indexes == nil {
break
}
target := model . LabelName ( cfg . Regex . ExpandString ( [ ] byte { } , cfg . TargetLabel , val , indexes ) )
if ! target . IsValid ( ) {
break
}
res := cfg . Regex . ExpandString ( [ ] byte { } , cfg . Replacement , val , indexes )
if len ( res ) == 0 {
2023-12-18 08:38:59 -08:00
lb . Del ( string ( target ) )
2016-12-28 10:04:59 -08:00
break
}
lb . Set ( string ( target ) , string ( res ) )
2022-05-03 01:09:53 -07:00
case Lowercase :
lb . Set ( cfg . TargetLabel , strings . ToLower ( val ) )
case Uppercase :
lb . Set ( cfg . TargetLabel , strings . ToUpper ( val ) )
2018-12-18 03:26:36 -08:00
case HashMod :
2023-02-28 09:36:58 -08:00
hash := md5 . Sum ( [ ] byte ( val ) )
// Use only the last 8 bytes of the hash to give the same result as earlier versions of this code.
mod := binary . BigEndian . Uint64 ( hash [ 8 : ] ) % cfg . Modulus
2024-02-28 17:33:17 -08:00
lb . Set ( cfg . TargetLabel , strconv . FormatUint ( mod , 10 ) )
2018-12-18 03:26:36 -08:00
case LabelMap :
2023-02-28 11:09:58 -08:00
lb . Range ( func ( l labels . Label ) {
2016-12-28 10:04:59 -08:00
if cfg . Regex . MatchString ( l . Name ) {
res := cfg . Regex . ReplaceAllString ( l . Name , cfg . Replacement )
lb . Set ( res , l . Value )
}
2022-03-09 14:13:03 -08:00
} )
2018-12-18 03:26:36 -08:00
case LabelDrop :
2023-02-28 11:09:58 -08:00
lb . Range ( func ( l labels . Label ) {
2016-12-28 10:04:59 -08:00
if cfg . Regex . MatchString ( l . Name ) {
lb . Del ( l . Name )
}
2022-03-09 14:13:03 -08:00
} )
2018-12-18 03:26:36 -08:00
case LabelKeep :
2023-02-28 11:09:58 -08:00
lb . Range ( func ( l labels . Label ) {
2016-12-28 10:04:59 -08:00
if ! cfg . Regex . MatchString ( l . Name ) {
lb . Del ( l . Name )
}
2022-03-09 14:13:03 -08:00
} )
2016-12-28 10:04:59 -08:00
default :
2022-06-27 12:29:19 -07:00
panic ( fmt . Errorf ( "relabel: unknown relabel action type %q" , cfg . Action ) )
2016-12-28 10:04:59 -08:00
}
2023-02-28 11:09:58 -08:00
return true
2016-12-28 10:04:59 -08:00
}
2023-03-24 02:18:24 -07:00
func varInRegexTemplate ( template string ) bool {
return strings . Contains ( template , "$" )
}