From a19cc4085b583220e07784c86820f490863660b7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 5 May 2026 23:04:56 +0000 Subject: [PATCH 1/2] Initial plan From e07f4595552f7f24100aebc1902b1fc37abfe6a7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 5 May 2026 23:10:43 +0000 Subject: [PATCH 2/2] Fix rules lint failing with address is required error Agent-Logs-Url: https://github.com/cortexproject/cortex-tools/sessions/98c83076-3e00-4d75-91e0-4a809ca0824d Co-authored-by: friedrichg <1517449+friedrichg@users.noreply.github.com> --- pkg/commands/rules.go | 30 +++++++++++++++++++++++++++--- pkg/commands/rules_test.go | 19 +++++++++++++++++++ 2 files changed, 46 insertions(+), 3 deletions(-) diff --git a/pkg/commands/rules.go b/pkg/commands/rules.go index 1237c99d8..459ebb22c 100644 --- a/pkg/commands/rules.go +++ b/pkg/commands/rules.go @@ -248,10 +248,10 @@ func (r *RuleCommand) setup(_ *kingpin.ParseContext) error { ) // Apply config file defaults - if err := ApplyConfigDefaults(&r.ClientConfig); err != nil { - return err - } + return ApplyConfigDefaults(&r.ClientConfig) +} +func (r *RuleCommand) setupClient() error { // Validate required fields (they may come from config file) if r.ClientConfig.Address == "" { return fmt.Errorf("cortex address is required (use --address flag, CORTEX_ADDRESS env var, or config file)") @@ -345,6 +345,9 @@ func (r *RuleCommand) setupFiles() error { } func (r *RuleCommand) listRules(_ *kingpin.ParseContext) error { + if err := r.setupClient(); err != nil { + return err + } rules, err := r.cli.ListRules(context.Background(), "") if err != nil { if errors.Is(err, client.ErrResourceNotFound) { @@ -359,6 +362,9 @@ func (r *RuleCommand) listRules(_ *kingpin.ParseContext) error { } func (r *RuleCommand) printRules(_ *kingpin.ParseContext) error { + if err := r.setupClient(); err != nil { + return err + } rules, err := r.cli.ListRules(context.Background(), "") if err != nil { if errors.Is(err, client.ErrResourceNotFound) { @@ -373,6 +379,9 @@ func (r *RuleCommand) printRules(_ *kingpin.ParseContext) error { } func (r *RuleCommand) getRuleGroup(_ *kingpin.ParseContext) error { + if err := r.setupClient(); err != nil { + return err + } group, err := r.cli.GetRuleGroup(context.Background(), r.Namespace, r.RuleGroup) if err != nil { if errors.Is(err, client.ErrResourceNotFound) { @@ -387,6 +396,9 @@ func (r *RuleCommand) getRuleGroup(_ *kingpin.ParseContext) error { } func (r *RuleCommand) deleteRuleGroup(_ *kingpin.ParseContext) error { + if err := r.setupClient(); err != nil { + return err + } err := r.cli.DeleteRuleGroup(context.Background(), r.Namespace, r.RuleGroup) if err != nil && !errors.Is(err, client.ErrResourceNotFound) { log.Fatalf("unable to delete rule group from cortex, %v", err) @@ -395,6 +407,9 @@ func (r *RuleCommand) deleteRuleGroup(_ *kingpin.ParseContext) error { } func (r *RuleCommand) deleteRuleNamespace(_ *kingpin.ParseContext) error { + if err := r.setupClient(); err != nil { + return err + } err := r.cli.DeleteRuleNamespace(context.Background(), r.Namespace) if err != nil && !errors.Is(err, client.ErrResourceNotFound) { log.Fatalf("unable to delete namespace from cortex, %v", err) @@ -403,6 +418,9 @@ func (r *RuleCommand) deleteRuleNamespace(_ *kingpin.ParseContext) error { } func (r *RuleCommand) loadRules(_ *kingpin.ParseContext) error { + if err := r.setupClient(); err != nil { + return err + } nss, err := rules.ParseFiles(r.RuleFilesList, r.getValidationScheme()) if err != nil { return errors.Wrap(err, "load operation unsuccessful, unable to parse rules files") @@ -460,6 +478,9 @@ func (r *RuleCommand) shouldCheckNamespace(namespace string) bool { } func (r *RuleCommand) diffRules(_ *kingpin.ParseContext) error { + if err := r.setupClient(); err != nil { + return err + } err := r.setupFiles() if err != nil { return errors.Wrap(err, "diff operation unsuccessful, unable to load rules files") @@ -523,6 +544,9 @@ func (r *RuleCommand) diffRules(_ *kingpin.ParseContext) error { } func (r *RuleCommand) syncRules(_ *kingpin.ParseContext) error { + if err := r.setupClient(); err != nil { + return err + } err := r.setupFiles() if err != nil { return errors.Wrap(err, "sync operation unsuccessful, unable to load rules files") diff --git a/pkg/commands/rules_test.go b/pkg/commands/rules_test.go index f67a76a7e..80926a0c2 100644 --- a/pkg/commands/rules_test.go +++ b/pkg/commands/rules_test.go @@ -5,6 +5,7 @@ import ( "github.com/prometheus/prometheus/model/rulefmt" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "github.com/cortexproject/cortex-tools/pkg/rules/rwrulefmt" ) @@ -61,3 +62,21 @@ func TestCheckDuplicates(t *testing.T) { }) } } + +// TestSetupClientRequiresAddress verifies that setupClient returns an error when +// no address is configured, while setup (the PreAction) does not. +// This ensures local-only commands (lint, prepare, check) work without a Cortex address. +func TestSetupClientRequiresAddress(t *testing.T) { + r := &RuleCommand{} + + // setupClient should fail when no address is set. + err := r.setupClient() + require.Error(t, err) + assert.Contains(t, err.Error(), "cortex address is required") + + // setupClient should fail when address is set but tenant ID is missing. + r.ClientConfig.Address = "http://cortex:9009" + err = r.setupClient() + require.Error(t, err) + assert.Contains(t, err.Error(), "tenant ID is required") +}