From ed656cf6882fecfc140030b01fd68dba68b20ead Mon Sep 17 00:00:00 2001 From: TRIVENI206 Date: Sat, 4 Apr 2026 00:46:16 +0530 Subject: [PATCH 1/8] fix(nats): derive CloudEvents subject from metadata instead of hardcoded TODO Signed-off-by: TRIVENI206 --- internal/events/nats/natschannel.go | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/internal/events/nats/natschannel.go b/internal/events/nats/natschannel.go index 57460699e2..3e6e54aef7 100644 --- a/internal/events/nats/natschannel.go +++ b/internal/events/nats/natschannel.go @@ -206,7 +206,20 @@ func sendEvent( event.SetID(msg.UUID) event.SetType(eventType) event.SetSource("minder") // The system which generated the event. The Minder URL would be nice here. - event.SetSubject("TODO") // This *should* represent the entity, but we don't have a standard field for it yet. + subject := "" + if val, ok := msg.Metadata["entity_id"]; ok && val != "" { + subject = val + } else if val, ok := msg.Metadata["repository_id"]; ok && val != "" { + subject = val + } else if val, ok := msg.Metadata["artifact_id"]; ok && val != "" { + subject = val + } else if val, ok := msg.Metadata["project_id"]; ok && val != "" { + subject = val + } else { + subject = "minder" + } + + event.SetSubject(subject) // All our current payloads are encoded JSON; we need to unmarshal payload := map[string]any{} From a83bf921f7855016d418c808c781e92c70c11d89 Mon Sep 17 00:00:00 2001 From: TRIVENI206 Date: Mon, 6 Apr 2026 23:56:44 +0530 Subject: [PATCH 2/8] fix(nats): derive CloudEvents subject from entity_id Signed-off-by: TRIVENI206 --- internal/events/nats/natschannel.go | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/internal/events/nats/natschannel.go b/internal/events/nats/natschannel.go index 3e6e54aef7..cba0c45a25 100644 --- a/internal/events/nats/natschannel.go +++ b/internal/events/nats/natschannel.go @@ -207,14 +207,9 @@ func sendEvent( event.SetType(eventType) event.SetSource("minder") // The system which generated the event. The Minder URL would be nice here. subject := "" + if val, ok := msg.Metadata["entity_id"]; ok && val != "" { subject = val - } else if val, ok := msg.Metadata["repository_id"]; ok && val != "" { - subject = val - } else if val, ok := msg.Metadata["artifact_id"]; ok && val != "" { - subject = val - } else if val, ok := msg.Metadata["project_id"]; ok && val != "" { - subject = val } else { subject = "minder" } From de4b3e316a0b55825e362a0acae2ed4d544e6496 Mon Sep 17 00:00:00 2001 From: Ajay Rajera Date: Tue, 7 Apr 2026 01:31:31 +0530 Subject: [PATCH 3/8] refactor: extract shared label filtering to internal/labels (#6222) * refactor: extract shared label filtering to internal/labels (#4982) * refactor: address review feedback and test warnings * refactor: remove error return from ParseLabelFilter and LabelsFromFilter --- .../controlplane/handlers_profile_test.go | 4 - .../handlers_repositories_test.go | 1 - internal/db/domain.go | 30 ++---- internal/history/models.go | 20 ++-- internal/history/service.go | 4 +- internal/labels/labels.go | 54 +++++++++++ internal/labels/labels_test.go | 94 +++++++++++++++++++ 7 files changed, 167 insertions(+), 40 deletions(-) create mode 100644 internal/labels/labels.go create mode 100644 internal/labels/labels_test.go diff --git a/internal/controlplane/handlers_profile_test.go b/internal/controlplane/handlers_profile_test.go index e6eec5abfb..5ab053ec1d 100644 --- a/internal/controlplane/handlers_profile_test.go +++ b/internal/controlplane/handlers_profile_test.go @@ -1051,10 +1051,6 @@ func TestPatchProfile(t *testing.T) { Name: tc.baseProfile.GetProfile().GetName(), ProjectID: dbproj.ID, }) - if err != nil { - t.Fatalf("Error getting profile: %v", err) - } - if err != nil { t.Fatalf("Error getting profile: %v", err) } diff --git a/internal/controlplane/handlers_repositories_test.go b/internal/controlplane/handlers_repositories_test.go index 26abe50a8b..ce7f31e8ae 100644 --- a/internal/controlplane/handlers_repositories_test.go +++ b/internal/controlplane/handlers_repositories_test.go @@ -531,7 +531,6 @@ const ( remoteRepoId int64 = 123456 repoName2 = "another-repo" remoteRepoId2 int64 = 234567 - accessToken = "TOKEN" ) var ( diff --git a/internal/db/domain.go b/internal/db/domain.go index 8a54e89ee5..8cdb712682 100644 --- a/internal/db/domain.go +++ b/internal/db/domain.go @@ -6,9 +6,10 @@ package db import ( "slices" - "strings" "github.com/sqlc-dev/pqtype" + + "github.com/mindersec/minder/internal/labels" ) // This file contains domain-level methods for db structs @@ -48,27 +49,8 @@ func (r ListProfilesByProjectIDAndLabelRow) GetSelectors() []ProfileSelector { // LabelsFromFilter parses the filter string and populates the IncludeLabels and ExcludeLabels fields func (lp *ListProfilesByProjectIDAndLabelParams) LabelsFromFilter(filter string) { - // If s does not contain sep and sep is not empty, Split returns a - // slice of length 1 whose only element is s. Work around that by - // returning early if filter is empty. - if filter == "" { - return - } - - var starMatched bool - for _, label := range strings.Split(filter, ",") { - switch { - case label == "*": - starMatched = true - case strings.HasPrefix(label, "!"): - // if the label starts with a "!", it is a negative filter, add it to the negative list - lp.ExcludeLabels = append(lp.ExcludeLabels, label[1:]) - default: - lp.IncludeLabels = append(lp.IncludeLabels, label) - } - } - - if starMatched { - lp.IncludeLabels = []string{"*"} - } + inc, exc := labels.ParseLabelFilter(filter) + lp.IncludeLabels = inc + lp.ExcludeLabels = exc } + diff --git a/internal/history/models.go b/internal/history/models.go index 9638bada61..099a00bddd 100644 --- a/internal/history/models.go +++ b/internal/history/models.go @@ -16,6 +16,7 @@ import ( "github.com/mindersec/minder/internal/db" em "github.com/mindersec/minder/internal/entities/models" + "github.com/mindersec/minder/internal/labels" ) var ( @@ -375,22 +376,21 @@ func (filter *listEvaluationFilter) ExcludedProfileNames() []string { } func (filter *listEvaluationFilter) AddLabel(label string) error { - if label == "!*" { - return fmt.Errorf("%w: label", ErrInvalidIdentifier) - } - if label == "*" && len(filter.includedLabels) != 0 { - return fmt.Errorf("%w: label", ErrInvalidIdentifier) + inc, exc := labels.ParseLabel(label) + + if inc != "" { + filter.includedLabels = append(filter.includedLabels, inc) } - if strings.HasPrefix(label, "!") { - label = strings.Split(label, "!")[1] // guaranteed to exist - filter.excludedLabels = append(filter.excludedLabels, label) - } else { - filter.includedLabels = append(filter.includedLabels, label) + if exc != "" { + filter.excludedLabels = append(filter.excludedLabels, exc) } return nil } func (filter *listEvaluationFilter) IncludedLabels() []string { + if slices.Contains(filter.includedLabels, "*") { + return []string{"*"} + } return filter.includedLabels } func (filter *listEvaluationFilter) ExcludedLabels() []string { diff --git a/internal/history/service.go b/internal/history/service.go index b18f426c0d..652ab3a0c5 100644 --- a/internal/history/service.go +++ b/internal/history/service.go @@ -398,7 +398,9 @@ func paramsFromLabelFilter( if len(filter.IncludedLabels()) != 0 { params.Labels = filter.IncludedLabels() } - // We do not exclude based on labels + if len(filter.ExcludedLabels()) != 0 { + params.Notlabels = filter.ExcludedLabels() + } return nil } diff --git a/internal/labels/labels.go b/internal/labels/labels.go new file mode 100644 index 0000000000..b25f02b542 --- /dev/null +++ b/internal/labels/labels.go @@ -0,0 +1,54 @@ +// SPDX-FileCopyrightText: Copyright 2026 The Minder Authors +// SPDX-License-Identifier: Apache-2.0 + +// Package labels contains logic for parsing label filters. +package labels + +import ( + "strings" +) + +// ParseLabelFilter parses a comma-separated label filter string into lists of +// labels to include and exclude. It resolves wildcards so that if any inclusion +// rule is `*`, the included labels list evaluates simply to `["*"]`. +func ParseLabelFilter(filter string) (include []string, exclude []string) { + if filter == "" { + return nil, nil + } + + var starMatched bool + for _, label := range strings.Split(filter, ",") { + label = strings.TrimSpace(label) + if label == "" { + continue + } + + inc, exc := ParseLabel(label) + + if inc != "" { + if inc == "*" { + starMatched = true + } else { + include = append(include, inc) + } + } + if exc != "" { + exclude = append(exclude, exc) + } + } + + if starMatched { + include = []string{"*"} + } + + return include, exclude +} + +// ParseLabel parses a single label (without commas) into an include or exclude string. +// Returns the include label (if any) and the exclude label (if any). +func ParseLabel(label string) (include string, exclude string) { + if strings.HasPrefix(label, "!") { + return "", strings.TrimPrefix(label, "!") + } + return label, "" +} diff --git a/internal/labels/labels_test.go b/internal/labels/labels_test.go new file mode 100644 index 0000000000..0fd03c0bde --- /dev/null +++ b/internal/labels/labels_test.go @@ -0,0 +1,94 @@ +// SPDX-FileCopyrightText: Copyright 2026 The Minder Authors +// SPDX-License-Identifier: Apache-2.0 + +package labels + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestParseLabelFilter(t *testing.T) { + t.Parallel() + tests := []struct { + name string + filter string + expectedInc []string + expectedExc []string + }{ + { + name: "empty", + filter: "", + }, + { + name: "single include", + filter: "foo", + expectedInc: []string{"foo"}, + }, + { + name: "single exclude", + filter: "!foo", + expectedExc: []string{"foo"}, + }, + { + name: "star include", + filter: "*", + expectedInc: []string{"*"}, + }, + { + name: "star exclude", + filter: "!*", + expectedExc: []string{"*"}, + }, + { + name: "multiple includes", + filter: "foo,bar", + expectedInc: []string{"foo", "bar"}, + }, + { + name: "includes and excludes", + filter: "foo,!bar,baz,!qux", + expectedInc: []string{"foo", "baz"}, + expectedExc: []string{"bar", "qux"}, + }, + { + name: "star mixed with includes", + filter: "foo,*", + expectedInc: []string{"*"}, + }, + { + name: "includes mixed with star", + filter: "*,foo", + expectedInc: []string{"*"}, + }, + { + name: "star and excludes", + filter: "*,!foo", + expectedInc: []string{"*"}, + expectedExc: []string{"foo"}, + }, + { + name: "whitespace handling", + filter: " foo , !bar ", + expectedInc: []string{"foo"}, + expectedExc: []string{"bar"}, + }, + { + name: "trailing commas", + filter: "foo,,!bar,", + expectedInc: []string{"foo"}, + expectedExc: []string{"bar"}, + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + inc, exc := ParseLabelFilter(tt.filter) + require.Equal(t, tt.expectedInc, inc) + require.Equal(t, tt.expectedExc, exc) + }) + } +} From bc5344d2962135576a6637fb6dc3012c010202e1 Mon Sep 17 00:00:00 2001 From: Dharun Date: Tue, 7 Apr 2026 02:11:11 +0530 Subject: [PATCH 4/8] Duplicate repo name (#6276) * fixed duplicate repo name in cli after filter Signed-off-by: DharunMR * fixed duplicate repo name in cli after filter Signed-off-by: DharunMR --------- Signed-off-by: DharunMR --- cmd/cli/app/repo/repo_register.go | 4 ---- internal/util/cli/multi_select.go | 14 ++++++++++++-- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/cmd/cli/app/repo/repo_register.go b/cmd/cli/app/repo/repo_register.go index 932e2fe60b..57cbefd0fb 100644 --- a/cmd/cli/app/repo/repo_register.go +++ b/cmd/cli/app/repo/repo_register.go @@ -34,10 +34,6 @@ var repoRegisterCmd = &cobra.Command{ return fmt.Errorf("cannot use --name and --all together") } - if len(inputRepoList) == 0 && !registerAll { - return fmt.Errorf("must provide either --name or --all") - } - return nil }, diff --git a/internal/util/cli/multi_select.go b/internal/util/cli/multi_select.go index d56d75149e..d27e8b70d6 100644 --- a/internal/util/cli/multi_select.go +++ b/internal/util/cli/multi_select.go @@ -122,9 +122,19 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { case "ctrl+c", "enter": return m, tea.Quit case " ": - idx := m.list.Index() oldItem := m.list.SelectedItem().(item) - cmd := m.list.SetItem(idx, item{ + + // find absolute index in the underlying list + var absoluteIdx int + for i, listItem := range m.list.Items() { + if listItem.(item).title == oldItem.title { + absoluteIdx = i + break + } + } + + // use absolute index to update item + cmd := m.list.SetItem(absoluteIdx, item{ title: oldItem.title, checked: !oldItem.checked, }) From e875b00fc71be5c5bbc80e4f486b965a90a47cc2 Mon Sep 17 00:00:00 2001 From: Dharun Date: Tue, 7 Apr 2026 12:29:11 +0530 Subject: [PATCH 5/8] Feat : Added a flag to ruletype subcommand (#6287) added a name flag to delete ruletype Signed-off-by: DharunMR --- cmd/cli/app/ruletype/ruletype_delete.go | 40 +++++++++++++++++-------- internal/engine/actions/actions_test.go | 2 +- 2 files changed, 28 insertions(+), 14 deletions(-) diff --git a/cmd/cli/app/ruletype/ruletype_delete.go b/cmd/cli/app/ruletype/ruletype_delete.go index 303d3cc411..e77aafebce 100644 --- a/cmd/cli/app/ruletype/ruletype_delete.go +++ b/cmd/cli/app/ruletype/ruletype_delete.go @@ -34,6 +34,7 @@ func deleteCommand(ctx context.Context, cmd *cobra.Command, _ []string, conn *gr project := viper.GetString("project") id := viper.GetString("id") + name := viper.GetString("name") deleteAll := viper.GetBool("all") yesFlag := viper.GetBool("yes") @@ -55,17 +56,31 @@ func deleteCommand(ctx context.Context, cmd *cobra.Command, _ []string, conn *gr // List of rule types to delete var rulesToDelete []*minderv1.RuleType + if !deleteAll { - // Fetch the rule type from the DB, so we can get its name - rtype, err := client.GetRuleTypeById(ctx, &minderv1.GetRuleTypeByIdRequest{ - Context: &minderv1.Context{Project: &project}, - Id: id, - }) - if err != nil { - return cli.MessageAndError("Error getting rule type", err) + // Fetch the rule type from the DB by either ID or Name + if id != "" { + rtype, err := client.GetRuleTypeById(ctx, &minderv1.GetRuleTypeByIdRequest{ + Context: &minderv1.Context{Project: &project}, + Id: id, + }) + if err != nil { + return cli.MessageAndError("Error getting rule type by id", err) + } + rulesToDelete = append(rulesToDelete, rtype.RuleType) } - // Add the rule type for deletion - rulesToDelete = append(rulesToDelete, rtype.RuleType) + + if name != "" { + rtype, err := client.GetRuleTypeByName(ctx, &minderv1.GetRuleTypeByNameRequest{ + Context: &minderv1.Context{Project: &project}, + Name: name, + }) + if err != nil { + return cli.MessageAndError("Error getting rule type by name", err) + } + rulesToDelete = append(rulesToDelete, rtype.RuleType) + } + } else { // List all rule types resp, err := client.ListRuleTypes(ctx, &minderv1.ListRuleTypesRequest{ @@ -174,11 +189,10 @@ func init() { ruleTypeCmd.AddCommand(deleteCmd) // Flags deleteCmd.Flags().StringP("id", "i", "", "ID of rule type to delete") + deleteCmd.Flags().StringP("name", "n", "", "Name of rule type to delete") deleteCmd.Flags().BoolP("all", "a", false, "Warning: Deletes all rule types") deleteCmd.Flags().BoolP("yes", "y", false, "Bypass yes/no prompt when deleting all rule types") - // TODO: add a flag for the rule type name // Exclusive - deleteCmd.MarkFlagsOneRequired("id", "all") - deleteCmd.MarkFlagsMutuallyExclusive("id", "all") - + deleteCmd.MarkFlagsOneRequired("id", "name", "all") + deleteCmd.MarkFlagsMutuallyExclusive("id", "name", "all") } diff --git a/internal/engine/actions/actions_test.go b/internal/engine/actions/actions_test.go index 7d9dc8c90d..17ed1d7283 100644 --- a/internal/engine/actions/actions_test.go +++ b/internal/engine/actions/actions_test.go @@ -11,8 +11,8 @@ import ( "github.com/mindersec/minder/internal/db" "github.com/mindersec/minder/internal/engine/actions/remediate/pull_request" - enginerr "github.com/mindersec/minder/internal/engine/errors" engif "github.com/mindersec/minder/internal/engine/interfaces" + enginerr "github.com/mindersec/minder/pkg/engine/errors" ) func TestShouldRemediate(t *testing.T) { From 2b9b45bdea5b451f15839f3aeda2569573dfe47f Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 7 Apr 2026 00:05:09 -0700 Subject: [PATCH 6/8] Auto-generated cli documentation update - 2026-04-06 23:59:11 (#6296) Update documentation Co-authored-by: evankanderson --- docs/docs/ref/cli/minder_ruletype_delete.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/docs/docs/ref/cli/minder_ruletype_delete.md b/docs/docs/ref/cli/minder_ruletype_delete.md index 76d38ac501..b6d79b99fe 100644 --- a/docs/docs/ref/cli/minder_ruletype_delete.md +++ b/docs/docs/ref/cli/minder_ruletype_delete.md @@ -16,10 +16,11 @@ minder ruletype delete [flags] ### Options ``` - -a, --all Warning: Deletes all rule types - -h, --help help for delete - -i, --id string ID of rule type to delete - -y, --yes Bypass yes/no prompt when deleting all rule types + -a, --all Warning: Deletes all rule types + -h, --help help for delete + -i, --id string ID of rule type to delete + -n, --name string Name of rule type to delete + -y, --yes Bypass yes/no prompt when deleting all rule types ``` ### Options inherited from parent commands From f1c2b69e4c6170c666b885daff3d02ea24ee1a81 Mon Sep 17 00:00:00 2001 From: TRIVENI206 Date: Tue, 7 Apr 2026 14:54:11 +0530 Subject: [PATCH 7/8] fix: address lint and test issues Signed-off-by: TRIVENI206 --- internal/engine/actions/actions_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/internal/engine/actions/actions_test.go b/internal/engine/actions/actions_test.go index 17ed1d7283..75959953be 100644 --- a/internal/engine/actions/actions_test.go +++ b/internal/engine/actions/actions_test.go @@ -15,6 +15,7 @@ import ( enginerr "github.com/mindersec/minder/pkg/engine/errors" ) + func TestShouldRemediate(t *testing.T) { t.Parallel() From a1912a86bce30b55a51babdc9c4cdcc43b94c3ff Mon Sep 17 00:00:00 2001 From: TRIVENI206 Date: Thu, 9 Apr 2026 19:48:15 +0530 Subject: [PATCH 8/8] fix: revert unintended changes from bad merge Signed-off-by: TRIVENI206 --- internal/db/domain.go | 1 - internal/engine/actions/actions_test.go | 1 - 2 files changed, 2 deletions(-) diff --git a/internal/db/domain.go b/internal/db/domain.go index 8cdb712682..c4685a1933 100644 --- a/internal/db/domain.go +++ b/internal/db/domain.go @@ -53,4 +53,3 @@ func (lp *ListProfilesByProjectIDAndLabelParams) LabelsFromFilter(filter string) lp.IncludeLabels = inc lp.ExcludeLabels = exc } - diff --git a/internal/engine/actions/actions_test.go b/internal/engine/actions/actions_test.go index 75959953be..17ed1d7283 100644 --- a/internal/engine/actions/actions_test.go +++ b/internal/engine/actions/actions_test.go @@ -15,7 +15,6 @@ import ( enginerr "github.com/mindersec/minder/pkg/engine/errors" ) - func TestShouldRemediate(t *testing.T) { t.Parallel()