github.com/muhammadn/cortex@v1.9.1-0.20220510110439-46bb7000d03d/pkg/configs/userconfig/config.go (about)

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