-
Notifications
You must be signed in to change notification settings - Fork 2.4k
Fix subtle bug in JSON/YAML encoding of inhibition rules #4292
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix subtle bug in JSON/YAML encoding of inhibition rules #4292
Conversation
1b619f8 to
044424b
Compare
044424b to
28fc755
Compare
This commit fixes a subtle bug introduced in prometheus#4177 where external code that imports config.InhibitRule for the purpose of JSON/YAML encoding can encode an inhibition rule with missing equals labels. This bug will happen if someone updates their Go modules, writes labels to the Equal field, and does not also write the same labels to the EqualStr field. To fix this, I propose removing EqualStr and changing Equal from model.LabelNames to []string. This will cause an intentional breaking change, and require the maintainer of downstream code to use []string instead of model.LabelNames. I consider this a better option to the subtle bug explained earlier. This still keeps the fix for prometheus#4177, but does so with an explicit breaking change to downstream users. Signed-off-by: George Robinson <george.robinson@grafana.com>
28fc755 to
34115a7
Compare
|
I have not observed or seen this bug in practice, but it's something I reconsidered when updating Mimir, and on reflection, I thought I should change. |
|
I also considered two other options where I used a private struct just for decoding YAML. The advantage here is that we get to keep using Option 1I first considered a fix based on this: diff --git a/config/config.go b/config/config.go
index b8e48413..1715b8fe 100644
--- a/config/config.go
+++ b/config/config.go
@@ -968,33 +968,40 @@ type InhibitRule struct {
TargetMatchers Matchers `yaml:"target_matchers,omitempty" json:"target_matchers,omitempty"`
// A set of labels that must be equal between the source and target alert
// for them to be a match.
- Equal model.LabelNames `yaml:"-" json:"-"`
- // EqualStr allows us to validate the label depending on whether UTF-8 is
- // enabled or disabled. It should be removed when Alertmanager is updated
- // to use the validation modes in recent versions of prometheus/common.
- EqualStr []string `yaml:"equal,omitempty" json:"equal,omitempty"`
+ Equal model.LabelNames `yaml:"equal,omitempty" json:"equal,omitempty"`
}
// UnmarshalYAML implements the yaml.Unmarshaler interface for InhibitRule.
func (r *InhibitRule) UnmarshalYAML(unmarshal func(interface{}) error) error {
type plain InhibitRule
- if err := unmarshal((*plain)(r)); err != nil {
+
+ // tmp allows us to validate the labels in Equals depending on whether UTF-8
+ // is enabled or disabled. It should be deleted hen Alertmanager is updated
+ // to use the validation modes in recent versions of prometheus/common.
+ tmp := struct {
+ plain `yaml:",inline"`
+ Equal []string `yaml:"equal,omitempty" json:"equal,omitempty"`
+ }{}
+
+ if err := unmarshal(&tmp); err != nil {
return err
}However, this causes issues with plain `yaml:",inline"`
Equal []string `yaml:"equal,omitempty" json:"equal,omitempty"`Option 2To avoid the issues with Option 1, I need to duplicate all of the fields like so: diff --git a/config/config.go b/config/config.go
index b8e48413..f66484be 100644
--- a/config/config.go
+++ b/config/config.go
@@ -968,33 +968,43 @@ type InhibitRule struct {
TargetMatchers Matchers `yaml:"target_matchers,omitempty" json:"target_matchers,omitempty"`
// A set of labels that must be equal between the source and target alert
// for them to be a match.
- Equal model.LabelNames `yaml:"-" json:"-"`
- // EqualStr allows us to validate the label depending on whether UTF-8 is
- // enabled or disabled. It should be removed when Alertmanager is updated
- // to use the validation modes in recent versions of prometheus/common.
- EqualStr []string `yaml:"equal,omitempty" json:"equal,omitempty"`
+ Equal model.LabelNames `yaml:"equal,omitempty" json:"equal,omitempty"`
}
// UnmarshalYAML implements the yaml.Unmarshaler interface for InhibitRule.
func (r *InhibitRule) UnmarshalYAML(unmarshal func(interface{}) error) error {
type plain InhibitRule
- if err := unmarshal((*plain)(r)); err != nil {
+
+ // tmp allows us to validate the labels in Equals depending on whether UTF-8
+ // is enabled or disabled. It should be deleted hen Alertmanager is updated
+ // to use the validation modes in recent versions of prometheus/common.
+ tmp := struct {
+ SourceMatch map[string]string `yaml:"source_match,omitempty" json:"source_match,omitempty"`
+ SourceMatchRE MatchRegexps `yaml:"source_match_re,omitempty" json:"source_match_re,omitempty"`
+ SourceMatchers Matchers `yaml:"source_matchers,omitempty" json:"source_matchers,omitempty"`
+ TargetMatch map[string]string `yaml:"target_match,omitempty" json:"target_match,omitempty"`
+ TargetMatchRE MatchRegexps `yaml:"target_match_re,omitempty" json:"target_match_re,omitempty"`
+ TargetMatchers Matchers `yaml:"target_matchers,omitempty" json:"target_matchers,omitempty"`
+ Equal []string `yaml:"equal,omitempty" json:"equal,omitempty"`
+ }{}
+
+ if err := unmarshal(&tmp); err != nil {
return err
}which works, but is a much larger change, and I feel more susceptible to error, as I still need to do the following at the end of + r.SourceMatch = tmp.SourceMatch
+ r.SourceMatchRE = tmp.SourceMatchRE
+ r.SourceMatchers = tmp.SourceMatchers
+ r.TargetMatch = tmp.TargetMatch
+ r.TargetMatchRE = tmp.TargetMatchRE
+ r.TargetMatchers = tmp.TargetMatchers |
| require.Len(t, c.InhibitRules, 1) | ||
| r := c.InhibitRules[0] | ||
| require.Equal(t, model.LabelNames{"qux", "corge"}, r.Equal) | ||
| require.Equal(t, []string{"qux", "corge"}, r.Equal) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, I thought there was some new changes to provide better UTF-8 support.
CC @bwplotka
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are, but...
The reason we change
model.LabelNamesto[]stringisprometheus/commoncannot support two modes at the same time (UTF-8 enabled and UTF-8 disabled). This is a blocking issue for projects like Mimir where UTF-8 development is at different stages in the Alertmanager and metrics codebases, and so Mimir depends on a global variable in prometheus/common due to a diamond dependency problem. This change avoids that dependency problem.
tl;dr is Mimir imports prometheus/common via prometheus/alertmanager and also via prometheus/prometheus. Alertmanager and Prometheus needs to set different values for model.NameValidationScheme at the same time, in the same program. Which of course isn't possible as it's a shared global variable. Instead, in Alertmanager we added our own compat package that duplicates the logic from promethues/common to specifically allow Alertmanager and Prometheus to have different levels of UTF-8 support at the same time when linked to the same program.
The problem is here we forgot to use it for inhibition rules equals fields. That's what #4177 solved, but it also introduced this bug. This PR fixes that bug.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Makes sense, thanks!
| require.Len(t, c.InhibitRules, 1) | ||
| r := c.InhibitRules[0] | ||
| require.Equal(t, model.LabelNames{"qux", "corge"}, r.Equal) | ||
| require.Equal(t, []string{"qux", "corge"}, r.Equal) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Makes sense, thanks!
This commit fixes a bug where UTF-8 characters were not allowed in the Equal field for inhibition rules when UTF-8 support was enabled. This meant a tenant who was using UTF-8 characters in their alerts, and were matching these characters in their routes, silences, etc - could not do the same with the Equal field for their inhibition rules. The fix is taken from prometheus/alertmanager#4292 where it is in Alertmanager v0.28.1. It has also been tested in Mimir.
This commit fixes a bug where UTF-8 characters were not allowed in the Equal field for inhibition rules when UTF-8 support was enabled. This meant a tenant who was using UTF-8 characters in their alerts, and were matching these characters in their routes, silences, etc - could not do the same with the Equal field for their inhibition rules. The fix is taken from prometheus/alertmanager#4292 where it is in Alertmanager v0.28.1. It has also been tested in Mimir.
This commit fixes a subtle bug introduced in #4177 where external code that imports
config.InhibitRulefor the purpose of JSON/YAML encoding can encode an inhibition rule with missing equals labels. This bug will happen if someone updates their Go modules, writes labels to theEqualfield, and does not also write the same labels to theEqualStrfield.Before #4177:
{ "equals": ["foo"] }After #4177:
{}To fix this, I propose removing
EqualStrand changingEqualfrommodel.LabelNamesto[]string. This will cause an intentional breaking change, and require the maintainer of downstream code to use[]stringinstead ofmodel.LabelNames. I consider this a better option to the subtle bug explained earlier.The reason we change
model.LabelNamesto[]stringisprometheus/commoncannot support two modes at the same time (UTF-8 enabled and UTF-8 disabled). This is a blocking issue for projects like Mimir where UTF-8 development is at different stages in the Alertmanager and metrics codebases, and so Mimir depends on a global variable inprometheus/commondue to a diamond dependency problem. This change avoids that dependency problem.This still keeps the fix for #4177, but does so with an explicit breaking change to downstream users.