bosun.org@v0.0.0-20210513094433-e25bc3e69a1f/cmd/bosun/sched/views.go (about) 1 package sched 2 3 import ( 4 "fmt" 5 "strings" 6 "time" 7 8 "bosun.org/cmd/bosun/conf" 9 "bosun.org/models" 10 "bosun.org/opentsdb" 11 "github.com/ryanuber/go-glob" 12 ) 13 14 // Views 15 16 type EventSummary struct { 17 Status models.Status 18 Time int64 19 } 20 21 // EventSummary is like a models.Event but strips the Results and Unevaluated 22 func MakeEventSummary(e models.Event) (EventSummary, bool) { 23 return EventSummary{ 24 Status: e.Status, 25 Time: e.Time.Unix(), 26 }, e.Unevaluated 27 } 28 29 type EpochAction struct { 30 User string 31 Message string 32 Time int64 33 Type models.ActionType 34 } 35 36 func MakeEpochAction(a models.Action) EpochAction { 37 return EpochAction{ 38 User: a.User, 39 Message: a.Message, 40 Time: a.Time.UTC().Unix(), 41 Type: a.Type, 42 } 43 } 44 45 type IncidentSummaryView struct { 46 Id int64 47 Subject string 48 Start int64 49 AlertName string 50 Tags opentsdb.TagSet 51 TagsString string 52 CurrentStatus models.Status 53 WorstStatus models.Status 54 LastAbnormalStatus models.Status 55 LastAbnormalTime models.Epoch 56 Unevaluated bool 57 NeedAck bool 58 Silenced bool 59 Actions []EpochAction 60 Events []EventSummary 61 WarnNotificationChains [][]string 62 CritNotificationChains [][]string 63 LastStatusTime int64 64 } 65 66 func MakeIncidentSummary(c conf.RuleConfProvider, s SilenceTester, is *models.IncidentState) (*IncidentSummaryView, error) { 67 alert := c.GetAlert(is.AlertKey.Name()) 68 if alert == nil { 69 return nil, fmt.Errorf("alert %v does not exist in the configuration", is.AlertKey.Name()) 70 } 71 warnNotifications := alert.WarnNotification.Get(c, is.AlertKey.Group()) 72 critNotifications := alert.CritNotification.Get(c, is.AlertKey.Group()) 73 eventSummaries := []EventSummary{} 74 nonNormalNonUnknownCount := 0 75 for _, event := range is.Events { 76 if event.Status > models.StNormal && event.Status < models.StUnknown { 77 nonNormalNonUnknownCount++ 78 } 79 if eventSummary, unevaluated := MakeEventSummary(event); !unevaluated { 80 eventSummaries = append(eventSummaries, eventSummary) 81 } 82 } 83 actions := make([]EpochAction, len(is.Actions)) 84 for i, action := range is.Actions { 85 actions[i] = MakeEpochAction(action) 86 } 87 subject := is.Subject 88 // There is no rendered subject when the state is unknown and 89 // there is no other non-normal status in the history. 90 if subject == "" && nonNormalNonUnknownCount == 0 { 91 subject = fmt.Sprintf("%s: %v", is.CurrentStatus, is.AlertKey) 92 } 93 return &IncidentSummaryView{ 94 Id: is.Id, 95 Subject: subject, 96 Start: is.Start.Unix(), 97 AlertName: is.AlertKey.Name(), 98 Tags: is.AlertKey.Group(), 99 TagsString: is.AlertKey.Group().String(), 100 CurrentStatus: is.CurrentStatus, 101 WorstStatus: is.WorstStatus, 102 LastAbnormalStatus: is.LastAbnormalStatus, 103 LastAbnormalTime: is.LastAbnormalTime, 104 Unevaluated: is.Unevaluated, 105 NeedAck: is.NeedAck, 106 Silenced: s(is.AlertKey) != nil, 107 Actions: actions, 108 Events: eventSummaries, 109 WarnNotificationChains: conf.GetNotificationChains(warnNotifications), 110 CritNotificationChains: conf.GetNotificationChains(critNotifications), 111 LastStatusTime: is.Last().Time.Unix(), 112 }, nil 113 } 114 115 func (is IncidentSummaryView) Ask(filter string) (bool, error) { 116 sp := strings.SplitN(filter, ":", 2) 117 if len(sp) != 2 { 118 return false, fmt.Errorf("bad filter, filter must be in k:v format, got %v", filter) 119 } 120 key := sp[0] 121 value := sp[1] 122 switch key { 123 case "ack": 124 switch value { 125 case "true": 126 return is.NeedAck == false, nil 127 case "false": 128 return is.NeedAck == true, nil 129 default: 130 return false, fmt.Errorf("unknown %s value: %s", key, value) 131 } 132 case "ackTime": 133 if is.NeedAck == true { 134 return false, nil 135 } 136 for _, action := range is.Actions { 137 if action.Type == models.ActionAcknowledge { 138 return checkTimeArg(action.Time, value) 139 } 140 } 141 // In case an incident does not need ack but have no ack event 142 // I found one case of this from bosun autoclosing, but it was 143 // a very old incident. Don't think we should end up here, but 144 // if we do better to show the ack'd incident than hide it. 145 return true, nil 146 case "hasTag": 147 if strings.Contains(value, "=") { 148 if strings.HasPrefix(value, "=") { 149 q := strings.TrimPrefix(value, "=") 150 for _, v := range is.Tags { 151 if glob.Glob(q, v) { 152 return true, nil 153 } 154 } 155 return false, nil 156 } 157 if strings.HasSuffix(value, "=") { 158 q := strings.TrimSuffix(value, "=") 159 _, ok := is.Tags[q] 160 return ok, nil 161 } 162 sp := strings.Split(value, "=") 163 if len(sp) != 2 { 164 return false, fmt.Errorf("unexpected tag specification: %v", value) 165 } 166 tagValues := strings.Split(sp[1], "|") 167 for k, v := range is.Tags { 168 for _, tagValue := range tagValues { 169 if k == sp[0] && glob.Glob(tagValue, v) { 170 return true, nil 171 } 172 } 173 } 174 return false, nil 175 } 176 q := strings.TrimRight(value, "=") 177 _, ok := is.Tags[q] 178 return ok, nil 179 case "hidden": 180 hide := is.Silenced || is.Unevaluated 181 switch value { 182 case "true": 183 return hide == true, nil 184 case "false": 185 return hide == false, nil 186 default: 187 return false, fmt.Errorf("unknown %s value: %s", key, value) 188 } 189 case "name": 190 return glob.Glob(value, is.AlertName), nil 191 case "user": 192 for _, action := range is.Actions { 193 if glob.Glob(value, action.User) { 194 return true, nil 195 } 196 } 197 return false, nil 198 case "notify": 199 for _, chain := range is.WarnNotificationChains { 200 for _, wn := range chain { 201 if glob.Glob(value, wn) { 202 return true, nil 203 } 204 } 205 } 206 for _, chain := range is.CritNotificationChains { 207 for _, cn := range chain { 208 if glob.Glob(value, cn) { 209 return true, nil 210 } 211 } 212 } 213 return false, nil 214 case "silenced": 215 switch value { 216 case "true": 217 return is.Silenced == true, nil 218 case "false": 219 return is.Silenced == false, nil 220 default: 221 return false, fmt.Errorf("unknown %s value: %s", key, value) 222 } 223 case "start": 224 return checkTimeArg(is.Start, value) 225 case "unevaluated": 226 switch value { 227 case "true": 228 return is.Unevaluated == true, nil 229 case "false": 230 return is.Unevaluated == false, nil 231 default: 232 return false, fmt.Errorf("unknown %s value: %s", key, value) 233 } 234 case "status": // CurrentStatus 235 return is.CurrentStatus.String() == value, nil 236 case "worstStatus": 237 return is.WorstStatus.String() == value, nil 238 case "lastAbnormalStatus": 239 return is.LastAbnormalStatus.String() == value, nil 240 case "subject": 241 return glob.Glob(value, is.Subject), nil 242 case "since": 243 return checkTimeArg(is.LastStatusTime, value) 244 } 245 return false, nil 246 } 247 248 func checkTimeArg(ts int64, arg string) (bool, error) { 249 var op string 250 val := arg 251 if strings.HasPrefix(arg, "<") { 252 op = "<" 253 val = strings.TrimLeft(arg, op) 254 } 255 if strings.HasPrefix(arg, ">") { 256 op = ">" 257 val = strings.TrimLeft(arg, op) 258 } 259 d, err := opentsdb.ParseDuration(val) 260 if err != nil { 261 return false, err 262 } 263 startTime := time.Unix(ts, 0) 264 // might want to make Now a property of incident summary for viewing things in the past 265 // but not going there at the moment. This is because right now I'm working with open 266 // incidents. And "What did incidents look like at this time?" is a different question 267 // since those incidents will no longer be open. 268 relativeTime := time.Now().UTC().Add(time.Duration(-d)) 269 switch op { 270 case ">", "": 271 return startTime.After(relativeTime), nil 272 case "<": 273 return startTime.Before(relativeTime), nil 274 default: 275 return false, fmt.Errorf("unexpected op: %v", op) 276 } 277 }