github.com/yankunsam/loki/v2@v2.6.3-0.20220817130409-389df5235c27/pkg/configs/userconfig/config.go (about)

     1  package userconfig
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"time"
     7  
     8  	"github.com/go-kit/log"
     9  	"github.com/pkg/errors"
    10  	"github.com/prometheus/prometheus/model/labels"
    11  	"github.com/prometheus/prometheus/model/rulefmt"
    12  	"github.com/prometheus/prometheus/promql/parser"
    13  	"github.com/prometheus/prometheus/rules"
    14  	"gopkg.in/yaml.v3"
    15  
    16  	util_log "github.com/grafana/loki/pkg/util/log"
    17  )
    18  
    19  // An ID is the ID of a single users's Cortex configuration. When a
    20  // configuration changes, it gets a new ID.
    21  type ID int
    22  
    23  // RuleFormatVersion indicates which Prometheus rule format (v1 vs. v2) to use in parsing.
    24  type RuleFormatVersion int
    25  
    26  const (
    27  	// RuleFormatV1 is the Prometheus 1.x rule format.
    28  	RuleFormatV1 RuleFormatVersion = iota
    29  	// RuleFormatV2 is the Prometheus 2.x rule format.
    30  	RuleFormatV2 RuleFormatVersion = iota
    31  )
    32  
    33  // IsValid returns whether the rules format version is a valid (known) version.
    34  func (v RuleFormatVersion) IsValid() bool {
    35  	switch v {
    36  	case RuleFormatV1, RuleFormatV2:
    37  		return true
    38  	default:
    39  		return false
    40  	}
    41  }
    42  
    43  // MarshalJSON implements json.Marshaler.
    44  func (v RuleFormatVersion) MarshalJSON() ([]byte, error) {
    45  	switch v {
    46  	case RuleFormatV1:
    47  		return json.Marshal("1")
    48  	case RuleFormatV2:
    49  		return json.Marshal("2")
    50  	default:
    51  		return nil, fmt.Errorf("unknown rule format version %d", v)
    52  	}
    53  }
    54  
    55  // MarshalYAML implements yaml.Marshaler.
    56  func (v RuleFormatVersion) MarshalYAML() (interface{}, error) {
    57  	switch v {
    58  	case RuleFormatV1:
    59  		return yaml.Marshal("1")
    60  	case RuleFormatV2:
    61  		return yaml.Marshal("2")
    62  	default:
    63  		return nil, fmt.Errorf("unknown rule format version %d", v)
    64  	}
    65  }
    66  
    67  // UnmarshalJSON implements json.Unmarshaler.
    68  func (v *RuleFormatVersion) UnmarshalJSON(data []byte) error {
    69  	var s string
    70  	if err := json.Unmarshal(data, &s); err != nil {
    71  		return err
    72  	}
    73  	switch s {
    74  	case "1":
    75  		*v = RuleFormatV1
    76  	case "2":
    77  		*v = RuleFormatV2
    78  	default:
    79  		return fmt.Errorf("unknown rule format version %q", string(data))
    80  	}
    81  	return nil
    82  }
    83  
    84  // UnmarshalYAML implements yaml.Unmarshaler.
    85  func (v *RuleFormatVersion) UnmarshalYAML(unmarshal func(interface{}) error) error {
    86  	var s string
    87  	if err := unmarshal(&s); err != nil {
    88  		return err
    89  	}
    90  	switch s {
    91  	case "1":
    92  		*v = RuleFormatV1
    93  	case "2":
    94  		*v = RuleFormatV2
    95  	default:
    96  		return fmt.Errorf("unknown rule format version %q", s)
    97  	}
    98  	return nil
    99  }
   100  
   101  // A Config is a Cortex configuration for a single user.
   102  type Config struct {
   103  	// RulesFiles maps from a rules filename to file contents.
   104  	RulesConfig        RulesConfig
   105  	TemplateFiles      map[string]string
   106  	AlertmanagerConfig string
   107  }
   108  
   109  // configCompat is a compatibility struct to support old JSON config blobs
   110  // saved in the config DB that didn't have a rule format version yet and
   111  // just had a top-level field for the rule files.
   112  type configCompat struct {
   113  	RulesFiles         map[string]string `json:"rules_files" yaml:"rules_files"`
   114  	RuleFormatVersion  RuleFormatVersion `json:"rule_format_version" yaml:"rule_format_version"`
   115  	TemplateFiles      map[string]string `json:"template_files" yaml:"template_files"`
   116  	AlertmanagerConfig string            `json:"alertmanager_config" yaml:"alertmanager_config"`
   117  }
   118  
   119  // MarshalJSON implements json.Marshaler.
   120  func (c Config) MarshalJSON() ([]byte, error) {
   121  	compat := &configCompat{
   122  		RulesFiles:         c.RulesConfig.Files,
   123  		RuleFormatVersion:  c.RulesConfig.FormatVersion,
   124  		TemplateFiles:      c.TemplateFiles,
   125  		AlertmanagerConfig: c.AlertmanagerConfig,
   126  	}
   127  
   128  	return json.Marshal(compat)
   129  }
   130  
   131  // MarshalYAML implements yaml.Marshaler.
   132  func (c Config) MarshalYAML() (interface{}, error) {
   133  	compat := &configCompat{
   134  		RulesFiles:         c.RulesConfig.Files,
   135  		RuleFormatVersion:  c.RulesConfig.FormatVersion,
   136  		TemplateFiles:      c.TemplateFiles,
   137  		AlertmanagerConfig: c.AlertmanagerConfig,
   138  	}
   139  
   140  	return yaml.Marshal(compat)
   141  }
   142  
   143  // UnmarshalJSON implements json.Unmarshaler.
   144  func (c *Config) UnmarshalJSON(data []byte) error {
   145  	compat := configCompat{}
   146  	if err := json.Unmarshal(data, &compat); err != nil {
   147  		return err
   148  	}
   149  	*c = Config{
   150  		RulesConfig: RulesConfig{
   151  			Files:         compat.RulesFiles,
   152  			FormatVersion: compat.RuleFormatVersion,
   153  		},
   154  		TemplateFiles:      compat.TemplateFiles,
   155  		AlertmanagerConfig: compat.AlertmanagerConfig,
   156  	}
   157  	return nil
   158  }
   159  
   160  // UnmarshalYAML implements yaml.Unmarshaler.
   161  func (c *Config) UnmarshalYAML(unmarshal func(interface{}) error) error {
   162  	compat := configCompat{}
   163  	if err := unmarshal(&compat); err != nil {
   164  		return errors.WithStack(err)
   165  	}
   166  	*c = Config{
   167  		RulesConfig: RulesConfig{
   168  			Files:         compat.RulesFiles,
   169  			FormatVersion: compat.RuleFormatVersion,
   170  		},
   171  		TemplateFiles:      compat.TemplateFiles,
   172  		AlertmanagerConfig: compat.AlertmanagerConfig,
   173  	}
   174  	return nil
   175  }
   176  
   177  // View is what's returned from the Weave Cloud configs service
   178  // when we ask for all Cortex configurations.
   179  //
   180  // The configs service is essentially a JSON blob store that gives each
   181  // _version_ of a configuration a unique ID and guarantees that later versions
   182  // have greater IDs.
   183  type View struct {
   184  	ID        ID        `json:"id"`
   185  	Config    Config    `json:"config"`
   186  	DeletedAt time.Time `json:"deleted_at"`
   187  }
   188  
   189  // IsDeleted tells you if the config is deleted.
   190  func (v View) IsDeleted() bool {
   191  	return !v.DeletedAt.IsZero()
   192  }
   193  
   194  // GetVersionedRulesConfig specializes the view to just the rules config.
   195  func (v View) GetVersionedRulesConfig() *VersionedRulesConfig {
   196  	if v.Config.RulesConfig.Files == nil {
   197  		return nil
   198  	}
   199  	return &VersionedRulesConfig{
   200  		ID:        v.ID,
   201  		Config:    v.Config.RulesConfig,
   202  		DeletedAt: v.DeletedAt,
   203  	}
   204  }
   205  
   206  // RulesConfig is the rules configuration for a particular organization.
   207  type RulesConfig struct {
   208  	FormatVersion RuleFormatVersion `json:"format_version"`
   209  	Files         map[string]string `json:"files"`
   210  }
   211  
   212  // Equal compares two RulesConfigs for equality.
   213  //
   214  // instance Eq RulesConfig
   215  func (c RulesConfig) Equal(o RulesConfig) bool {
   216  	if c.FormatVersion != o.FormatVersion {
   217  		return false
   218  	}
   219  	if len(o.Files) != len(c.Files) {
   220  		return false
   221  	}
   222  	for k, v1 := range c.Files {
   223  		v2, ok := o.Files[k]
   224  		if !ok || v1 != v2 {
   225  			return false
   226  		}
   227  	}
   228  	return true
   229  }
   230  
   231  // Parse parses and validates the content of the rule files in a RulesConfig
   232  // according to the passed rule format version.
   233  func (c RulesConfig) Parse() (map[string][]rules.Rule, error) {
   234  	switch c.FormatVersion {
   235  	case RuleFormatV1:
   236  		return nil, fmt.Errorf("version %v isn't supported", c.FormatVersion)
   237  	case RuleFormatV2:
   238  		return c.parseV2()
   239  	default:
   240  		return nil, fmt.Errorf("unknown rule format version %v", c.FormatVersion)
   241  	}
   242  }
   243  
   244  // ParseFormatted returns the rulefmt map of a users rules configs. It allows
   245  // for rules to be mapped to disk and read by the prometheus rules manager.
   246  func (c RulesConfig) ParseFormatted() (map[string]rulefmt.RuleGroups, error) {
   247  	switch c.FormatVersion {
   248  	case RuleFormatV1:
   249  		return nil, fmt.Errorf("version %v isn't supported", c.FormatVersion)
   250  	case RuleFormatV2:
   251  		return c.parseV2Formatted()
   252  	default:
   253  		return nil, fmt.Errorf("unknown rule format version %v", c.FormatVersion)
   254  	}
   255  }
   256  
   257  // parseV2 parses and validates the content of the rule files in a RulesConfig
   258  // according to the Prometheus 2.x rule format.
   259  func (c RulesConfig) parseV2Formatted() (map[string]rulefmt.RuleGroups, error) {
   260  	ruleMap := map[string]rulefmt.RuleGroups{}
   261  
   262  	for fn, content := range c.Files {
   263  		rgs, errs := rulefmt.Parse([]byte(content))
   264  		for _, err := range errs { // return just the first error, if any
   265  			return nil, err
   266  		}
   267  		ruleMap[fn] = *rgs
   268  
   269  	}
   270  	return ruleMap, nil
   271  }
   272  
   273  // parseV2 parses and validates the content of the rule files in a RulesConfig
   274  // according to the Prometheus 2.x rule format.
   275  //
   276  // NOTE: On one hand, we cannot return fully-fledged lists of rules.Group
   277  // here yet, as creating a rules.Group requires already
   278  // passing in rules.ManagerOptions options (which in turn require a
   279  // notifier, appender, etc.), which we do not want to create simply
   280  // for parsing. On the other hand, we should not return barebones
   281  // rulefmt.RuleGroup sets here either, as only a fully-converted rules.Rule
   282  // is able to track alert states over multiple rule evaluations. The caller
   283  // would otherwise have to ensure to convert the rulefmt.RuleGroup only exactly
   284  // once, not for every evaluation (or risk losing alert pending states). So
   285  // it's probably better to just return a set of rules.Rule here.
   286  func (c RulesConfig) parseV2() (map[string][]rules.Rule, error) {
   287  	groups := map[string][]rules.Rule{}
   288  
   289  	for fn, content := range c.Files {
   290  		rgs, errs := rulefmt.Parse([]byte(content))
   291  		if len(errs) > 0 {
   292  			return nil, fmt.Errorf("error parsing %s: %v", fn, errs[0])
   293  		}
   294  
   295  		for _, rg := range rgs.Groups {
   296  			rls := make([]rules.Rule, 0, len(rg.Rules))
   297  			for _, rl := range rg.Rules {
   298  				expr, err := parser.ParseExpr(rl.Expr.Value)
   299  				if err != nil {
   300  					return nil, err
   301  				}
   302  
   303  				if rl.Alert.Value != "" {
   304  					rls = append(rls, rules.NewAlertingRule(
   305  						rl.Alert.Value,
   306  						expr,
   307  						time.Duration(rl.For),
   308  						labels.FromMap(rl.Labels),
   309  						labels.FromMap(rl.Annotations),
   310  						nil,
   311  						"",
   312  						true,
   313  						log.With(util_log.Logger, "alert", rl.Alert.Value),
   314  					))
   315  					continue
   316  				}
   317  				rls = append(rls, rules.NewRecordingRule(
   318  					rl.Record.Value,
   319  					expr,
   320  					labels.FromMap(rl.Labels),
   321  				))
   322  			}
   323  
   324  			// Group names have to be unique in Prometheus, but only within one rules file.
   325  			groups[rg.Name+";"+fn] = rls
   326  		}
   327  	}
   328  
   329  	return groups, nil
   330  }
   331  
   332  // VersionedRulesConfig is a RulesConfig together with a version.
   333  // `data Versioned a = Versioned { id :: ID , config :: a }`
   334  type VersionedRulesConfig struct {
   335  	ID        ID          `json:"id"`
   336  	Config    RulesConfig `json:"config"`
   337  	DeletedAt time.Time   `json:"deleted_at"`
   338  }
   339  
   340  // IsDeleted tells you if the config is deleted.
   341  func (vr VersionedRulesConfig) IsDeleted() bool {
   342  	return !vr.DeletedAt.IsZero()
   343  }