diff --git a/events/hey_test.go b/events/hey_test.go new file mode 100644 index 000000000..ecd3bbfd8 --- /dev/null +++ b/events/hey_test.go @@ -0,0 +1,27 @@ +package events + +import ( + "fmt" + "testing" + + "github.com/flanksource/commons/logger" +) + +func TestLabe(t *testing.T) { + out := []int{1, 2, 3, 4, 5} + in := []int{6, 7, 8, 9, 10} + +OUTER: + for _, o := range out { + logger.Infof("In %d", o) + + for _, i := range in { + if o*i == 14 { + fmt.Println("Continuing to outer") + continue OUTER + } + } + + logger.Infof("Completed %d", o) + } +} diff --git a/events/playbook.go b/events/playbook.go index 78a013cbe..486eb5ae9 100644 --- a/events/playbook.go +++ b/events/playbook.go @@ -144,39 +144,34 @@ func logToJobHistory(ctx *api.Context, playbookID, err string) { // matchResource returns true if any one of the matchFilter is true // for the given labels and cel env. func matchResource(labels map[string]string, celEnv map[string]any, matchFilters []v1.PlaybookEventDetail) (bool, error) { +outer: for _, mf := range matchFilters { - var ( - filterPassed = true - allLabelsMatched = true - ) - if mf.Filter != "" { - filterPassed = false res, err := gomplate.RunTemplate(celEnv, gomplate.Template{Expression: mf.Filter}) if err != nil { return false, err } - filterPassed, _ = strconv.ParseBool(res) + if ok, err := strconv.ParseBool(res); err != nil { + return false, api.Errorf(api.EINVALID, "expression didn't evaluate to a boolean value. got %s", res) + } else if !ok { + continue outer + } } for k, v := range mf.Labels { qVal, ok := labels[k] if !ok { - allLabelsMatched = false - break + continue outer } configuredLabels := strings.Split(v, ",") if !collections.MatchItems(qVal, configuredLabels...) { - allLabelsMatched = false - break + continue outer } } - if filterPassed && allLabelsMatched { - return true, nil - } + return true, nil } return false, nil diff --git a/events/playbook_test.go b/events/playbook_test.go index d309fd1a9..79a35d288 100644 --- a/events/playbook_test.go +++ b/events/playbook_test.go @@ -2,6 +2,7 @@ package events import ( "encoding/json" + "testing" "github.com/flanksource/duty/fixtures/dummy" "github.com/flanksource/duty/models" @@ -94,3 +95,214 @@ var _ = ginkgo.Describe("Should save playbook run on the correct event", ginkgo. Expect(playbook.Status).To(Equal(models.PlaybookRunStatusScheduled)) }) }) + +func Test_matchResource(t *testing.T) { + type args struct { + labels map[string]string + eventResource EventResource + matchFilters []v1.PlaybookEventDetail + } + tests := []struct { + name string + args args + want bool + wantErr bool + }{ + { + name: "With Filter | Without Labels | Match", + args: args{ + labels: map[string]string{ + "telemetry": "enabled", + }, + eventResource: EventResource{ + Component: &models.Component{ + Type: "Entity", + }, + }, + matchFilters: []v1.PlaybookEventDetail{{Filter: "component.type == 'Entity'"}}, + }, + want: true, + wantErr: false, + }, + { + name: "With Filter | Without Labels | No match", + args: args{ + eventResource: EventResource{ + Component: &models.Component{ + Type: "Database", + }, + }, + matchFilters: []v1.PlaybookEventDetail{{Filter: "component.type == 'Entity'"}}, + }, + want: false, + wantErr: false, + }, + { + name: "Without Filter | With Labels | Match", + args: args{ + labels: map[string]string{ + "telemetry": "enabled", + }, + eventResource: EventResource{}, + matchFilters: []v1.PlaybookEventDetail{ + { + Labels: map[string]string{ + "telemetry": "enabled", + }, + }, + }, + }, + want: true, + wantErr: false, + }, + { + name: "Without Filter | With Labels | No match", + args: args{ + labels: map[string]string{ + "telemetry": "enabled", + }, + eventResource: EventResource{}, + matchFilters: []v1.PlaybookEventDetail{ + { + Labels: map[string]string{ + "telemetry": "enabled", + "env": "production", + }, + }, + }, + }, + want: false, + wantErr: false, + }, + { + name: "With Filter | With Labels | match", + args: args{ + labels: map[string]string{ + "telemetry": "enabled", + }, + eventResource: EventResource{ + Check: &models.Check{ + Type: "http", + }, + }, + matchFilters: []v1.PlaybookEventDetail{ + { + Labels: map[string]string{ + "telemetry": "enabled", + }, + Filter: "check.type == 'http'", + }, + }, + }, + want: true, + wantErr: false, + }, + { + name: "With Filter | With Labels | no match", + args: args{ + labels: map[string]string{ + "telemetry": "enabled", + }, + eventResource: EventResource{ + Check: &models.Check{ + Type: "http", + }, + }, + matchFilters: []v1.PlaybookEventDetail{ + { + Labels: map[string]string{ + "telemetry": "enabled", + }, + Filter: "check.type == 'exec'", + }, + }, + }, + want: false, + wantErr: false, + }, + { + name: "With Filter | With Labels | one of the filters match", + args: args{ + labels: map[string]string{ + "telemetry": "enabled", + }, + eventResource: EventResource{ + Check: &models.Check{ + Type: "http", + }, + CheckSummary: &models.CheckSummary{ + Uptime: types.Uptime{ + Failed: 12, + }, + }, + }, + matchFilters: []v1.PlaybookEventDetail{ + { + Labels: map[string]string{ + "telemetry": "enabled", + "env": "production", + }, + }, + {Filter: "check.type == 'http' && check_summary.uptime.failed > 15"}, + {Filter: "check.type == 'http' && check_summary.uptime.failed > 10"}, + }, + }, + want: true, + wantErr: false, + }, + { + name: "Invalid filter expression", + args: args{ + eventResource: EventResource{ + Check: &models.Check{ + Type: "http", + }, + CheckSummary: &models.CheckSummary{ + Uptime: types.Uptime{ + Failed: 12, + }, + }, + }, + matchFilters: []v1.PlaybookEventDetail{ + {Filter: "summary.uptime.failed > 15"}, + }, + }, + want: false, + wantErr: true, + }, + { + name: "Expression not returning boolean", + args: args{ + eventResource: EventResource{ + Check: &models.Check{ + Type: "http", + }, + CheckSummary: &models.CheckSummary{ + Uptime: types.Uptime{ + Failed: 12, + }, + }, + }, + matchFilters: []v1.PlaybookEventDetail{ + {Filter: "check.type"}, + }, + }, + want: false, + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := matchResource(tt.args.labels, tt.args.eventResource.AsMap(), tt.args.matchFilters) + if (err != nil) != tt.wantErr { + t.Errorf("matchResource() error = %v, wantErr %v", err, tt.wantErr) + return + } + + if got != tt.want { + t.Errorf("matchResource() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/fixtures/playbooks/stop-crashloop-pods.yaml b/fixtures/playbooks/stop-crashloop-pods.yaml index 9fade79b9..b5d9ab1ee 100644 --- a/fixtures/playbooks/stop-crashloop-pods.yaml +++ b/fixtures/playbooks/stop-crashloop-pods.yaml @@ -8,7 +8,7 @@ spec: - event: failed labels: alertname: KubePodCrashLoopingcontainer - description: Stop Pods that's on CrashLoop + description: Stop Pods that are on CrashLoop actions: - name: 'Stop pod' exec: