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 }