diff --git a/cmd/promtool/main.go b/cmd/promtool/main.go index 22e7d16ff..9b436ff71 100644 --- a/cmd/promtool/main.go +++ b/cmd/promtool/main.go @@ -44,13 +44,16 @@ import ( yaml "gopkg.in/yaml.v2" "github.com/prometheus/prometheus/config" + "github.com/prometheus/prometheus/discovery" "github.com/prometheus/prometheus/discovery/file" _ "github.com/prometheus/prometheus/discovery/install" // Register service discovery implementations. "github.com/prometheus/prometheus/discovery/kubernetes" "github.com/prometheus/prometheus/discovery/targetgroup" + "github.com/prometheus/prometheus/notifier" "github.com/prometheus/prometheus/pkg/labels" "github.com/prometheus/prometheus/pkg/rulefmt" "github.com/prometheus/prometheus/promql" + "github.com/prometheus/prometheus/scrape" ) func main() { @@ -363,19 +366,60 @@ func checkConfig(filename string) ([]string, error) { } if len(files) != 0 { for _, f := range files { - err = checkSDFile(f) + var targetGroups []*targetgroup.Group + targetGroups, err = checkSDFile(f) if err != nil { return nil, errors.Errorf("checking SD file %q: %v", file, err) } + if err := checkTargetGroupsForScrapeConfig(targetGroups, scfg); err != nil { + return nil, err + } } continue } fmt.Printf(" WARNING: file %q for file_sd in scrape job %q does not exist\n", file, scfg.JobName) } + case discovery.StaticConfig: + if err := checkTargetGroupsForScrapeConfig(c, scfg); err != nil { + return nil, err + } } } } + alertConfig := cfg.AlertingConfig + for _, amcfg := range alertConfig.AlertmanagerConfigs { + for _, c := range amcfg.ServiceDiscoveryConfigs { + switch c := c.(type) { + case *file.SDConfig: + for _, file := range c.Files { + files, err := filepath.Glob(file) + if err != nil { + return nil, err + } + if len(files) != 0 { + for _, f := range files { + var targetGroups []*targetgroup.Group + targetGroups, err = checkSDFile(f) + if err != nil { + return nil, errors.Errorf("checking SD file %q: %v", file, err) + } + + if err := checkTargetGroupsForAlertmanager(targetGroups, amcfg); err != nil { + return nil, err + } + } + continue + } + fmt.Printf(" WARNING: file %q for file_sd in alertmanager config does not exist\n", file) + } + case discovery.StaticConfig: + if err := checkTargetGroupsForAlertmanager(c, amcfg); err != nil { + return nil, err + } + } + } + } return ruleFiles, nil } @@ -397,16 +441,16 @@ func checkTLSConfig(tlsConfig config_util.TLSConfig) error { return nil } -func checkSDFile(filename string) error { +func checkSDFile(filename string) ([]*targetgroup.Group, error) { fd, err := os.Open(filename) if err != nil { - return err + return nil, err } defer fd.Close() content, err := ioutil.ReadAll(fd) if err != nil { - return err + return nil, err } var targetGroups []*targetgroup.Group @@ -414,23 +458,23 @@ func checkSDFile(filename string) error { switch ext := filepath.Ext(filename); strings.ToLower(ext) { case ".json": if err := json.Unmarshal(content, &targetGroups); err != nil { - return err + return nil, err } case ".yml", ".yaml": if err := yaml.UnmarshalStrict(content, &targetGroups); err != nil { - return err + return nil, err } default: - return errors.Errorf("invalid file extension: %q", ext) + return nil, errors.Errorf("invalid file extension: %q", ext) } for i, tg := range targetGroups { if tg == nil { - return errors.Errorf("nil target group item found (index %d)", i) + return nil, errors.Errorf("nil target group item found (index %d)", i) } } - return nil + return targetGroups, nil } // CheckRules validates rule files. @@ -981,3 +1025,25 @@ func importRules(url *url.URL, start, end, outputDir string, evalInterval time.D return nil } + +func checkTargetGroupsForAlertmanager(targetGroups []*targetgroup.Group, amcfg *config.AlertmanagerConfig) error { + for _, tg := range targetGroups { + if _, _, err := notifier.AlertmanagerFromGroup(tg, amcfg); err != nil { + return err + } + } + + return nil +} + +func checkTargetGroupsForScrapeConfig(targetGroups []*targetgroup.Group, scfg *config.ScrapeConfig) error { + for _, tg := range targetGroups { + _, failures := scrape.TargetsFromGroup(tg, scfg) + if len(failures) > 0 { + first := failures[0] + return first + } + } + + return nil +} diff --git a/cmd/promtool/main_test.go b/cmd/promtool/main_test.go index 87ac13e76..8ca078cc5 100644 --- a/cmd/promtool/main_test.go +++ b/cmd/promtool/main_test.go @@ -111,7 +111,7 @@ func TestCheckSDFile(t *testing.T) { } for _, test := range cases { t.Run(test.name, func(t *testing.T) { - err := checkSDFile(test.file) + _, err := checkSDFile(test.file) if test.err != "" { require.Equalf(t, test.err, err.Error(), "Expected error %q, got %q", test.err, err.Error()) return @@ -163,3 +163,42 @@ func BenchmarkCheckDuplicates(b *testing.B) { checkDuplicates(rgs.Groups) } } + +func TestCheckTargetConfig(t *testing.T) { + cases := []struct { + name string + file string + err string + }{ + { + name: "url_in_scrape_targetgroup_with_relabel_config.good", + file: "url_in_scrape_targetgroup_with_relabel_config.good.yml", + err: "", + }, + { + name: "url_in_alert_targetgroup_with_relabel_config.good", + file: "url_in_alert_targetgroup_with_relabel_config.good.yml", + err: "", + }, + { + name: "url_in_scrape_targetgroup_with_relabel_config.bad", + file: "url_in_scrape_targetgroup_with_relabel_config.bad.yml", + err: "instance 0 in group 0: \"http://bad\" is not a valid hostname", + }, + { + name: "url_in_alert_targetgroup_with_relabel_config.bad", + file: "url_in_alert_targetgroup_with_relabel_config.bad.yml", + err: "\"http://bad\" is not a valid hostname", + }, + } + for _, test := range cases { + t.Run(test.name, func(t *testing.T) { + _, err := checkConfig("testdata/" + test.file) + if test.err != "" { + require.Equalf(t, test.err, err.Error(), "Expected error %q, got %q", test.err, err.Error()) + return + } + require.NoError(t, err) + }) + } +} diff --git a/cmd/promtool/testdata/url_in_alert_targetgroup_with_relabel_config.bad.yml b/cmd/promtool/testdata/url_in_alert_targetgroup_with_relabel_config.bad.yml new file mode 100644 index 000000000..a23628e18 --- /dev/null +++ b/cmd/promtool/testdata/url_in_alert_targetgroup_with_relabel_config.bad.yml @@ -0,0 +1,8 @@ +alerting: + alertmanagers: + - relabel_configs: + - source_labels: [__address__] + target_label: __param_target + static_configs: + - targets: + - http://bad diff --git a/cmd/promtool/testdata/url_in_alert_targetgroup_with_relabel_config.good.yml b/cmd/promtool/testdata/url_in_alert_targetgroup_with_relabel_config.good.yml new file mode 100644 index 000000000..575fe5f11 --- /dev/null +++ b/cmd/promtool/testdata/url_in_alert_targetgroup_with_relabel_config.good.yml @@ -0,0 +1,10 @@ +alerting: + alertmanagers: + - relabel_configs: + - source_labels: [__address__] + target_label: __param_target + - target_label: __address__ + replacement: good + static_configs: + - targets: + - http://bad diff --git a/cmd/promtool/testdata/url_in_scrape_targetgroup_with_relabel_config.bad.yml b/cmd/promtool/testdata/url_in_scrape_targetgroup_with_relabel_config.bad.yml new file mode 100644 index 000000000..d5daf6e9b --- /dev/null +++ b/cmd/promtool/testdata/url_in_scrape_targetgroup_with_relabel_config.bad.yml @@ -0,0 +1,8 @@ +scrape_configs: + - job_name: prometheus + relabel_configs: + - source_labels: [__address__] + target_label: __param_target + static_configs: + - targets: + - http://bad diff --git a/cmd/promtool/testdata/url_in_scrape_targetgroup_with_relabel_config.good.yml b/cmd/promtool/testdata/url_in_scrape_targetgroup_with_relabel_config.good.yml new file mode 100644 index 000000000..ab26f00ed --- /dev/null +++ b/cmd/promtool/testdata/url_in_scrape_targetgroup_with_relabel_config.good.yml @@ -0,0 +1,10 @@ +scrape_configs: + - job_name: prometheus + relabel_configs: + - source_labels: [__address__] + target_label: __param_target + - target_label: __address__ + replacement: good + static_configs: + - targets: + - http://good diff --git a/notifier/notifier.go b/notifier/notifier.go index 97086d562..19a7f0bf9 100644 --- a/notifier/notifier.go +++ b/notifier/notifier.go @@ -602,7 +602,7 @@ func (n *Manager) Stop() { n.cancel() } -// alertmanager holds Alertmanager endpoint information. +// Alertmanager holds Alertmanager endpoint information. type alertmanager interface { url() *url.URL } @@ -654,7 +654,7 @@ func (s *alertmanagerSet) sync(tgs []*targetgroup.Group) { allDroppedAms := []alertmanager{} for _, tg := range tgs { - ams, droppedAms, err := alertmanagerFromGroup(tg, s.cfg) + ams, droppedAms, err := AlertmanagerFromGroup(tg, s.cfg) if err != nil { level.Error(s.logger).Log("msg", "Creating discovered Alertmanagers failed", "err", err) continue @@ -691,9 +691,9 @@ func postPath(pre string, v config.AlertmanagerAPIVersion) string { return path.Join("/", pre, alertPushEndpoint) } -// alertmanagerFromGroup extracts a list of alertmanagers from a target group +// AlertmanagerFromGroup extracts a list of alertmanagers from a target group // and an associated AlertmanagerConfig. -func alertmanagerFromGroup(tg *targetgroup.Group, cfg *config.AlertmanagerConfig) ([]alertmanager, []alertmanager, error) { +func AlertmanagerFromGroup(tg *targetgroup.Group, cfg *config.AlertmanagerConfig) ([]alertmanager, []alertmanager, error) { var res []alertmanager var droppedAlertManagers []alertmanager diff --git a/notifier/notifier_test.go b/notifier/notifier_test.go index e4a7f26cd..b04b07e94 100644 --- a/notifier/notifier_test.go +++ b/notifier/notifier_test.go @@ -447,7 +447,7 @@ func (a alertmanagerMock) url() *url.URL { func TestLabelSetNotReused(t *testing.T) { tg := makeInputTargetGroup() - _, _, err := alertmanagerFromGroup(tg, &config.AlertmanagerConfig{}) + _, _, err := AlertmanagerFromGroup(tg, &config.AlertmanagerConfig{}) require.NoError(t, err) diff --git a/scrape/scrape.go b/scrape/scrape.go index e981ad36a..590133cde 100644 --- a/scrape/scrape.go +++ b/scrape/scrape.go @@ -472,7 +472,7 @@ func (sp *scrapePool) Sync(tgs []*targetgroup.Group) { var all []*Target sp.droppedTargets = []*Target{} for _, tg := range tgs { - targets, failures := targetsFromGroup(tg, sp.config) + targets, failures := TargetsFromGroup(tg, sp.config) for _, err := range failures { level.Error(sp.logger).Log("msg", "Creating target failed", "err", err) } diff --git a/scrape/target.go b/scrape/target.go index 35d691751..c791a8a22 100644 --- a/scrape/target.go +++ b/scrape/target.go @@ -469,8 +469,8 @@ func populateLabels(lset labels.Labels, cfg *config.ScrapeConfig) (res, orig lab return res, preRelabelLabels, nil } -// targetsFromGroup builds targets based on the given TargetGroup and config. -func targetsFromGroup(tg *targetgroup.Group, cfg *config.ScrapeConfig) ([]*Target, []error) { +// TargetsFromGroup builds targets based on the given TargetGroup and config. +func TargetsFromGroup(tg *targetgroup.Group, cfg *config.ScrapeConfig) ([]*Target, []error) { targets := make([]*Target, 0, len(tg.Targets)) failures := []error{} diff --git a/scrape/target_test.go b/scrape/target_test.go index 542326d7c..fa03c068e 100644 --- a/scrape/target_test.go +++ b/scrape/target_test.go @@ -371,7 +371,7 @@ func TestNewClientWithBadTLSConfig(t *testing.T) { func TestTargetsFromGroup(t *testing.T) { expectedError := "instance 0 in group : no address" - targets, failures := targetsFromGroup(&targetgroup.Group{Targets: []model.LabelSet{{}, {model.AddressLabel: "localhost:9090"}}}, &config.ScrapeConfig{}) + targets, failures := TargetsFromGroup(&targetgroup.Group{Targets: []model.LabelSet{{}, {model.AddressLabel: "localhost:9090"}}}, &config.ScrapeConfig{}) if len(targets) != 1 { t.Fatalf("Expected 1 target, got %v", len(targets)) }