diff --git a/cmd/pint/bench_test.go b/cmd/pint/bench_test.go index eff74211..0077ac82 100644 --- a/cmd/pint/bench_test.go +++ b/cmd/pint/bench_test.go @@ -94,7 +94,7 @@ rule { require.NoError(b, os.WriteFile(tmp+"/.pint.hcl", content, 0o644)) ctx := context.Background() - cfg, err := config.Load(tmp+"/.pint.hcl", false) + cfg, _, err := config.Load(tmp+"/.pint.hcl", false) require.NoError(b, err) gen := config.NewPrometheusGenerator(cfg, prometheus.NewRegistry()) diff --git a/cmd/pint/config.go b/cmd/pint/config.go index 12ec04bd..f93ce1e7 100644 --- a/cmd/pint/config.go +++ b/cmd/pint/config.go @@ -21,7 +21,7 @@ func actionConfig(c *cli.Context) (err error) { return fmt.Errorf("failed to set log level: %w", err) } - cfg, err := config.Load(c.Path(configFlag), c.IsSet(configFlag)) + cfg, _, err := config.Load(c.Path(configFlag), c.IsSet(configFlag)) if err != nil { return fmt.Errorf("failed to load config file %q: %w", c.Path(configFlag), err) } diff --git a/cmd/pint/main.go b/cmd/pint/main.go index c2afc174..d4e0bc51 100644 --- a/cmd/pint/main.go +++ b/cmd/pint/main.go @@ -14,6 +14,7 @@ import ( const ( configFlag = "config" logLevelFlag = "log-level" + enabledFlag = "enabled" disabledFlag = "disabled" offlineFlag = "offline" noColorFlag = "no-color" @@ -59,6 +60,12 @@ func newApp() *cli.App { Value: cli.NewStringSlice(), Usage: "List of checks to disable (example: promql/cost).", }, + &cli.StringSliceFlag{ + Name: enabledFlag, + Aliases: []string{"e"}, + Value: cli.NewStringSlice(), + Usage: "Only enable these checks (example: promql/cost).", + }, &cli.BoolFlag{ Name: offlineFlag, Aliases: []string{"o"}, @@ -100,11 +107,22 @@ func actionSetup(c *cli.Context) (meta actionMeta, err error) { return meta, fmt.Errorf("--%s flag must be > 0", workersFlag) } - meta.cfg, err = config.Load(c.Path(configFlag), c.IsSet(configFlag)) + var fromFile bool + meta.cfg, fromFile, err = config.Load(c.Path(configFlag), c.IsSet(configFlag)) if err != nil { return meta, fmt.Errorf("failed to load config file %q: %w", c.Path(configFlag), err) } + if fromFile { + slog.Debug("Adding pint config to the parser exclude list", slog.String("path", c.Path(configFlag))) + meta.cfg.Parser.Exclude = append(meta.cfg.Parser.Exclude, c.Path(configFlag)) + } + meta.cfg.SetDisabledChecks(c.StringSlice(disabledFlag)) + enabled := c.StringSlice(enabledFlag) + if len(enabled) > 0 { + meta.cfg.Checks.Enabled = enabled + } + if c.Bool(offlineFlag) { meta.isOffline = true meta.cfg.DisableOnlineChecks() diff --git a/cmd/pint/tests/0018_match_alerting.txt b/cmd/pint/tests/0018_match_alerting.txt index 8b0fff03..b60e0ded 100644 --- a/cmd/pint/tests/0018_match_alerting.txt +++ b/cmd/pint/tests/0018_match_alerting.txt @@ -4,6 +4,7 @@ cmp stderr stderr.txt -- stderr.txt -- level=INFO msg="Loading configuration file" path=.pint.hcl +level=DEBUG msg="Adding pint config to the parser exclude list" path=.pint.hcl level=INFO msg="Finding all rules to check" paths=["rules"] level=DEBUG msg="File parsed" path=rules/0001.yml rules=2 level=DEBUG msg="Glob finder completed" count=2 diff --git a/cmd/pint/tests/0019_match_recording.txt b/cmd/pint/tests/0019_match_recording.txt index f533671d..3b4e568b 100644 --- a/cmd/pint/tests/0019_match_recording.txt +++ b/cmd/pint/tests/0019_match_recording.txt @@ -4,6 +4,7 @@ cmp stderr stderr.txt -- stderr.txt -- level=INFO msg="Loading configuration file" path=.pint.hcl +level=DEBUG msg="Adding pint config to the parser exclude list" path=.pint.hcl level=INFO msg="Finding all rules to check" paths=["rules"] level=DEBUG msg="File parsed" path=rules/0001.yml rules=2 level=DEBUG msg="Glob finder completed" count=2 diff --git a/cmd/pint/tests/0020_ignore_kind.txt b/cmd/pint/tests/0020_ignore_kind.txt index 48f040c8..f615366c 100644 --- a/cmd/pint/tests/0020_ignore_kind.txt +++ b/cmd/pint/tests/0020_ignore_kind.txt @@ -4,6 +4,7 @@ cmp stderr stderr.txt -- stderr.txt -- level=INFO msg="Loading configuration file" path=.pint.hcl +level=DEBUG msg="Adding pint config to the parser exclude list" path=.pint.hcl level=INFO msg="Finding all rules to check" paths=["rules"] level=DEBUG msg="File parsed" path=rules/0001.yml rules=2 level=DEBUG msg="Glob finder completed" count=2 diff --git a/cmd/pint/tests/0028_ci_git_error.txt b/cmd/pint/tests/0028_ci_git_error.txt index 609d7576..0dd931e8 100644 --- a/cmd/pint/tests/0028_ci_git_error.txt +++ b/cmd/pint/tests/0028_ci_git_error.txt @@ -21,11 +21,12 @@ cmp stderr ../stderr.txt -- stderr.txt -- level=INFO msg="Loading configuration file" path=.pint.hcl +level=DEBUG msg="Adding pint config to the parser exclude list" path=.pint.hcl level=DEBUG msg="Running git command" args=["rev-parse","--abbrev-ref","HEAD"] level=DEBUG msg="Got branch information" base=notmain current=v2 level=INFO msg="Finding all rules to check on current git branch" base=notmain level=DEBUG msg="Excluding git directory from glob results" path=.git glob=* -level=DEBUG msg="File parsed" path=.pint.hcl rules=0 +level=DEBUG msg="File path is in the exclude list" path=.pint.hcl exclude=["^.pint.hcl$"] level=DEBUG msg="File parsed" path=rules.yml rules=2 level=DEBUG msg="Glob finder completed" count=2 level=DEBUG msg="Running git command" args=["log","--reverse","--no-merges","--first-parent","--format=%H","--name-status","notmain..HEAD"] diff --git a/cmd/pint/tests/0037_disable_checks.txt b/cmd/pint/tests/0037_disable_checks.txt index 84178faf..391a03b9 100644 --- a/cmd/pint/tests/0037_disable_checks.txt +++ b/cmd/pint/tests/0037_disable_checks.txt @@ -4,6 +4,7 @@ cmp stderr stderr.txt -- stderr.txt -- level=INFO msg="Loading configuration file" path=.pint.hcl +level=DEBUG msg="Adding pint config to the parser exclude list" path=.pint.hcl level=INFO msg="Finding all rules to check" paths=["rules"] level=DEBUG msg="File parsed" path=rules/0001.yml rules=3 level=DEBUG msg="Glob finder completed" count=3 diff --git a/cmd/pint/tests/0039_prom_selected_path.txt b/cmd/pint/tests/0039_prom_selected_path.txt index be5ea151..9af0421c 100644 --- a/cmd/pint/tests/0039_prom_selected_path.txt +++ b/cmd/pint/tests/0039_prom_selected_path.txt @@ -4,6 +4,7 @@ cmp stderr stderr.txt -- stderr.txt -- level=INFO msg="Loading configuration file" path=.pint.hcl +level=DEBUG msg="Adding pint config to the parser exclude list" path=.pint.hcl level=INFO msg="Finding all rules to check" paths=["rules"] level=DEBUG msg="File parsed" path=rules/0001.yml rules=3 level=DEBUG msg="Glob finder completed" count=3 diff --git a/cmd/pint/tests/0040_rule_match_label.txt b/cmd/pint/tests/0040_rule_match_label.txt index 77d8d2f7..ecdaae08 100644 --- a/cmd/pint/tests/0040_rule_match_label.txt +++ b/cmd/pint/tests/0040_rule_match_label.txt @@ -4,6 +4,7 @@ cmp stderr stderr.txt -- stderr.txt -- level=INFO msg="Loading configuration file" path=.pint.hcl +level=DEBUG msg="Adding pint config to the parser exclude list" path=.pint.hcl level=INFO msg="Finding all rules to check" paths=["rules"] level=DEBUG msg="File parsed" path=rules/rules.yml rules=4 level=DEBUG msg="Glob finder completed" count=4 diff --git a/cmd/pint/tests/0052_match_multiple.txt b/cmd/pint/tests/0052_match_multiple.txt index 99b39a95..b21b15e8 100644 --- a/cmd/pint/tests/0052_match_multiple.txt +++ b/cmd/pint/tests/0052_match_multiple.txt @@ -4,6 +4,7 @@ cmp stderr stderr.txt -- stderr.txt -- level=INFO msg="Loading configuration file" path=.pint.hcl +level=DEBUG msg="Adding pint config to the parser exclude list" path=.pint.hcl level=INFO msg="Finding all rules to check" paths=["rules"] level=DEBUG msg="File parsed" path=rules/0001.yml rules=2 level=DEBUG msg="Glob finder completed" count=2 diff --git a/cmd/pint/tests/0053_ignore_multiple.txt b/cmd/pint/tests/0053_ignore_multiple.txt index 738e4cd0..a768734b 100644 --- a/cmd/pint/tests/0053_ignore_multiple.txt +++ b/cmd/pint/tests/0053_ignore_multiple.txt @@ -4,6 +4,7 @@ cmp stderr stderr.txt -- stderr.txt -- level=INFO msg="Loading configuration file" path=.pint.hcl +level=DEBUG msg="Adding pint config to the parser exclude list" path=.pint.hcl level=INFO msg="Finding all rules to check" paths=["rules"] level=DEBUG msg="File parsed" path=rules/0001.yml rules=2 level=DEBUG msg="Glob finder completed" count=2 diff --git a/cmd/pint/tests/0075_ci_strict.txt b/cmd/pint/tests/0075_ci_strict.txt index 5ee57a78..2cbe731d 100644 --- a/cmd/pint/tests/0075_ci_strict.txt +++ b/cmd/pint/tests/0075_ci_strict.txt @@ -45,6 +45,9 @@ groups: ci { baseBranch = "main" } +parser { + include = [".*.yml"] +} repository { bitbucket { uri = "http://127.0.0.1:6075" @@ -53,3 +56,4 @@ repository { repository = "rules" } } + diff --git a/cmd/pint/tests/0083_github_action.txt b/cmd/pint/tests/0083_github_action.txt index 3b7be0c3..39cb2a67 100644 --- a/cmd/pint/tests/0083_github_action.txt +++ b/cmd/pint/tests/0083_github_action.txt @@ -60,3 +60,6 @@ groups: -- src/.pint.hcl -- repository {} +parser { + include = [".*.yml"] +} diff --git a/cmd/pint/tests/0084_github_action_override.txt b/cmd/pint/tests/0084_github_action_override.txt index cb815ecc..4232dd1c 100644 --- a/cmd/pint/tests/0084_github_action_override.txt +++ b/cmd/pint/tests/0084_github_action_override.txt @@ -63,3 +63,6 @@ repository { repo = "pint" } } +parser { + include = [".*.yml"] +} diff --git a/cmd/pint/tests/0085_github_no_envs.txt b/cmd/pint/tests/0085_github_no_envs.txt index cb08aa4a..2e45ba74 100644 --- a/cmd/pint/tests/0085_github_no_envs.txt +++ b/cmd/pint/tests/0085_github_no_envs.txt @@ -58,3 +58,6 @@ repository { repo = "pint" } } +parser { + include = [".*.yml"] +} diff --git a/cmd/pint/tests/0094_rule_file_symlink_bb.txt b/cmd/pint/tests/0094_rule_file_symlink_bb.txt index 813f5239..e314706d 100644 --- a/cmd/pint/tests/0094_rule_file_symlink_bb.txt +++ b/cmd/pint/tests/0094_rule_file_symlink_bb.txt @@ -90,6 +90,9 @@ groups: ci { baseBranch = "main" } +parser { + include = [".*.yml"] +} repository { bitbucket { uri = "http://127.0.0.1:6094" diff --git a/cmd/pint/tests/0095_rulefmt_symlink.txt b/cmd/pint/tests/0095_rulefmt_symlink.txt index 4c20a525..4569d6b1 100644 --- a/cmd/pint/tests/0095_rulefmt_symlink.txt +++ b/cmd/pint/tests/0095_rulefmt_symlink.txt @@ -7,6 +7,7 @@ cmp stderr stderr.txt -- stderr.txt -- level=INFO msg="Loading configuration file" path=.pint.hcl +level=DEBUG msg="Adding pint config to the parser exclude list" path=.pint.hcl level=INFO msg="Finding all rules to check" paths=["rules"] level=DEBUG msg="File parsed" path=rules/relaxed/1.yml rules=1 level=DEBUG msg="File parsed" path=rules/strict/symlink.yml rules=1 diff --git a/cmd/pint/tests/0098_rule_file_symlink_gh.txt b/cmd/pint/tests/0098_rule_file_symlink_gh.txt index 1827196a..71f4398d 100644 --- a/cmd/pint/tests/0098_rule_file_symlink_gh.txt +++ b/cmd/pint/tests/0098_rule_file_symlink_gh.txt @@ -50,6 +50,9 @@ groups: ci { baseBranch = "main" } +parser { + include = [".*.yml"] +} repository { github { baseuri = "http://127.0.0.1:6098" diff --git a/cmd/pint/tests/0099_symlink_outside_glob.txt b/cmd/pint/tests/0099_symlink_outside_glob.txt index f58ee7b4..8fa8a567 100644 --- a/cmd/pint/tests/0099_symlink_outside_glob.txt +++ b/cmd/pint/tests/0099_symlink_outside_glob.txt @@ -7,6 +7,7 @@ cmp stderr stderr.txt -- stderr.txt -- level=INFO msg="Loading configuration file" path=.pint.hcl +level=DEBUG msg="Adding pint config to the parser exclude list" path=.pint.hcl level=INFO msg="Finding all rules to check" paths=["rules/relaxed"] level=DEBUG msg="File parsed" path=rules/relaxed/1.yml rules=1 level=DEBUG msg="Glob finder completed" count=1 diff --git a/cmd/pint/tests/0103_file_disable.txt b/cmd/pint/tests/0103_file_disable.txt index b0a54823..ca0b7645 100644 --- a/cmd/pint/tests/0103_file_disable.txt +++ b/cmd/pint/tests/0103_file_disable.txt @@ -4,6 +4,7 @@ cmp stderr stderr.txt -- stderr.txt -- level=INFO msg="Loading configuration file" path=.pint.hcl +level=DEBUG msg="Adding pint config to the parser exclude list" path=.pint.hcl level=INFO msg="Finding all rules to check" paths=["rules"] level=DEBUG msg="File parsed" path=rules/0001.yml rules=1 level=DEBUG msg="Glob finder completed" count=1 diff --git a/cmd/pint/tests/0111_snooze.txt b/cmd/pint/tests/0111_snooze.txt index 067ffb64..899088df 100644 --- a/cmd/pint/tests/0111_snooze.txt +++ b/cmd/pint/tests/0111_snooze.txt @@ -4,6 +4,7 @@ cmp stderr stderr.txt -- stderr.txt -- level=INFO msg="Loading configuration file" path=.pint.hcl +level=DEBUG msg="Adding pint config to the parser exclude list" path=.pint.hcl level=INFO msg="Finding all rules to check" paths=["rules"] level=DEBUG msg="File parsed" path=rules/0001.yml rules=1 level=DEBUG msg="Glob finder completed" count=1 diff --git a/cmd/pint/tests/0112_expired_snooze.txt b/cmd/pint/tests/0112_expired_snooze.txt index ee770c9b..172b6d4b 100644 --- a/cmd/pint/tests/0112_expired_snooze.txt +++ b/cmd/pint/tests/0112_expired_snooze.txt @@ -4,6 +4,7 @@ cmp stderr stderr.txt -- stderr.txt -- level=INFO msg="Loading configuration file" path=.pint.hcl +level=DEBUG msg="Adding pint config to the parser exclude list" path=.pint.hcl level=INFO msg="Finding all rules to check" paths=["rules"] level=DEBUG msg="File parsed" path=rules/0001.yml rules=1 level=DEBUG msg="Glob finder completed" count=1 diff --git a/cmd/pint/tests/0115_file_disable_tag.txt b/cmd/pint/tests/0115_file_disable_tag.txt index 502d0c8a..db49d85f 100644 --- a/cmd/pint/tests/0115_file_disable_tag.txt +++ b/cmd/pint/tests/0115_file_disable_tag.txt @@ -4,6 +4,7 @@ cmp stderr stderr.txt -- stderr.txt -- level=INFO msg="Loading configuration file" path=.pint.hcl +level=DEBUG msg="Adding pint config to the parser exclude list" path=.pint.hcl level=INFO msg="Finding all rules to check" paths=["rules"] level=DEBUG msg="File parsed" path=rules/0001.yml rules=1 level=DEBUG msg="Glob finder completed" count=1 diff --git a/cmd/pint/tests/0116_file_snooze.txt b/cmd/pint/tests/0116_file_snooze.txt index ac70bfbf..11aed777 100644 --- a/cmd/pint/tests/0116_file_snooze.txt +++ b/cmd/pint/tests/0116_file_snooze.txt @@ -4,6 +4,7 @@ cmp stderr stderr.txt -- stderr.txt -- level=INFO msg="Loading configuration file" path=.pint.hcl +level=DEBUG msg="Adding pint config to the parser exclude list" path=.pint.hcl level=INFO msg="Finding all rules to check" paths=["rules"] level=DEBUG msg="Check snoozed by comment" check=promql/aggregate(job:true) match=promql/aggregate(job:true) until="2099-11-28T10:24:18Z" level=DEBUG msg="Check snoozed by comment" check=alerts/for match=alerts/for until="2099-11-28T10:24:18Z" diff --git a/cmd/pint/tests/0134_ci_base_branch_flag_path.txt b/cmd/pint/tests/0134_ci_base_branch_flag_path.txt index 5b2fc91e..530728ad 100644 --- a/cmd/pint/tests/0134_ci_base_branch_flag_path.txt +++ b/cmd/pint/tests/0134_ci_base_branch_flag_path.txt @@ -21,11 +21,12 @@ cmp stderr ../stderr.txt -- stderr.txt -- level=INFO msg="Loading configuration file" path=.pint.hcl +level=DEBUG msg="Adding pint config to the parser exclude list" path=.pint.hcl level=DEBUG msg="Running git command" args=["rev-parse","--abbrev-ref","HEAD"] level=DEBUG msg="Got branch information" base=origin/main current=v2 level=INFO msg="Finding all rules to check on current git branch" base=origin/main level=DEBUG msg="Excluding git directory from glob results" path=.git glob=* -level=DEBUG msg="File parsed" path=.pint.hcl rules=0 +level=DEBUG msg="File path is in the exclude list" path=.pint.hcl exclude=["^.pint.hcl$"] level=DEBUG msg="File parsed" path=rules.yml rules=1 level=DEBUG msg="Glob finder completed" count=1 level=DEBUG msg="Running git command" args=["log","--reverse","--no-merges","--first-parent","--format=%H","--name-status","origin/main..HEAD"] diff --git a/cmd/pint/tests/0135_ci_base_branch_config_path.txt b/cmd/pint/tests/0135_ci_base_branch_config_path.txt index bcd99329..87395209 100644 --- a/cmd/pint/tests/0135_ci_base_branch_config_path.txt +++ b/cmd/pint/tests/0135_ci_base_branch_config_path.txt @@ -21,11 +21,12 @@ cmp stderr ../stderr.txt -- stderr.txt -- level=INFO msg="Loading configuration file" path=.pint.hcl +level=DEBUG msg="Adding pint config to the parser exclude list" path=.pint.hcl level=DEBUG msg="Running git command" args=["rev-parse","--abbrev-ref","HEAD"] level=DEBUG msg="Got branch information" base=origin/main current=v2 level=INFO msg="Finding all rules to check on current git branch" base=origin/main level=DEBUG msg="Excluding git directory from glob results" path=.git glob=* -level=DEBUG msg="File parsed" path=.pint.hcl rules=0 +level=DEBUG msg="File path is in the exclude list" path=.pint.hcl exclude=["^.pint.hcl$"] level=DEBUG msg="File parsed" path=rules.yml rules=1 level=DEBUG msg="Glob finder completed" count=1 level=DEBUG msg="Running git command" args=["log","--reverse","--no-merges","--first-parent","--format=%H","--name-status","origin/main..HEAD"] diff --git a/cmd/pint/tests/0144_discovery_filepath.txt b/cmd/pint/tests/0144_discovery_filepath.txt index e8e9de78..3ab79b22 100644 --- a/cmd/pint/tests/0144_discovery_filepath.txt +++ b/cmd/pint/tests/0144_discovery_filepath.txt @@ -4,6 +4,7 @@ cmp stderr stderr.txt -- stderr.txt -- level=INFO msg="Loading configuration file" path=.pint.hcl +level=DEBUG msg="Adding pint config to the parser exclude list" path=.pint.hcl level=INFO msg="Finding all rules to check" paths=["rules"] level=DEBUG msg="File parsed" path=rules/0001.yml rules=1 level=DEBUG msg="Glob finder completed" count=1 diff --git a/cmd/pint/tests/0145_discovery_filepath_dup.txt b/cmd/pint/tests/0145_discovery_filepath_dup.txt index 6cea1829..058087d0 100644 --- a/cmd/pint/tests/0145_discovery_filepath_dup.txt +++ b/cmd/pint/tests/0145_discovery_filepath_dup.txt @@ -4,6 +4,7 @@ cmp stderr stderr.txt -- stderr.txt -- level=INFO msg="Loading configuration file" path=.pint.hcl +level=DEBUG msg="Adding pint config to the parser exclude list" path=.pint.hcl level=INFO msg="Finding all rules to check" paths=["rules"] level=DEBUG msg="File parsed" path=rules/0001.yml rules=1 level=DEBUG msg="Glob finder completed" count=1 diff --git a/cmd/pint/tests/0147_discovery_filepath_error.txt b/cmd/pint/tests/0147_discovery_filepath_error.txt index a6cecc65..789e9165 100644 --- a/cmd/pint/tests/0147_discovery_filepath_error.txt +++ b/cmd/pint/tests/0147_discovery_filepath_error.txt @@ -4,6 +4,7 @@ cmp stderr stderr.txt -- stderr.txt -- level=INFO msg="Loading configuration file" path=.pint.hcl +level=DEBUG msg="Adding pint config to the parser exclude list" path=.pint.hcl level=INFO msg="Finding all rules to check" paths=["rules"] level=DEBUG msg="File parsed" path=rules/0001.yml rules=1 level=DEBUG msg="Glob finder completed" count=1 diff --git a/cmd/pint/tests/0148_discovery_prom_zero.txt b/cmd/pint/tests/0148_discovery_prom_zero.txt index 8cd2fc8e..4e84ad84 100644 --- a/cmd/pint/tests/0148_discovery_prom_zero.txt +++ b/cmd/pint/tests/0148_discovery_prom_zero.txt @@ -7,6 +7,7 @@ cmp stderr stderr.txt -- stderr.txt -- level=INFO msg="Loading configuration file" path=.pint.hcl +level=DEBUG msg="Adding pint config to the parser exclude list" path=.pint.hcl level=INFO msg="Finding all rules to check" paths=["rules"] level=DEBUG msg="File parsed" path=rules/0001.yml rules=1 level=DEBUG msg="Glob finder completed" count=1 diff --git a/cmd/pint/tests/0149_discovery_prom.txt b/cmd/pint/tests/0149_discovery_prom.txt index 82704e49..e40b0581 100644 --- a/cmd/pint/tests/0149_discovery_prom.txt +++ b/cmd/pint/tests/0149_discovery_prom.txt @@ -7,6 +7,7 @@ cmp stderr stderr.txt -- stderr.txt -- level=INFO msg="Loading configuration file" path=.pint.hcl +level=DEBUG msg="Adding pint config to the parser exclude list" path=.pint.hcl level=INFO msg="Finding all rules to check" paths=["rules"] level=DEBUG msg="File parsed" path=rules/0001.yml rules=1 level=DEBUG msg="Glob finder completed" count=1 diff --git a/cmd/pint/tests/0150_discovery_prom_dup_tags.txt b/cmd/pint/tests/0150_discovery_prom_dup_tags.txt index 29adeaf6..8880dc0b 100644 --- a/cmd/pint/tests/0150_discovery_prom_dup_tags.txt +++ b/cmd/pint/tests/0150_discovery_prom_dup_tags.txt @@ -7,6 +7,7 @@ cmp stderr stderr.txt -- stderr.txt -- level=INFO msg="Loading configuration file" path=.pint.hcl +level=DEBUG msg="Adding pint config to the parser exclude list" path=.pint.hcl level=INFO msg="Finding all rules to check" paths=["rules"] level=DEBUG msg="File parsed" path=rules/0001.yml rules=1 level=DEBUG msg="Glob finder completed" count=1 diff --git a/cmd/pint/tests/0151_discovery_prom_error.txt b/cmd/pint/tests/0151_discovery_prom_error.txt index a3520891..a32b6c24 100644 --- a/cmd/pint/tests/0151_discovery_prom_error.txt +++ b/cmd/pint/tests/0151_discovery_prom_error.txt @@ -7,6 +7,7 @@ cmp stderr stderr.txt -- stderr.txt -- level=INFO msg="Loading configuration file" path=.pint.hcl +level=DEBUG msg="Adding pint config to the parser exclude list" path=.pint.hcl level=INFO msg="Finding all rules to check" paths=["rules"] level=DEBUG msg="File parsed" path=rules/0001.yml rules=1 level=DEBUG msg="Glob finder completed" count=1 diff --git a/cmd/pint/tests/0152_discovery_prom_dup_uptime.txt b/cmd/pint/tests/0152_discovery_prom_dup_uptime.txt index 1e27c4ec..c71e09c3 100644 --- a/cmd/pint/tests/0152_discovery_prom_dup_uptime.txt +++ b/cmd/pint/tests/0152_discovery_prom_dup_uptime.txt @@ -7,6 +7,7 @@ cmp stderr stderr.txt -- stderr.txt -- level=INFO msg="Loading configuration file" path=.pint.hcl +level=DEBUG msg="Adding pint config to the parser exclude list" path=.pint.hcl level=INFO msg="Finding all rules to check" paths=["rules"] level=DEBUG msg="File parsed" path=rules/0001.yml rules=1 level=DEBUG msg="Glob finder completed" count=1 diff --git a/cmd/pint/tests/0155_discovery_prom_dup_include.txt b/cmd/pint/tests/0155_discovery_prom_dup_include.txt index e98c3b8d..57be5dd4 100644 --- a/cmd/pint/tests/0155_discovery_prom_dup_include.txt +++ b/cmd/pint/tests/0155_discovery_prom_dup_include.txt @@ -7,6 +7,7 @@ cmp stderr stderr.txt -- stderr.txt -- level=INFO msg="Loading configuration file" path=.pint.hcl +level=DEBUG msg="Adding pint config to the parser exclude list" path=.pint.hcl level=INFO msg="Finding all rules to check" paths=["rules"] level=DEBUG msg="File parsed" path=rules/0001.yml rules=1 level=DEBUG msg="Glob finder completed" count=1 diff --git a/cmd/pint/tests/0156_discovery_prom_dup_exclude.txt b/cmd/pint/tests/0156_discovery_prom_dup_exclude.txt index 42b87229..7f727439 100644 --- a/cmd/pint/tests/0156_discovery_prom_dup_exclude.txt +++ b/cmd/pint/tests/0156_discovery_prom_dup_exclude.txt @@ -7,6 +7,7 @@ cmp stderr stderr.txt -- stderr.txt -- level=INFO msg="Loading configuration file" path=.pint.hcl +level=DEBUG msg="Adding pint config to the parser exclude list" path=.pint.hcl level=INFO msg="Finding all rules to check" paths=["rules"] level=DEBUG msg="File parsed" path=rules/0001.yml rules=1 level=DEBUG msg="Glob finder completed" count=1 diff --git a/cmd/pint/tests/0163_ci_comment_resolve.txt b/cmd/pint/tests/0163_ci_comment_resolve.txt index 0edd6977..61ce8727 100644 --- a/cmd/pint/tests/0163_ci_comment_resolve.txt +++ b/cmd/pint/tests/0163_ci_comment_resolve.txt @@ -82,6 +82,9 @@ groups: ci { baseBranch = "main" } +parser { + exclude = [".*.pint.hcl"] +} repository { bitbucket { uri = "http://127.0.0.1:7163" diff --git a/cmd/pint/tests/0167_rule_duplicate_symlink.txt b/cmd/pint/tests/0167_rule_duplicate_symlink.txt index b7669a23..c992a505 100644 --- a/cmd/pint/tests/0167_rule_duplicate_symlink.txt +++ b/cmd/pint/tests/0167_rule_duplicate_symlink.txt @@ -28,7 +28,6 @@ cmp stderr ../stderrV1.txt -- stderrV1.txt -- level=INFO msg="Loading configuration file" path=.pint.hcl level=INFO msg="Finding all rules to check on current git branch" base=main -level=WARN msg="Failed to parse file content" err="error at line 1: top level field must be a groups key, got string" path=.pint.hcl lines=1-20 level=INFO msg="Configured new Prometheus server" name=prom1 uris=1 uptime=up tags=[] include=["^rules.yml$"] exclude=[] level=INFO msg="Configured new Prometheus server" name=prom2 uris=1 uptime=up tags=[] include=["^symlink.yml$"] exclude=[] -- stderrV2.txt -- diff --git a/cmd/pint/tests/0169_watch_rule_files_noprom.txt b/cmd/pint/tests/0169_watch_rule_files_noprom.txt index 272e10ee..6cf7cfce 100644 --- a/cmd/pint/tests/0169_watch_rule_files_noprom.txt +++ b/cmd/pint/tests/0169_watch_rule_files_noprom.txt @@ -4,6 +4,7 @@ cmp stderr stderr.txt -- stderr.txt -- level=INFO msg="Loading configuration file" path=.pint.hcl +level=DEBUG msg="Adding pint config to the parser exclude list" path=.pint.hcl level=INFO msg="Configured new Prometheus server" name=foo uris=1 uptime=up tags=[] include=[] exclude=[] level=DEBUG msg="Starting query workers" name=foo uri=http://localhost:7169 workers=16 level=ERROR msg="Fatal error" err="no Prometheus named \"prom\" configured in pint" diff --git a/cmd/pint/tests/0170_watch_rule_files_error.txt b/cmd/pint/tests/0170_watch_rule_files_error.txt index 206c5bc8..8c220152 100644 --- a/cmd/pint/tests/0170_watch_rule_files_error.txt +++ b/cmd/pint/tests/0170_watch_rule_files_error.txt @@ -8,6 +8,7 @@ cmp stderr stderr.txt -- stderr.txt -- level=INFO msg="Loading configuration file" path=.pint.hcl +level=DEBUG msg="Adding pint config to the parser exclude list" path=.pint.hcl level=INFO msg="Configured new Prometheus server" name=prom uris=1 uptime=up tags=[] include=[] exclude=[] level=DEBUG msg="Starting query workers" name=prom uri=http://localhost:7170 workers=16 level=DEBUG msg="Starting rule_fules watch" name=prom diff --git a/cmd/pint/tests/0173_rule_duplicate_move.txt b/cmd/pint/tests/0173_rule_duplicate_move.txt index 84e73f05..b2c55999 100644 --- a/cmd/pint/tests/0173_rule_duplicate_move.txt +++ b/cmd/pint/tests/0173_rule_duplicate_move.txt @@ -25,7 +25,6 @@ cmp stderr ../stderr.txt -- stderr.txt -- level=INFO msg="Loading configuration file" path=.pint.hcl level=INFO msg="Finding all rules to check on current git branch" base=main -level=WARN msg="Failed to parse file content" err="error at line 1: top level field must be a groups key, got string" path=.pint.hcl lines=1-24 level=INFO msg="Configured new Prometheus server" name=prom1 uris=1 uptime=up tags=[] include=["^rules/alert.*$"] exclude=[] level=INFO msg="Configured new Prometheus server" name=prom2a uris=1 uptime=up tags=[] include=["^rules/record.*$"] exclude=[] level=INFO msg="Configured new Prometheus server" name=prom2b uris=1 uptime=up tags=[] include=["^rules/record.*$"] exclude=[] diff --git a/cmd/pint/tests/0178_parser_include.txt b/cmd/pint/tests/0178_parser_include.txt index cf1546d3..b5601d93 100644 --- a/cmd/pint/tests/0178_parser_include.txt +++ b/cmd/pint/tests/0178_parser_include.txt @@ -4,6 +4,7 @@ cmp stderr stderr.txt -- stderr.txt -- level=INFO msg="Loading configuration file" path=.pint.hcl +level=DEBUG msg="Adding pint config to the parser exclude list" path=.pint.hcl level=INFO msg="Finding all rules to check" paths=["rules"] level=DEBUG msg="File path is in the include list" path=rules/0001.yml include=["^rules/0001.yml$"] level=DEBUG msg="File parsed" path=rules/0001.yml rules=1 diff --git a/cmd/pint/tests/0179_parser_exclude.txt b/cmd/pint/tests/0179_parser_exclude.txt index a759ebd0..6f29295c 100644 --- a/cmd/pint/tests/0179_parser_exclude.txt +++ b/cmd/pint/tests/0179_parser_exclude.txt @@ -4,9 +4,10 @@ cmp stderr stderr.txt -- stderr.txt -- level=INFO msg="Loading configuration file" path=.pint.hcl +level=DEBUG msg="Adding pint config to the parser exclude list" path=.pint.hcl level=INFO msg="Finding all rules to check" paths=["rules"] level=DEBUG msg="File parsed" path=rules/0001.yml rules=1 -level=DEBUG msg="File path is in the exclude list" path=rules/0002.yml exclude=["^rules/0002.yml$"] +level=DEBUG msg="File path is in the exclude list" path=rules/0002.yml exclude=["^rules/0002.yml$","^.pint.hcl$"] level=DEBUG msg="Glob finder completed" count=1 level=DEBUG msg="Generated all Prometheus servers" count=0 level=DEBUG msg="Found recording rule" path=rules/0001.yml record=ok lines=1-2 diff --git a/cmd/pint/tests/0180_parser_exclude_md.txt b/cmd/pint/tests/0180_parser_exclude_md.txt index 701fda72..aa238fe7 100644 --- a/cmd/pint/tests/0180_parser_exclude_md.txt +++ b/cmd/pint/tests/0180_parser_exclude_md.txt @@ -4,9 +4,10 @@ cmp stderr stderr.txt -- stderr.txt -- level=INFO msg="Loading configuration file" path=.pint.hcl +level=DEBUG msg="Adding pint config to the parser exclude list" path=.pint.hcl level=INFO msg="Finding all rules to check" paths=["rules"] level=DEBUG msg="File parsed" path=rules/0001.yml rules=1 -level=DEBUG msg="File path is in the exclude list" path=rules/README.md exclude=["^.*.md$"] +level=DEBUG msg="File path is in the exclude list" path=rules/README.md exclude=["^.*.md$","^.pint.hcl$"] level=DEBUG msg="Glob finder completed" count=1 level=DEBUG msg="Generated all Prometheus servers" count=0 level=DEBUG msg="Found recording rule" path=rules/0001.yml record=ok lines=1-2 diff --git a/cmd/pint/tests/0183_ci_state_any.txt b/cmd/pint/tests/0183_ci_state_any.txt new file mode 100644 index 00000000..bd334e34 --- /dev/null +++ b/cmd/pint/tests/0183_ci_state_any.txt @@ -0,0 +1,54 @@ +mkdir testrepo +cd testrepo +exec git init --initial-branch=main . + +cp ../src/rules.yml rules.yml +cp ../src/.pint.hcl . +env GIT_AUTHOR_NAME=pint +env GIT_AUTHOR_EMAIL=pint@example.com +env GIT_COMMITTER_NAME=pint +env GIT_COMMITTER_EMAIL=pint@example.com +exec git add . +exec git commit -am 'import rules and config' + +exec git checkout -b v2 +exec touch .keep +exec git add .keep +exec git commit -am 'v2' + +pint.error --no-color ci +! stdout . +cmp stderr ../stderr.txt + +-- stderr.txt -- +level=INFO msg="Loading configuration file" path=.pint.hcl +level=INFO msg="Finding all rules to check on current git branch" base=main +level=INFO msg="Problems found" Bug=1 +rules.yml:4 Bug: `job` label is required and should be preserved when aggregating `^.+$` rules, use `by(job, ...)`. (promql/aggregate) + 4 | expr: sum(foo) + +level=ERROR msg="Fatal error" err="problems found" +-- src/rules.yml -- +- record: rule1 + expr: sum(foo) by(job) +- record: rule2 + expr: sum(foo) + +-- src/.pint.hcl -- +ci { + baseBranch = "main" +} +parser { + relaxed = [".*"] + include = ["rules.yml"] +} +rule { + match { + kind = "recording" + state = ["any"] + } + aggregate ".+" { + keep = [ "job" ] + severity = "bug" + } +} diff --git a/cmd/pint/tests/0184_enable_checks.txt b/cmd/pint/tests/0184_enable_checks.txt new file mode 100644 index 00000000..ff81ffd3 --- /dev/null +++ b/cmd/pint/tests/0184_enable_checks.txt @@ -0,0 +1,44 @@ +pint.ok -l debug --no-color -e promql/aggregate lint rules +! stdout . +cmp stderr stderr.txt + +-- stderr.txt -- +level=INFO msg="Loading configuration file" path=.pint.hcl +level=DEBUG msg="Adding pint config to the parser exclude list" path=.pint.hcl +level=INFO msg="Finding all rules to check" paths=["rules"] +level=DEBUG msg="File parsed" path=rules/0001.yml rules=3 +level=DEBUG msg="Glob finder completed" count=3 +level=DEBUG msg="Generated all Prometheus servers" count=0 +level=DEBUG msg="Found alerting rule" path=rules/0001.yml alert=default-for lines=1-3 +level=DEBUG msg="Configured checks for rule" enabled=[] path=rules/0001.yml rule=default-for +level=DEBUG msg="Found recording rule" path=rules/0001.yml record=sum:job lines=5-6 +level=DEBUG msg="Configured checks for rule" enabled=["promql/aggregate(job:true)"] path=rules/0001.yml rule=sum:job +level=DEBUG msg="Found alerting rule" path=rules/0001.yml alert=no-comparison lines=8-9 +level=DEBUG msg="Configured checks for rule" enabled=[] path=rules/0001.yml rule=no-comparison +rules/0001.yml:6 Warning: `job` label is required and should be preserved when aggregating `^.+$` rules, use `by(job, ...)`. (promql/aggregate) + 6 | expr: sum(foo) + +level=INFO msg="Problems found" Warning=1 +-- rules/0001.yml -- +- alert: default-for + expr: foo > 1 + for: 0m + +- record: sum:job + expr: sum(foo) + +- alert: no-comparison + expr: foo + +-- .pint.hcl -- +parser { + relaxed = [".*"] +} +rule { + match { + kind = "recording" + } + aggregate ".+" { + keep = [ "job" ] + } +} diff --git a/docs/changelog.md b/docs/changelog.md index 62ade3fb..1710c9da 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -8,6 +8,13 @@ See check docs for details. - [promql/range_query](checks/promql/range_query.md) now allows to configure a custom maximum duration for range queries - #1064. +- Added `state` option to the rule `match` block. See [configuration](configuration.md) docs for details. +- Added `--enabled` flag to the pint command. Passing this flag will only run selected check(s). + +### Fixed + +- If there is a pint config file present then pint will now always add it to the `parser` block `exclude` list. + This is to avoid trying to parse it as a rule file if it's included in the same folder as rules. ## v0.64.1 diff --git a/docs/configuration.md b/docs/configuration.md index 2e9cb580..33c16029 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -12,7 +12,7 @@ nav_order: 2 {: .no_toc .text-delta } 1. TOC -{:toc} + {:toc} ## Environment variables @@ -80,10 +80,10 @@ parser { ```yaml groups: - - name: example - rules: - - record: ... - expr: ... + - name: example + rules: + - record: ... + expr: ... ``` If you're using pint to lint rules that are embedded inside a different structure @@ -519,9 +519,10 @@ Syntax: ```js rule { match { - path = "(.+)" - name = "(.+)" - kind = "alerting|recording" + path = "(.+)" + state = [ "any|added|modified|renamed|unmodified", ... ] + name = "(.+)" + kind = "alerting|recording" command = "ci|lint|watch" annotation "(.*)" { value = "(.*)" @@ -534,9 +535,9 @@ rule { match { ... } match { ... } ignore { - path = "(.+)" - name = "(.+)" - kind = "alerting|recording" + path = "(.+)" + name = "(.+)" + kind = "alerting|recording" command = "ci|lint|watch" annotation "(.*)" { value = "(.*)" @@ -555,10 +556,19 @@ rule { } ``` -- `match:path` - only files matching this pattern will be checked by this rule +- `match:path` - only files matching this pattern will be checked by this rule. +- `match:state` - only match rules based on their state. Default value for `state` depends on the + pint command that is being run, for `pint ci` the default value is `["added", "modified", "renamed"]`, + for any other command the default value is `["any"]`. + Possible values: + - `any` - match rule in any state + - `added` - a rule is being added in a git commit, a rule can only be in this state when running `pint ci` on a pull request. + - `modified` - a rule is being modified in a git commit, a rule can only be in this state when running `pint ci` on a pull request. + - `renamed` - a rule is being modified in a git commit, a rule can only be in this state when running `pint ci` on a pull request. + - `unmodified` - a rule is not being modified in a git commit when running `pint ci` or other pint command was run that doesn't try to detect which rules were modified. - `match:name` - only rules with names (`record` for recording rules and `alert` for alerting - rules) matching this pattern will be checked rule -- `match:kind` - optional rule type filter, only rule of this type will be checked + rules) matching this pattern will be checked rule. +- `match:kind` - optional rule type filter, only rule of this type will be checked. - `match:command` - optional command type filter, this allows to include or ignore rules based on the command pint is run with `pint ci`, `pint lint` or `pint watch`. - `match:annotation` - optional annotation filter, only alert rules with at least one @@ -581,6 +591,8 @@ be matched / ignored. Examples: +Check applied only to severity="critical" and severity="warning" alerts in "ci" or "lint" command is run: + ```js rule { match { @@ -593,10 +605,12 @@ rule { ignore { command = "watch" } - [ check applied only to severity="critical" and severity="warning" alerts in "ci" or "lint" command is run ] + check { ... } } ``` +Check applied unless "watch" or "lint" command is run: + ```js rule { ignore { @@ -605,24 +619,42 @@ rule { ignore { command = "lint" } - [ check applied unless "watch" or "lint" command is run ] + check { ... } } ``` +Check applied only to alerting rules with "for" field value that is >= 5m: + ```js rule { match { for = ">= 5m" } - [ check applied only to alerting rules with "for" field value that is >= 5m ] + check { ... } } ``` +Check applied only to alerting rules with "keep_firing_for" field value that is > 15m: + ```js rule { match { keep_firing_for = "> 15m" } - [ check applied only to alerting rules with "keep_firing_for" field value that is > 15m ] + check { ... } +} +``` + +Check that is run on all rules, including unmodified files, when running `pint ci`: + +```js +rule { + match { + state = ["any"] + } + ignore { + command = "ci" + } + check { ... } } ``` diff --git a/internal/config/config.go b/internal/config/config.go index a53519c9..e4f2bba4 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -165,7 +165,7 @@ func (cfg *Config) GetChecksForRule(ctx context.Context, gen *PrometheusGenerato } for _, rule := range cfg.Rules { - allChecks = append(allChecks, rule.resolveChecks(ctx, entry.Path.Name, entry.Rule, proms)...) + allChecks = append(allChecks, rule.resolveChecks(ctx, entry.Path.Name, entry, proms)...) } for _, cm := range allChecks { @@ -219,7 +219,7 @@ func getContext() *hcl.EvalContext { return &hcl.EvalContext{Variables: vars} } -func Load(path string, failOnMissing bool) (cfg Config, err error) { +func Load(path string, failOnMissing bool) (cfg Config, fromFile bool, err error) { cfg = Config{ CI: &CI{ MaxCommits: 20, @@ -237,81 +237,82 @@ func Load(path string, failOnMissing bool) (cfg Config, err error) { } if _, err = os.Stat(path); err == nil || failOnMissing { + fromFile = true slog.Info("Loading configuration file", slog.String("path", path)) ectx := getContext() err = hclsimple.DecodeFile(path, ectx, &cfg) if err != nil { - return cfg, err + return cfg, fromFile, err } } if cfg.CI != nil { if err = cfg.CI.validate(); err != nil { - return cfg, err + return cfg, fromFile, err } } if cfg.Owners != nil { if err = cfg.Owners.validate(); err != nil { - return cfg, err + return cfg, fromFile, err } } if cfg.Parser != nil { if err = cfg.Parser.validate(); err != nil { - return cfg, err + return cfg, fromFile, err } } if cfg.Repository != nil { if err = cfg.Repository.validate(); err != nil { - return cfg, err + return cfg, fromFile, err } } if cfg.Checks != nil { if err = cfg.Checks.validate(); err != nil { - return cfg, err + return cfg, fromFile, err } } for _, chk := range cfg.Check { if err = chk.validate(); err != nil { - return cfg, err + return cfg, fromFile, err } } promNames := make([]string, 0, len(cfg.Prometheus)) for i, prom := range cfg.Prometheus { if err = prom.validate(); err != nil { - return cfg, err + return cfg, fromFile, err } if slices.Contains(promNames, prom.Name) { - return cfg, fmt.Errorf("prometheus server name must be unique, found two or more config blocks using %q name", prom.Name) + return cfg, fromFile, fmt.Errorf("prometheus server name must be unique, found two or more config blocks using %q name", prom.Name) } promNames = append(promNames, prom.Name) cfg.Prometheus[i].applyDefaults() if _, err = prom.TLS.toHTTPConfig(); err != nil { - return cfg, fmt.Errorf("invalid prometheus TLS configuration: %w", err) + return cfg, fromFile, fmt.Errorf("invalid prometheus TLS configuration: %w", err) } } if cfg.Discovery != nil { if err = cfg.Discovery.validate(); err != nil { - return cfg, err + return cfg, fromFile, err } } for _, rule := range cfg.Rules { if err = rule.validate(); err != nil { - return cfg, err + return cfg, fromFile, err } } - return cfg, nil + return cfg, fromFile, nil } func parseDuration(d string) (time.Duration, error) { diff --git a/internal/config/config_test.go b/internal/config/config_test.go index d859b3ce..7a5cefeb 100644 --- a/internal/config/config_test.go +++ b/internal/config/config_test.go @@ -24,13 +24,15 @@ func TestMain(t *testing.M) { } func TestConfigLoadMissingFile(t *testing.T) { - _, err := config.Load("/foo/bar/pint.hcl", true) + _, ok, err := config.Load("/foo/bar/pint.hcl", true) require.EqualError(t, err, ": Configuration file not found; The configuration file /foo/bar/pint.hcl does not exist.") + require.True(t, ok) } func TestConfigLoadMissingFileOk(t *testing.T) { - _, err := config.Load("/foo/bar/pint.hcl", false) + _, ok, err := config.Load("/foo/bar/pint.hcl", false) require.NoError(t, err) + require.False(t, ok) } func TestDisableOnlineChecksWithPrometheus(t *testing.T) { @@ -43,8 +45,9 @@ prometheus "prom" { `), 0o644) require.NoError(t, err) - cfg, err := config.Load(path, true) + cfg, ok, err := config.Load(path, true) require.NoError(t, err) + require.True(t, ok) gen := config.NewPrometheusGenerator(cfg, prometheus.NewRegistry()) defer gen.Stop() @@ -64,7 +67,7 @@ func TestDisableOnlineChecksWithoutPrometheus(t *testing.T) { err := os.WriteFile(path, []byte(``), 0o644) require.NoError(t, err) - cfg, err := config.Load(path, true) + cfg, _, err := config.Load(path, true) require.NoError(t, err) gen := config.NewPrometheusGenerator(cfg, prometheus.NewRegistry()) @@ -90,7 +93,7 @@ prometheus "prom" { `), 0o644) require.NoError(t, err) - cfg, err := config.Load(path, true) + cfg, _, err := config.Load(path, true) require.NoError(t, err) gen := config.NewPrometheusGenerator(cfg, prometheus.NewRegistry()) @@ -117,7 +120,7 @@ func TestSetDisabledChecks(t *testing.T) { err := os.WriteFile(path, []byte(``), 0o644) require.NoError(t, err) - cfg, err := config.Load(path, true) + cfg, _, err := config.Load(path, true) require.NoError(t, err) gen := config.NewPrometheusGenerator(cfg, prometheus.NewRegistry()) @@ -1967,7 +1970,7 @@ rule { require.NoError(t, err) } - cfg, err := config.Load(path, false) + cfg, _, err := config.Load(path, false) require.NoError(t, err) gen := config.NewPrometheusGenerator(cfg, prometheus.NewRegistry()) @@ -2311,6 +2314,14 @@ func TestConfigErrors(t *testing.T) { }`, err: `not a valid duration string: "abc"`, }, + { + config: `rule { + match { + state = ["added", "foo"] + } +}`, + err: "unknown rule state: foo", + }, } dir := t.TempDir() @@ -2322,7 +2333,7 @@ func TestConfigErrors(t *testing.T) { require.NoError(t, err) } - _, err := config.Load(path, false) + _, _, err := config.Load(path, false) require.EqualError(t, err, tc.err, tc.config) }) } @@ -2343,6 +2354,6 @@ prometheus "prom" { `), 0o644) require.NoError(t, err) - _, err = config.Load(path, true) + _, _, err = config.Load(path, true) require.EqualError(t, err, `prometheus server name must be unique, found two or more config blocks using "prom" name`) } diff --git a/internal/config/match.go b/internal/config/match.go index ef0c1573..4fee015f 100644 --- a/internal/config/match.go +++ b/internal/config/match.go @@ -4,10 +4,12 @@ import ( "context" "errors" "fmt" + "log/slog" "regexp" "strings" "time" + "github.com/cloudflare/pint/internal/discovery" "github.com/cloudflare/pint/internal/parser" ) @@ -15,11 +17,12 @@ const ( AlertingRuleType = "alerting" RecordingRuleType = "recording" InvalidRuleType = "invalid" -) -type ( - ContextCommandKey string - ContextCommandVal string + StateAny = "any" + StateAdded = "added" + StateModified = "modified" + StateRenamed = "renamed" + StateUnmodified = "unmodified" ) var ( @@ -27,6 +30,15 @@ var ( CICommand ContextCommandVal = "ci" LintCommand ContextCommandVal = "lint" WatchCommand ContextCommandVal = "watch" + + CIStates = []string{StateAdded, StateModified, StateRenamed} + LintStates = []string{StateAny} + WatchStates = []string{StateAny} +) + +type ( + ContextCommandKey string + ContextCommandVal string ) type Match struct { @@ -38,6 +50,7 @@ type Match struct { Kind string `hcl:"kind,optional" json:"kind,omitempty"` For string `hcl:"for,optional" json:"for,omitempty"` KeepFiringFor string `hcl:"keep_firing_for,optional" json:"keep_firing_for,omitempty"` + State []string `hcl:"state,optional" json:"state,omitempty"` } func (m Match) validate(allowEmpty bool) error { @@ -76,6 +89,15 @@ func (m Match) validate(allowEmpty bool) error { } } + for _, s := range m.State { + switch s { + case StateAny, StateAdded, StateModified, StateRenamed, StateUnmodified: + // valid values + default: + return fmt.Errorf("unknown rule state: %s", s) + } + } + if !allowEmpty && m.Path == "" && m.Name == "" && m.Kind == "" && m.Label == nil && m.Annotation == nil && m.Command == nil && m.For == "" { return errors.New("ignore block must have at least one condition") } @@ -83,12 +105,24 @@ func (m Match) validate(allowEmpty bool) error { return nil } -func (m Match) IsMatch(ctx context.Context, path string, r parser.Rule) bool { +func (m Match) IsMatch(ctx context.Context, path string, e discovery.Entry) bool { + cmd := ctx.Value(CommandKey).(ContextCommandVal) + + if m.Command != nil { + if cmd != *m.Command { + return false + } + } + + if !stateMatches(ruleState(m.State, cmd), e.State) { + return false + } + if m.Kind != "" { - if r.AlertingRule != nil && m.Kind != AlertingRuleType { + if e.Rule.AlertingRule != nil && m.Kind != AlertingRuleType { return false } - if r.RecordingRule != nil && m.Kind != RecordingRuleType { + if e.Rule.RecordingRule != nil && m.Kind != RecordingRuleType { return false } } @@ -102,37 +136,30 @@ func (m Match) IsMatch(ctx context.Context, path string, r parser.Rule) bool { if m.Name != "" { re := strictRegex(m.Name) - if r.AlertingRule != nil && !re.MatchString(r.AlertingRule.Alert.Value) { + if e.Rule.AlertingRule != nil && !re.MatchString(e.Rule.AlertingRule.Alert.Value) { return false } - if r.RecordingRule != nil && !re.MatchString(r.RecordingRule.Record.Value) { + if e.Rule.RecordingRule != nil && !re.MatchString(e.Rule.RecordingRule.Record.Value) { return false } } if m.Label != nil { - if !m.Label.isMatching(r) { + if !m.Label.isMatching(e.Rule) { return false } } if m.Annotation != nil { - if !m.Annotation.isMatching(r) { - return false - } - } - - if m.Command != nil { - cmd := ctx.Value(CommandKey).(ContextCommandVal) - if cmd != *m.Command { + if !m.Annotation.isMatching(e.Rule) { return false } } if m.For != "" { - if r.AlertingRule != nil && r.AlertingRule.For != nil { + if e.Rule.AlertingRule != nil && e.Rule.AlertingRule.For != nil { dm, _ := parseDurationMatch(m.For) - if dur, err := parseDuration(r.AlertingRule.For.Value); err == nil { + if dur, err := parseDuration(e.Rule.AlertingRule.For.Value); err == nil { if !dm.isMatch(dur) { return false } @@ -143,9 +170,9 @@ func (m Match) IsMatch(ctx context.Context, path string, r parser.Rule) bool { } if m.KeepFiringFor != "" { - if r.AlertingRule != nil && r.AlertingRule.KeepFiringFor != nil { + if e.Rule.AlertingRule != nil && e.Rule.AlertingRule.KeepFiringFor != nil { dm, _ := parseDurationMatch(m.KeepFiringFor) - if dur, err := parseDuration(r.AlertingRule.KeepFiringFor.Value); err == nil { + if dur, err := parseDuration(e.Rule.AlertingRule.KeepFiringFor.Value); err == nil { if !dm.isMatch(dur) { return false } @@ -297,3 +324,54 @@ func (dm durationMatch) isMatch(dur time.Duration) bool { } return false } + +func ruleState(state []string, cmd ContextCommandVal) []string { + if len(state) != 0 { + return state + } + + switch cmd { + case CICommand: + return CIStates + case LintCommand: + return LintStates + case WatchCommand: + return WatchStates + } + return state +} + +func stateMatches(states []string, state discovery.ChangeType) bool { + if len(states) == 0 { + slog.Debug("Empty rule state match, including rule in checks") + return true + } + for _, s := range states { + switch s { + case StateAny: + return true + case StateAdded: + if state == discovery.Added { + return true + } + case StateModified: + if state == discovery.Modified { + return true + } + case StateRenamed: + if state == discovery.Moved { + return true + } + case StateUnmodified: + if state == discovery.Noop { + return true + } + } + } + slog.Debug( + "Rule state doesn't match, ignoring entry", + slog.String("state", state.String()), + slog.Any("allowed", states), + ) + return false +} diff --git a/internal/config/rule.go b/internal/config/rule.go index 3a5a3e3f..9a26d36c 100644 --- a/internal/config/rule.go +++ b/internal/config/rule.go @@ -9,6 +9,7 @@ import ( "github.com/cloudflare/pint/internal/checks" "github.com/cloudflare/pint/internal/comments" + "github.com/cloudflare/pint/internal/discovery" "github.com/cloudflare/pint/internal/parser" "github.com/cloudflare/pint/internal/promapi" ) @@ -111,11 +112,11 @@ func (rule Rule) validate() (err error) { return nil } -func (rule Rule) resolveChecks(ctx context.Context, path string, r parser.Rule, prometheusServers []*promapi.FailoverGroup) []checkMeta { +func (rule Rule) resolveChecks(ctx context.Context, path string, e discovery.Entry, prometheusServers []*promapi.FailoverGroup) []checkMeta { enabled := []checkMeta{} for _, ignore := range rule.Ignore { - if ignore.IsMatch(ctx, path, r) { + if ignore.IsMatch(ctx, path, e) { return enabled } } @@ -123,7 +124,7 @@ func (rule Rule) resolveChecks(ctx context.Context, path string, r parser.Rule, if len(rule.Match) > 0 { var found bool for _, match := range rule.Match { - if match.IsMatch(ctx, path, r) { + if match.IsMatch(ctx, path, e) { found = true break } @@ -131,6 +132,8 @@ func (rule Rule) resolveChecks(ctx context.Context, path string, r parser.Rule, if !found { return enabled } + } else if !(Match{}).IsMatch(ctx, path, e) { // Check empty matcher to validate rule state + return enabled } if len(rule.Aggregate) > 0 { diff --git a/internal/config/rule_test.go b/internal/config/rule_test.go index 702be8b6..f5f3ccea 100644 --- a/internal/config/rule_test.go +++ b/internal/config/rule_test.go @@ -6,6 +6,7 @@ import ( "testing" "github.com/cloudflare/pint/internal/config" + "github.com/cloudflare/pint/internal/discovery" "github.com/cloudflare/pint/internal/parser" "github.com/stretchr/testify/require" @@ -16,7 +17,7 @@ func TestMatch(t *testing.T) { match config.Match cmd config.ContextCommandVal path string - rule parser.Rule + entry discovery.Entry isMatch bool } @@ -24,15 +25,20 @@ func TestMatch(t *testing.T) { { cmd: config.LintCommand, path: "foo.yaml", - rule: parser.Rule{}, - + entry: discovery.Entry{ + Rule: parser.Rule{}, + State: discovery.Noop, + }, match: config.Match{}, isMatch: true, }, { cmd: config.LintCommand, path: "foo.yaml", - rule: parser.Rule{}, + entry: discovery.Entry{ + Rule: parser.Rule{}, + State: discovery.Noop, + }, match: config.Match{ Path: "bar.yaml", }, @@ -41,7 +47,10 @@ func TestMatch(t *testing.T) { { cmd: config.LintCommand, path: "foo.yaml", - rule: parser.Rule{}, + entry: discovery.Entry{ + Rule: parser.Rule{}, + State: discovery.Noop, + }, match: config.Match{ Path: "foo.yaml", }, @@ -50,7 +59,10 @@ func TestMatch(t *testing.T) { { cmd: config.LintCommand, path: "foo.yaml", - rule: parser.Rule{}, + entry: discovery.Entry{ + Rule: parser.Rule{}, + State: discovery.Noop, + }, match: config.Match{ Path: ".+.yaml", }, @@ -59,7 +71,10 @@ func TestMatch(t *testing.T) { { cmd: config.LintCommand, path: "foo.yaml", - rule: parser.Rule{}, + entry: discovery.Entry{ + Rule: parser.Rule{}, + State: discovery.Noop, + }, match: config.Match{ Path: "bar.+.yaml", }, @@ -68,10 +83,13 @@ func TestMatch(t *testing.T) { { cmd: config.LintCommand, path: "foo.yaml", - rule: parser.Rule{ - AlertingRule: &parser.AlertingRule{ - Alert: parser.YamlNode{Value: "Foo"}, + entry: discovery.Entry{ + Rule: parser.Rule{ + AlertingRule: &parser.AlertingRule{ + Alert: parser.YamlNode{Value: "Foo"}, + }, }, + State: discovery.Noop, }, match: config.Match{ Name: "Foo", @@ -81,10 +99,13 @@ func TestMatch(t *testing.T) { { cmd: config.LintCommand, path: "foo.yaml", - rule: parser.Rule{ - AlertingRule: &parser.AlertingRule{ - Alert: parser.YamlNode{Value: "Foo"}, + entry: discovery.Entry{ + Rule: parser.Rule{ + AlertingRule: &parser.AlertingRule{ + Alert: parser.YamlNode{Value: "Foo"}, + }, }, + State: discovery.Noop, }, match: config.Match{ Name: "Foo", @@ -95,10 +116,13 @@ func TestMatch(t *testing.T) { { cmd: config.LintCommand, path: "foo.yaml", - rule: parser.Rule{ - AlertingRule: &parser.AlertingRule{ - Alert: parser.YamlNode{Value: "Foo"}, + entry: discovery.Entry{ + Rule: parser.Rule{ + AlertingRule: &parser.AlertingRule{ + Alert: parser.YamlNode{Value: "Foo"}, + }, }, + State: discovery.Noop, }, match: config.Match{ Name: "Bar", @@ -108,10 +132,13 @@ func TestMatch(t *testing.T) { { cmd: config.LintCommand, path: "foo.yaml", - rule: parser.Rule{ - RecordingRule: &parser.RecordingRule{ - Record: parser.YamlNode{Value: "Foo"}, + entry: discovery.Entry{ + Rule: parser.Rule{ + RecordingRule: &parser.RecordingRule{ + Record: parser.YamlNode{Value: "Foo"}, + }, }, + State: discovery.Noop, }, match: config.Match{ Name: "Bar", @@ -121,17 +148,20 @@ func TestMatch(t *testing.T) { { cmd: config.LintCommand, path: "foo.yaml", - rule: parser.Rule{ - RecordingRule: &parser.RecordingRule{ - Labels: &parser.YamlMap{ - Items: []*parser.YamlKeyValue{ - { - Key: &parser.YamlNode{Value: "cluster"}, - Value: &parser.YamlNode{Value: "prod"}, + entry: discovery.Entry{ + Rule: parser.Rule{ + RecordingRule: &parser.RecordingRule{ + Labels: &parser.YamlMap{ + Items: []*parser.YamlKeyValue{ + { + Key: &parser.YamlNode{Value: "cluster"}, + Value: &parser.YamlNode{Value: "prod"}, + }, }, }, }, }, + State: discovery.Noop, }, match: config.Match{ Label: &config.MatchLabel{Key: "foo", Value: "bar"}, @@ -141,17 +171,20 @@ func TestMatch(t *testing.T) { { cmd: config.LintCommand, path: "foo.yaml", - rule: parser.Rule{ - RecordingRule: &parser.RecordingRule{ - Labels: &parser.YamlMap{ - Items: []*parser.YamlKeyValue{ - { - Key: &parser.YamlNode{Value: "cluster"}, - Value: &parser.YamlNode{Value: "prod"}, + entry: discovery.Entry{ + Rule: parser.Rule{ + RecordingRule: &parser.RecordingRule{ + Labels: &parser.YamlMap{ + Items: []*parser.YamlKeyValue{ + { + Key: &parser.YamlNode{Value: "cluster"}, + Value: &parser.YamlNode{Value: "prod"}, + }, }, }, }, }, + State: discovery.Noop, }, match: config.Match{ Annotation: &config.MatchAnnotation{Key: "foo", Value: "bar"}, @@ -161,17 +194,20 @@ func TestMatch(t *testing.T) { { cmd: config.LintCommand, path: "foo.yaml", - rule: parser.Rule{ - RecordingRule: &parser.RecordingRule{ - Labels: &parser.YamlMap{ - Items: []*parser.YamlKeyValue{ - { - Key: &parser.YamlNode{Value: "cluster"}, - Value: &parser.YamlNode{Value: "prod"}, + entry: discovery.Entry{ + Rule: parser.Rule{ + RecordingRule: &parser.RecordingRule{ + Labels: &parser.YamlMap{ + Items: []*parser.YamlKeyValue{ + { + Key: &parser.YamlNode{Value: "cluster"}, + Value: &parser.YamlNode{Value: "prod"}, + }, }, }, }, }, + State: discovery.Noop, }, match: config.Match{ Annotation: &config.MatchAnnotation{Key: "cluster", Value: "dev"}, @@ -181,17 +217,20 @@ func TestMatch(t *testing.T) { { cmd: config.LintCommand, path: "foo.yaml", - rule: parser.Rule{ - RecordingRule: &parser.RecordingRule{ - Labels: &parser.YamlMap{ - Items: []*parser.YamlKeyValue{ - { - Key: &parser.YamlNode{Value: "cluster"}, - Value: &parser.YamlNode{Value: "prod"}, + entry: discovery.Entry{ + Rule: parser.Rule{ + RecordingRule: &parser.RecordingRule{ + Labels: &parser.YamlMap{ + Items: []*parser.YamlKeyValue{ + { + Key: &parser.YamlNode{Value: "cluster"}, + Value: &parser.YamlNode{Value: "prod"}, + }, }, }, }, }, + State: discovery.Noop, }, match: config.Match{ Label: &config.MatchLabel{Key: "cluster", Value: "dev"}, @@ -201,17 +240,20 @@ func TestMatch(t *testing.T) { { cmd: config.LintCommand, path: "foo.yaml", - rule: parser.Rule{ - RecordingRule: &parser.RecordingRule{ - Labels: &parser.YamlMap{ - Items: []*parser.YamlKeyValue{ - { - Key: &parser.YamlNode{Value: "cluster"}, - Value: &parser.YamlNode{Value: "prod"}, + entry: discovery.Entry{ + Rule: parser.Rule{ + RecordingRule: &parser.RecordingRule{ + Labels: &parser.YamlMap{ + Items: []*parser.YamlKeyValue{ + { + Key: &parser.YamlNode{Value: "cluster"}, + Value: &parser.YamlNode{Value: "prod"}, + }, }, }, }, }, + State: discovery.Noop, }, match: config.Match{ Annotation: &config.MatchAnnotation{Key: "cluster", Value: "prod"}, @@ -221,17 +263,20 @@ func TestMatch(t *testing.T) { { cmd: config.LintCommand, path: "foo.yaml", - rule: parser.Rule{ - RecordingRule: &parser.RecordingRule{ - Labels: &parser.YamlMap{ - Items: []*parser.YamlKeyValue{ - { - Key: &parser.YamlNode{Value: "cluster"}, - Value: &parser.YamlNode{Value: "prod"}, + entry: discovery.Entry{ + Rule: parser.Rule{ + RecordingRule: &parser.RecordingRule{ + Labels: &parser.YamlMap{ + Items: []*parser.YamlKeyValue{ + { + Key: &parser.YamlNode{Value: "cluster"}, + Value: &parser.YamlNode{Value: "prod"}, + }, }, }, }, }, + State: discovery.Noop, }, match: config.Match{ Label: &config.MatchLabel{Key: "cluster", Value: "prod"}, @@ -241,8 +286,11 @@ func TestMatch(t *testing.T) { { cmd: config.LintCommand, path: "foo.yaml", - rule: parser.Rule{ - AlertingRule: &parser.AlertingRule{}, + entry: discovery.Entry{ + Rule: parser.Rule{ + AlertingRule: &parser.AlertingRule{}, + }, + State: discovery.Noop, }, match: config.Match{ Kind: "alerting", @@ -252,18 +300,23 @@ func TestMatch(t *testing.T) { { cmd: config.LintCommand, path: "foo.yaml", - rule: parser.Rule{ - AlertingRule: &parser.AlertingRule{}, + entry: discovery.Entry{ + Rule: parser.Rule{ + AlertingRule: &parser.AlertingRule{}, + }, + State: discovery.Noop, }, - match: config.Match{}, isMatch: true, }, { cmd: config.LintCommand, path: "foo.yaml", - rule: parser.Rule{ - AlertingRule: &parser.AlertingRule{}, + entry: discovery.Entry{ + Rule: parser.Rule{ + AlertingRule: &parser.AlertingRule{}, + }, + State: discovery.Noop, }, match: config.Match{ Kind: "recording", @@ -273,8 +326,11 @@ func TestMatch(t *testing.T) { { cmd: config.LintCommand, path: "foo.yaml", - rule: parser.Rule{ - RecordingRule: &parser.RecordingRule{}, + entry: discovery.Entry{ + Rule: parser.Rule{ + RecordingRule: &parser.RecordingRule{}, + }, + State: discovery.Noop, }, match: config.Match{ Kind: "recording", @@ -284,8 +340,11 @@ func TestMatch(t *testing.T) { { cmd: config.LintCommand, path: "foo.yaml", - rule: parser.Rule{ - RecordingRule: &parser.RecordingRule{}, + entry: discovery.Entry{ + Rule: parser.Rule{ + RecordingRule: &parser.RecordingRule{}, + }, + State: discovery.Noop, }, match: config.Match{ Kind: "alerting", @@ -295,17 +354,20 @@ func TestMatch(t *testing.T) { { cmd: config.LintCommand, path: "foo.yaml", - rule: parser.Rule{ - AlertingRule: &parser.AlertingRule{ - Annotations: &parser.YamlMap{ - Items: []*parser.YamlKeyValue{ - { - Key: &parser.YamlNode{Value: "cluster"}, - Value: &parser.YamlNode{Value: "prod"}, + entry: discovery.Entry{ + Rule: parser.Rule{ + AlertingRule: &parser.AlertingRule{ + Annotations: &parser.YamlMap{ + Items: []*parser.YamlKeyValue{ + { + Key: &parser.YamlNode{Value: "cluster"}, + Value: &parser.YamlNode{Value: "prod"}, + }, }, }, }, }, + State: discovery.Noop, }, match: config.Match{ Label: &config.MatchLabel{Key: "foo", Value: "bar"}, @@ -315,17 +377,20 @@ func TestMatch(t *testing.T) { { cmd: config.LintCommand, path: "foo.yaml", - rule: parser.Rule{ - AlertingRule: &parser.AlertingRule{ - Annotations: &parser.YamlMap{ - Items: []*parser.YamlKeyValue{ - { - Key: &parser.YamlNode{Value: "cluster"}, - Value: &parser.YamlNode{Value: "prod"}, + entry: discovery.Entry{ + Rule: parser.Rule{ + AlertingRule: &parser.AlertingRule{ + Annotations: &parser.YamlMap{ + Items: []*parser.YamlKeyValue{ + { + Key: &parser.YamlNode{Value: "cluster"}, + Value: &parser.YamlNode{Value: "prod"}, + }, }, }, }, }, + State: discovery.Noop, }, match: config.Match{ Annotation: &config.MatchAnnotation{Key: "foo", Value: "bar"}, @@ -335,17 +400,20 @@ func TestMatch(t *testing.T) { { cmd: config.LintCommand, path: "foo.yaml", - rule: parser.Rule{ - AlertingRule: &parser.AlertingRule{ - Annotations: &parser.YamlMap{ - Items: []*parser.YamlKeyValue{ - { - Key: &parser.YamlNode{Value: "cluster"}, - Value: &parser.YamlNode{Value: "prod"}, + entry: discovery.Entry{ + Rule: parser.Rule{ + AlertingRule: &parser.AlertingRule{ + Annotations: &parser.YamlMap{ + Items: []*parser.YamlKeyValue{ + { + Key: &parser.YamlNode{Value: "cluster"}, + Value: &parser.YamlNode{Value: "prod"}, + }, }, }, }, }, + State: discovery.Noop, }, match: config.Match{ Label: &config.MatchLabel{Key: "cluster", Value: "prod"}, @@ -355,17 +423,20 @@ func TestMatch(t *testing.T) { { cmd: config.LintCommand, path: "foo.yaml", - rule: parser.Rule{ - AlertingRule: &parser.AlertingRule{ - Annotations: &parser.YamlMap{ - Items: []*parser.YamlKeyValue{ - { - Key: &parser.YamlNode{Value: "cluster"}, - Value: &parser.YamlNode{Value: "prod"}, + entry: discovery.Entry{ + Rule: parser.Rule{ + AlertingRule: &parser.AlertingRule{ + Annotations: &parser.YamlMap{ + Items: []*parser.YamlKeyValue{ + { + Key: &parser.YamlNode{Value: "cluster"}, + Value: &parser.YamlNode{Value: "prod"}, + }, }, }, }, }, + State: discovery.Noop, }, match: config.Match{ Annotation: &config.MatchAnnotation{Key: "cluster", Value: "prod"}, @@ -375,7 +446,10 @@ func TestMatch(t *testing.T) { { cmd: config.LintCommand, path: "foo.yaml", - rule: parser.Rule{}, + entry: discovery.Entry{ + Rule: parser.Rule{}, + State: discovery.Noop, + }, match: config.Match{ Annotation: &config.MatchAnnotation{Key: "cluster", Value: "prod"}, }, @@ -384,7 +458,10 @@ func TestMatch(t *testing.T) { { cmd: config.LintCommand, path: "foo.yaml", - rule: parser.Rule{}, + entry: discovery.Entry{ + Rule: parser.Rule{}, + State: discovery.Noop, + }, match: config.Match{ Label: &config.MatchLabel{Key: "cluster", Value: "prod"}, }, @@ -393,7 +470,10 @@ func TestMatch(t *testing.T) { { cmd: config.LintCommand, path: "foo.yaml", - rule: parser.Rule{}, + entry: discovery.Entry{ + Rule: parser.Rule{}, + State: discovery.Noop, + }, match: config.Match{ Command: &config.LintCommand, }, @@ -402,7 +482,10 @@ func TestMatch(t *testing.T) { { cmd: config.LintCommand, path: "foo.yaml", - rule: parser.Rule{}, + entry: discovery.Entry{ + Rule: parser.Rule{}, + State: discovery.Noop, + }, match: config.Match{ Command: &config.WatchCommand, }, @@ -411,19 +494,167 @@ func TestMatch(t *testing.T) { { cmd: config.CICommand, path: "foo.yaml", - rule: parser.Rule{}, + entry: discovery.Entry{ + Rule: parser.Rule{}, + State: discovery.Noop, + }, match: config.Match{ Command: &config.CICommand, Path: "bar.yaml", }, isMatch: false, }, + { + cmd: config.CICommand, + path: "foo.yaml", + entry: discovery.Entry{ + Rule: parser.Rule{}, + State: discovery.Noop, + }, + match: config.Match{ + Command: &config.CICommand, + }, + isMatch: false, + }, + { + cmd: config.CICommand, + path: "foo.yaml", + entry: discovery.Entry{ + Rule: parser.Rule{}, + State: discovery.Added, + }, + match: config.Match{ + Command: &config.CICommand, + }, + isMatch: true, + }, + { + cmd: config.CICommand, + path: "foo.yaml", + entry: discovery.Entry{ + Rule: parser.Rule{}, + State: discovery.Noop, + }, + match: config.Match{ + Command: &config.CICommand, + State: []string{config.StateAny}, + }, + isMatch: true, + }, + { + cmd: config.CICommand, + path: "foo.yaml", + entry: discovery.Entry{ + Rule: parser.Rule{}, + State: discovery.Noop, + }, + match: config.Match{ + Command: &config.CICommand, + State: []string{config.StateAdded}, + }, + isMatch: false, + }, + { + cmd: config.CICommand, + path: "foo.yaml", + entry: discovery.Entry{ + Rule: parser.Rule{}, + State: discovery.Noop, + }, + match: config.Match{ + Command: &config.CICommand, + State: []string{config.StateUnmodified}, + }, + isMatch: true, + }, + { + cmd: config.CICommand, + path: "foo.yaml", + entry: discovery.Entry{ + Rule: parser.Rule{}, + State: discovery.Moved, + }, + match: config.Match{ + Command: &config.CICommand, + State: []string{config.StateUnmodified}, + }, + isMatch: false, + }, + { + cmd: config.CICommand, + path: "foo.yaml", + entry: discovery.Entry{ + Rule: parser.Rule{}, + State: discovery.Modified, + }, + match: config.Match{ + Command: &config.CICommand, + State: []string{config.StateModified}, + }, + isMatch: true, + }, + { + cmd: config.CICommand, + path: "foo.yaml", + entry: discovery.Entry{ + Rule: parser.Rule{}, + State: discovery.Removed, + }, + match: config.Match{ + State: []string{config.StateModified}, + }, + isMatch: false, + }, + { + cmd: config.CICommand, + path: "foo.yaml", + entry: discovery.Entry{ + Rule: parser.Rule{}, + State: discovery.Moved, + }, + match: config.Match{ + State: []string{config.StateRenamed}, + }, + isMatch: true, + }, + { + cmd: config.CICommand, + path: "foo.yaml", + entry: discovery.Entry{ + Rule: parser.Rule{}, + State: discovery.Unknown, + }, + match: config.Match{ + State: []string{config.StateRenamed}, + }, + isMatch: false, + }, + { + cmd: config.LintCommand, + path: "foo.yaml", + entry: discovery.Entry{ + Rule: parser.Rule{}, + State: discovery.Noop, + }, + match: config.Match{}, + isMatch: true, + }, + { + cmd: config.WatchCommand, + path: "foo.yaml", + entry: discovery.Entry{ + Rule: parser.Rule{}, + State: discovery.Noop, + }, + match: config.Match{}, + isMatch: true, + }, } for i, tc := range testCases { t.Run(strconv.Itoa(i+1), func(t *testing.T) { ctx := context.WithValue(context.Background(), config.CommandKey, tc.cmd) - isMatch := tc.match.IsMatch(ctx, tc.path, tc.rule) + isMatch := tc.match.IsMatch(ctx, tc.path, tc.entry) require.Equal(t, tc.isMatch, isMatch) }) } diff --git a/internal/discovery/git_branch.go b/internal/discovery/git_branch.go index 91b40ae8..eba76368 100644 --- a/internal/discovery/git_branch.go +++ b/internal/discovery/git_branch.go @@ -36,10 +36,6 @@ type GitBranchFinder struct { } func (f GitBranchFinder) Find(allEntries []Entry) (entries []Entry, err error) { - for i := range allEntries { - allEntries[i].State = Excluded - } - changes, err := git.Changes(f.gitCmd, f.baseBranch, f.filter) if err != nil { return nil, err @@ -110,22 +106,24 @@ func (f GitBranchFinder) Find(allEntries []Entry) (entries []Entry, err error) { case me.hasBefore && me.hasAfter: switch { case me.isIdentical && !me.wasMoved: + me.after.State = Excluded + me.after.ModifiedLines = []int{} slog.Debug( "Rule content was not modified on HEAD, identical rule present before", slog.String("name", me.after.Rule.Name()), slog.String("lines", me.after.Rule.Lines.String()), ) - me.after.State = Excluded - me.after.ModifiedLines = []int{} case me.wasMoved: + me.after.State = Moved + me.after.ModifiedLines = git.CountLines(change.Body.After) slog.Debug( "Rule content was not modified on HEAD but the file was moved or renamed", slog.String("name", me.after.Rule.Name()), slog.String("lines", me.after.Rule.Lines.String()), ) - me.after.State = Moved - me.after.ModifiedLines = git.CountLines(change.Body.After) default: + me.after.State = Modified + me.after.ModifiedLines = commonLines(change.Body.ModifiedLines, me.after.ModifiedLines) slog.Debug( "Rule modified on HEAD branch", slog.String("name", me.after.Rule.Name()), @@ -134,8 +132,6 @@ func (f GitBranchFinder) Find(allEntries []Entry) (entries []Entry, err error) { slog.String("ruleLines", me.after.Rule.Lines.String()), slog.String("modifiedLines", output.FormatLineRangeString(me.after.ModifiedLines)), ) - me.after.State = Modified - me.after.ModifiedLines = commonLines(change.Body.ModifiedLines, me.after.ModifiedLines) } entries = append(entries, me.after) case me.hasBefore && !me.hasAfter && len(failedEntries) == 0: