diff --git a/config/crds/troubleshoot.sh_analyzers.yaml b/config/crds/troubleshoot.sh_analyzers.yaml index c18eff2e8..680e60d6d 100644 --- a/config/crds/troubleshoot.sh_analyzers.yaml +++ b/config/crds/troubleshoot.sh_analyzers.yaml @@ -559,6 +559,65 @@ spec: required: - outcomes type: object + event: + properties: + annotations: + additionalProperties: + type: string + type: object + checkName: + type: string + collectorName: + type: string + exclude: + type: BoolString + kind: + type: string + namespace: + type: string + outcomes: + items: + properties: + fail: + properties: + message: + type: string + uri: + type: string + when: + type: string + type: object + pass: + properties: + message: + type: string + uri: + type: string + when: + type: string + type: object + warn: + properties: + message: + type: string + uri: + type: string + when: + type: string + type: object + type: object + type: array + reason: + type: string + regex: + type: string + strict: + type: BoolString + required: + - collectorName + - outcomes + - reason + type: object goldpinger: properties: annotations: diff --git a/config/crds/troubleshoot.sh_preflights.yaml b/config/crds/troubleshoot.sh_preflights.yaml index 596fe4b1f..1663d2026 100644 --- a/config/crds/troubleshoot.sh_preflights.yaml +++ b/config/crds/troubleshoot.sh_preflights.yaml @@ -559,6 +559,65 @@ spec: required: - outcomes type: object + event: + properties: + annotations: + additionalProperties: + type: string + type: object + checkName: + type: string + collectorName: + type: string + exclude: + type: BoolString + kind: + type: string + namespace: + type: string + outcomes: + items: + properties: + fail: + properties: + message: + type: string + uri: + type: string + when: + type: string + type: object + pass: + properties: + message: + type: string + uri: + type: string + when: + type: string + type: object + warn: + properties: + message: + type: string + uri: + type: string + when: + type: string + type: object + type: object + type: array + reason: + type: string + regex: + type: string + strict: + type: BoolString + required: + - collectorName + - outcomes + - reason + type: object goldpinger: properties: annotations: diff --git a/config/crds/troubleshoot.sh_supportbundles.yaml b/config/crds/troubleshoot.sh_supportbundles.yaml index 69c4319f0..6e2e66ac8 100644 --- a/config/crds/troubleshoot.sh_supportbundles.yaml +++ b/config/crds/troubleshoot.sh_supportbundles.yaml @@ -590,6 +590,65 @@ spec: required: - outcomes type: object + event: + properties: + annotations: + additionalProperties: + type: string + type: object + checkName: + type: string + collectorName: + type: string + exclude: + type: BoolString + kind: + type: string + namespace: + type: string + outcomes: + items: + properties: + fail: + properties: + message: + type: string + uri: + type: string + when: + type: string + type: object + pass: + properties: + message: + type: string + uri: + type: string + when: + type: string + type: object + warn: + properties: + message: + type: string + uri: + type: string + when: + type: string + type: object + type: object + type: array + reason: + type: string + regex: + type: string + strict: + type: BoolString + required: + - collectorName + - outcomes + - reason + type: object goldpinger: properties: annotations: diff --git a/pkg/analyze/analyzer.go b/pkg/analyze/analyzer.go index e54d4f3b6..62a9dd2ba 100644 --- a/pkg/analyze/analyzer.go +++ b/pkg/analyze/analyzer.go @@ -246,6 +246,8 @@ func getAnalyzer(analyzer *troubleshootv1beta2.Analyze) Analyzer { return &AnalyzeCertificates{analyzer: analyzer.Certificates} case analyzer.Goldpinger != nil: return &AnalyzeGoldpinger{analyzer: analyzer.Goldpinger} + case analyzer.Event != nil: + return &AnalyzeEvent{analyzer: analyzer.Event} default: return nil } diff --git a/pkg/analyze/event.go b/pkg/analyze/event.go new file mode 100644 index 000000000..20a27eddb --- /dev/null +++ b/pkg/analyze/event.go @@ -0,0 +1,218 @@ +package analyzer + +import ( + "bytes" + "encoding/json" + "fmt" + "path" + "regexp" + "strconv" + "strings" + "text/template" + + "github.com/pkg/errors" + troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2" + "github.com/replicatedhq/troubleshoot/pkg/constants" + corev1 "k8s.io/api/core/v1" + "k8s.io/klog/v2" +) + +type AnalyzeEvent struct { + analyzer *troubleshootv1beta2.EventAnalyze +} + +type eventFilter struct { + kind string + reason string + msgRegex string +} + +func (a *AnalyzeEvent) Title() string { + if a.analyzer.CheckName != "" { + return a.analyzer.CheckName + } + if a.analyzer.CollectorName != "" { + return a.analyzer.CollectorName + } + return "Event" +} + +func (a *AnalyzeEvent) IsExcluded() (bool, error) { + return isExcluded(a.analyzer.Exclude) +} + +func (a *AnalyzeEvent) Analyze(getFile getCollectedFileContents, findFiles getChildCollectedFileContents) ([]*AnalyzeResult, error) { + // required check + if a.analyzer.Reason == "" { + return nil, errors.New("reason is required") + } + + // read collected events based on namespace + namespace := getNamespace(a.analyzer.Namespace) + fullPath := path.Join(constants.CLUSTER_RESOURCES_DIR, constants.CLUSTER_RESOURCES_EVENTS, namespace) + fullPath = fmt.Sprintf("%s.json", fullPath) + fileContent, err := getFile(fullPath) + if err != nil { + return nil, errors.Wrapf(err, "failed to read collected events for namespace: %s", namespace) + } + + events, err := convertToEventList(fileContent) + if err != nil { + return nil, errors.Wrap(err, "failed to read collected events") + } + + // filter if there's single event matched with the given criteria + // match: Reason && Kind (optional) && MessageRegex (optional) + // e.g. Reason: Unhealthy. Kind: Pod. Message: Readiness probe failed:... + event := getEvent(events, eventFilter{ + kind: a.analyzer.Kind, + reason: a.analyzer.Reason, + msgRegex: a.analyzer.RegexPattern, + }) + + return analyzeEventResult(event, a.analyzer.Outcomes, a.Title()) + +} + +func getNamespace(namespace string) string { + if namespace == "" { + return corev1.NamespaceDefault + } + return namespace +} + +func convertToEventList(data []byte) (*corev1.EventList, error) { + var eventList corev1.EventList + err := json.Unmarshal(data, &eventList) + if err != nil { + return nil, fmt.Errorf("failed to convert []byte to corev1.EventList: %w", err) + } + return &eventList, nil +} + +func getEvent(events *corev1.EventList, filter eventFilter) *corev1.Event { + var ( + re *regexp.Regexp + errParseRegex error + ) + + if filter.msgRegex != "" { + re, errParseRegex = regexp.Compile(filter.msgRegex) + if errParseRegex != nil { + klog.V(2).Infof("failed to read message regex: %v", errParseRegex) + return nil + } + } + + for _, event := range events.Items { + if !matchReason(event.Reason, filter.reason) { + continue + } + if !matchKind(event.InvolvedObject.Kind, filter.kind) { + continue + } + if re == nil || re.MatchString(event.Message) { + klog.V(2).Infof("event matched: %v for reason: %s kind: %s messageRegex: %s ", event, filter.reason, filter.kind, filter.msgRegex) + return &event + } + } + return nil +} + +func matchReason(actual, expected string) bool { + // not possible to have empty reason + if expected == "" { + return false + } + return strings.EqualFold(actual, expected) +} + +func matchKind(actual, expected string) bool { + // kind is optional + if expected == "" { + return true + } + return strings.EqualFold(actual, expected) +} + +func analyzeEventResult(event *corev1.Event, outcomes []*troubleshootv1beta2.Outcome, checkName string) ([]*AnalyzeResult, error) { + + results := []*AnalyzeResult{} + + // for now, only support single outcome + // we will return when there's a matched event + willReturn := event != nil + + result := &AnalyzeResult{ + Title: checkName, + IconKey: "kubernetes_event", + IconURI: "https://troubleshoot.sh/images/analyzer-icons/kubernetes.svg?w=16&h=16", + } + + for _, o := range outcomes { + if o.Fail != nil { + toReturn, err := strconv.ParseBool(o.Fail.When) + if err != nil { + return nil, errors.Wrapf(err, "failed to parse when condition: %s", o.Fail.When) + } + if toReturn == willReturn { + result.IsFail = true + result.Message = decorateMessage(o.Fail.Message, event) + result.URI = o.Fail.URI + break + } + } + + if o.Warn != nil { + toReturn, err := strconv.ParseBool(o.Warn.When) + if err != nil { + return nil, errors.Wrapf(err, "failed to parse when condition: %s", o.Warn.When) + } + if toReturn == willReturn { + result.IsWarn = true + result.Message = decorateMessage(o.Warn.Message, event) + result.URI = o.Warn.URI + break + } + } + + if o.Pass != nil { + toReturn, err := strconv.ParseBool(o.Pass.When) + if err != nil { + return nil, errors.Wrapf(err, "failed to parse when condition: %s", o.Pass.When) + } + if toReturn == willReturn { + result.IsPass = true + result.Message = decorateMessage(o.Pass.Message, event) + result.URI = o.Pass.URI + break + } + } + + } + results = append(results, result) + return results, nil +} + +func decorateMessage(message string, event *corev1.Event) string { + if event == nil { + return message + } + out := fmt.Sprintf("Event matched. Reason: %s Name: %s Message: %s", event.Reason, event.InvolvedObject.Name, event.Message) + + tmpl := template.New("event") + msgTmpl, err := tmpl.Parse(message) + if err != nil { + klog.V(2).Infof("failed to parse message template: %v", err) + return out + } + + var m bytes.Buffer + err = msgTmpl.Execute(&m, event) + if err != nil { + klog.V(2).Infof("failed to render message template: %v", err) + return out + } + + return strings.TrimSpace(m.String()) +} diff --git a/pkg/analyze/event_test.go b/pkg/analyze/event_test.go new file mode 100644 index 000000000..8f7c03138 --- /dev/null +++ b/pkg/analyze/event_test.go @@ -0,0 +1,310 @@ +package analyzer + +import ( + "testing" + + "github.com/pkg/errors" + troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2" + "github.com/stretchr/testify/require" + corev1 "k8s.io/api/core/v1" +) + +func TestAnalyzeEvent(t *testing.T) { + tests := []struct { + name string + analyzer troubleshootv1beta2.EventAnalyze + expectResult []AnalyzeResult + files map[string][]byte + err error + }{ + { + name: "reason is required", + analyzer: troubleshootv1beta2.EventAnalyze{ + CollectorName: "event-collector-0", + Kind: "Pod", + Namespace: "default", + Outcomes: []*troubleshootv1beta2.Outcome{ + { + Fail: &troubleshootv1beta2.SingleOutcome{ + When: "true", + Message: "test", + }, + }, + }, + }, + err: errors.New("reason is required"), + files: map[string][]byte{ + "cluster-resources/events/default.json": []byte(` + { + "kind": "EventList", + "apiVersion": "v1", + "metadata": { + "resourceVersion": "722" + }, + "items": [ + { + "kind": "Event", + "apiVersion": "v1", + "metadata": { + "name": "nginx-rc", + "namespace": "default", + "creationTimestamp": "2022-01-01T00:00:00Z" + }, + "involvedObject": { + "kind": "Pod", + "name": "nginx-rc-12345", + "namespace": "default" + }, + "reason": "OOMKilled", + "message": "The container was killed due to an out-of-memory condition.", + "type": "Warning" + } + ] + } + `), + }, + }, + { + name: "fail when OOMKilled event is present", + analyzer: troubleshootv1beta2.EventAnalyze{ + CollectorName: "event-collector-1", + Kind: "Pod", + Namespace: "default", + Reason: "OOMKilled", + Outcomes: []*troubleshootv1beta2.Outcome{ + { + Fail: &troubleshootv1beta2.SingleOutcome{ + When: "true", + Message: "Detect OOMKilled event with {{ .InvolvedObject.Kind }}-{{ .InvolvedObject.Name }} with message {{ .Message }}", + }, + Pass: &troubleshootv1beta2.SingleOutcome{ + When: "false", + Message: "No OOMKilled event detected", + }, + }, + }, + }, + expectResult: []AnalyzeResult{ + { + Title: "event-collector-1", + IconKey: "kubernetes_event", + IconURI: "https://troubleshoot.sh/images/analyzer-icons/kubernetes.svg?w=16&h=16", + IsFail: true, + IsWarn: false, + IsPass: false, + Message: "Detect OOMKilled event with Pod-nginx-rc-12345 with message The container was killed due to an out-of-memory condition.", + }, + }, + files: map[string][]byte{ + "cluster-resources/events/default.json": []byte(` + { + "kind": "EventList", + "apiVersion": "v1", + "metadata": { + "resourceVersion": "722" + }, + "items": [ + { + "kind": "Event", + "apiVersion": "v1", + "metadata": { + "name": "nginx-rc", + "namespace": "default", + "creationTimestamp": "2022-01-01T00:00:00Z" + }, + "involvedObject": { + "kind": "Pod", + "name": "nginx-rc-12345", + "namespace": "default" + }, + "reason": "OOMKilled", + "message": "The container was killed due to an out-of-memory condition.", + "type": "Warning" + } + ] + } + `), + }, + }, + { + name: "pass when no FailedMount event is present", + analyzer: troubleshootv1beta2.EventAnalyze{ + CollectorName: "event-collector-2", + Kind: "Pod", + Namespace: "default", + Reason: "FailedMount", + Outcomes: []*troubleshootv1beta2.Outcome{ + { + Pass: &troubleshootv1beta2.SingleOutcome{ + When: "false", + Message: "No FailedMount event detected", + }, + }, + }, + }, + expectResult: []AnalyzeResult{ + { + Title: "event-collector-2", + IconKey: "kubernetes_event", + IconURI: "https://troubleshoot.sh/images/analyzer-icons/kubernetes.svg?w=16&h=16", + IsFail: false, + IsWarn: false, + IsPass: true, + Message: "No FailedMount event detected", + }, + }, + files: map[string][]byte{ + "cluster-resources/events/default.json": []byte(` + { + "kind": "EventList", + "apiVersion": "v1", + "metadata": { + "resourceVersion": "722" + }, + "items": [ + { + "kind": "Event", + "apiVersion": "v1", + "metadata": { + "name": "nginx-rc-1", + "namespace": "default", + "creationTimestamp": "2022-01-01T00:00:00Z" + }, + "involvedObject": { + "kind": "Pod", + "name": "nginx-rc-12345", + "namespace": "default" + }, + "reason": "Created", + "message": "Created container", + "type": "Normal" + }, + { + "kind": "Event", + "apiVersion": "v1", + "metadata": { + "name": "nginx-rc-2", + "namespace": "default", + "creationTimestamp": "2022-01-01T00:00:00Z" + }, + "involvedObject": { + "kind": "Pod", + "name": "nginx-rc-67890", + "namespace": "default" + }, + "reason": "Started", + "message": "Started container", + "type": "Normal" + } + ] + } + `), + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + req := require.New(t) + + getFile := func(n string) ([]byte, error) { + if b, ok := test.files[n]; ok { + return b, nil + } + return nil, errors.New("file not found") + } + + findFiles := func(n string, _ []string) (map[string][]byte, error) { + return nil, errors.New("method not implemented") + } + + a := &AnalyzeEvent{ + analyzer: &test.analyzer, + } + actual, err := a.Analyze(getFile, findFiles) + if test.err != nil { + req.EqualError(err, test.err.Error()) + return + } + + req.NoError(err) + unPointered := []AnalyzeResult{} + for _, v := range actual { + unPointered = append(unPointered, *v) + } + req.ElementsMatch(test.expectResult, unPointered) + }) + } +} + +func TestAnalyzeEventResult(t *testing.T) { + event := &corev1.Event{ + InvolvedObject: corev1.ObjectReference{ + Kind: "Pod", + Name: "foo-pod", + }, + Reason: "Unhealthy", + Message: "foo-message", + Type: "Warning", + } + + outcomes := []*troubleshootv1beta2.Outcome{ + { + Fail: &troubleshootv1beta2.SingleOutcome{ + When: "true", + Message: "No unhealthy pods allowed", + }, + Pass: &troubleshootv1beta2.SingleOutcome{ + When: "false", + Message: "No unhealthy pod detected", + }, + }, + } + + checkName := "Test Event" + + expectedResults := []*AnalyzeResult{ + { + Title: "Test Event", + IconKey: "kubernetes_event", + IconURI: "https://troubleshoot.sh/images/analyzer-icons/kubernetes.svg?w=16&h=16", + IsFail: true, + IsWarn: false, + IsPass: false, + Message: "No unhealthy pods allowed", + }, + } + + results, err := analyzeEventResult(event, outcomes, checkName) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + if len(results) != len(expectedResults) { + t.Fatalf("unexpected number of results, got %d, want %d", len(results), len(expectedResults)) + } + + for i, result := range results { + expectedResult := expectedResults[i] + + if result.Title != expectedResult.Title { + t.Errorf("unexpected title, got %s, want %s", result.Title, expectedResult.Title) + } + + if result.IsFail != expectedResult.IsFail { + t.Errorf("unexpected IsFail value, got %v, want %v", result.IsFail, expectedResult.IsFail) + } + + if result.IsWarn != expectedResult.IsWarn { + t.Errorf("unexpected IsWarn value, got %v, want %v", result.IsWarn, expectedResult.IsWarn) + } + + if result.IsPass != expectedResult.IsPass { + t.Errorf("unexpected IsPass value, got %v, want %v", result.IsPass, expectedResult.IsPass) + } + + if result.Message != expectedResult.Message { + t.Errorf("unexpected message, got %s, want %s", result.Message, expectedResult.Message) + } + } +} diff --git a/pkg/apis/troubleshoot/v1beta2/analyzer_shared.go b/pkg/apis/troubleshoot/v1beta2/analyzer_shared.go index 8fc62d8bf..0ecfd7a2e 100644 --- a/pkg/apis/troubleshoot/v1beta2/analyzer_shared.go +++ b/pkg/apis/troubleshoot/v1beta2/analyzer_shared.go @@ -232,6 +232,16 @@ type GoldpingerAnalyze struct { FilePath string `json:"filePath,omitempty" yaml:"filePath,omitempty"` } +type EventAnalyze struct { + AnalyzeMeta `json:",inline" yaml:",inline"` + CollectorName string `json:"collectorName" yaml:"collectorName"` + Namespace string `json:"namespace,omitempty" yaml:"namespace,omitempty"` + Kind string `json:"kind,omitempty" yaml:"kind,omitempty"` + Reason string `json:"reason" yaml:"reason"` + RegexPattern string `json:"regex,omitempty" yaml:"regex,omitempty"` + Outcomes []*Outcome `json:"outcomes" yaml:"outcomes"` +} + type Analyze struct { ClusterVersion *ClusterVersion `json:"clusterVersion,omitempty" yaml:"clusterVersion,omitempty"` StorageClass *StorageClass `json:"storageClass,omitempty" yaml:"storageClass,omitempty"` @@ -264,4 +274,5 @@ type Analyze struct { ClusterResource *ClusterResource `json:"clusterResource,omitempty" yaml:"clusterResource,omitempty"` Certificates *CertificatesAnalyze `json:"certificates,omitempty" yaml:"certificates,omitempty"` Goldpinger *GoldpingerAnalyze `json:"goldpinger,omitempty" yaml:"goldpinger,omitempty"` + Event *EventAnalyze `json:"event,omitempty" yaml:"event,omitempty"` } diff --git a/pkg/apis/troubleshoot/v1beta2/zz_generated.deepcopy.go b/pkg/apis/troubleshoot/v1beta2/zz_generated.deepcopy.go index 274285b53..7c8c03240 100644 --- a/pkg/apis/troubleshoot/v1beta2/zz_generated.deepcopy.go +++ b/pkg/apis/troubleshoot/v1beta2/zz_generated.deepcopy.go @@ -209,6 +209,11 @@ func (in *Analyze) DeepCopyInto(out *Analyze) { *out = new(GoldpingerAnalyze) (*in).DeepCopyInto(*out) } + if in.Event != nil { + in, out := &in.Event, &out.Event + *out = new(EventAnalyze) + (*in).DeepCopyInto(*out) + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Analyze. @@ -1412,6 +1417,33 @@ func (in *Distribution) DeepCopy() *Distribution { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *EventAnalyze) DeepCopyInto(out *EventAnalyze) { + *out = *in + in.AnalyzeMeta.DeepCopyInto(&out.AnalyzeMeta) + if in.Outcomes != nil { + in, out := &in.Outcomes, &out.Outcomes + *out = make([]*Outcome, len(*in)) + for i := range *in { + if (*in)[i] != nil { + in, out := &(*in)[i], &(*out)[i] + *out = new(Outcome) + (*in).DeepCopyInto(*out) + } + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EventAnalyze. +func (in *EventAnalyze) DeepCopy() *EventAnalyze { + if in == nil { + return nil + } + out := new(EventAnalyze) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Exec) DeepCopyInto(out *Exec) { *out = *in diff --git a/schemas/analyzer-troubleshoot-v1beta2.json b/schemas/analyzer-troubleshoot-v1beta2.json index f9f12b937..07cdce604 100644 --- a/schemas/analyzer-troubleshoot-v1beta2.json +++ b/schemas/analyzer-troubleshoot-v1beta2.json @@ -824,6 +824,96 @@ } } }, + "event": { + "type": "object", + "required": [ + "collectorName", + "outcomes", + "reason" + ], + "properties": { + "annotations": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "checkName": { + "type": "string" + }, + "collectorName": { + "type": "string" + }, + "exclude": { + "oneOf": [{"type": "string"},{"type": "boolean"}] + }, + "kind": { + "type": "string" + }, + "namespace": { + "type": "string" + }, + "outcomes": { + "type": "array", + "items": { + "type": "object", + "properties": { + "fail": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "uri": { + "type": "string" + }, + "when": { + "type": "string" + } + } + }, + "pass": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "uri": { + "type": "string" + }, + "when": { + "type": "string" + } + } + }, + "warn": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "uri": { + "type": "string" + }, + "when": { + "type": "string" + } + } + } + } + } + }, + "reason": { + "type": "string" + }, + "regex": { + "type": "string" + }, + "strict": { + "oneOf": [{"type": "string"},{"type": "boolean"}] + } + } + }, "goldpinger": { "type": "object", "required": [ diff --git a/schemas/preflight-troubleshoot-v1beta2.json b/schemas/preflight-troubleshoot-v1beta2.json index fed38eacc..aac4e0b5b 100644 --- a/schemas/preflight-troubleshoot-v1beta2.json +++ b/schemas/preflight-troubleshoot-v1beta2.json @@ -824,6 +824,96 @@ } } }, + "event": { + "type": "object", + "required": [ + "collectorName", + "outcomes", + "reason" + ], + "properties": { + "annotations": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "checkName": { + "type": "string" + }, + "collectorName": { + "type": "string" + }, + "exclude": { + "oneOf": [{"type": "string"},{"type": "boolean"}] + }, + "kind": { + "type": "string" + }, + "namespace": { + "type": "string" + }, + "outcomes": { + "type": "array", + "items": { + "type": "object", + "properties": { + "fail": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "uri": { + "type": "string" + }, + "when": { + "type": "string" + } + } + }, + "pass": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "uri": { + "type": "string" + }, + "when": { + "type": "string" + } + } + }, + "warn": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "uri": { + "type": "string" + }, + "when": { + "type": "string" + } + } + } + } + } + }, + "reason": { + "type": "string" + }, + "regex": { + "type": "string" + }, + "strict": { + "oneOf": [{"type": "string"},{"type": "boolean"}] + } + } + }, "goldpinger": { "type": "object", "required": [ diff --git a/schemas/supportbundle-troubleshoot-v1beta2.json b/schemas/supportbundle-troubleshoot-v1beta2.json index 2b34ced86..b7677aa60 100644 --- a/schemas/supportbundle-troubleshoot-v1beta2.json +++ b/schemas/supportbundle-troubleshoot-v1beta2.json @@ -870,6 +870,96 @@ } } }, + "event": { + "type": "object", + "required": [ + "collectorName", + "outcomes", + "reason" + ], + "properties": { + "annotations": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "checkName": { + "type": "string" + }, + "collectorName": { + "type": "string" + }, + "exclude": { + "oneOf": [{"type": "string"},{"type": "boolean"}] + }, + "kind": { + "type": "string" + }, + "namespace": { + "type": "string" + }, + "outcomes": { + "type": "array", + "items": { + "type": "object", + "properties": { + "fail": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "uri": { + "type": "string" + }, + "when": { + "type": "string" + } + } + }, + "pass": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "uri": { + "type": "string" + }, + "when": { + "type": "string" + } + } + }, + "warn": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "uri": { + "type": "string" + }, + "when": { + "type": "string" + } + } + } + } + } + }, + "reason": { + "type": "string" + }, + "regex": { + "type": "string" + }, + "strict": { + "oneOf": [{"type": "string"},{"type": "boolean"}] + } + } + }, "goldpinger": { "type": "object", "required": [