|
| 1 | +package v1 |
| 2 | + |
| 3 | +import ( |
| 4 | + "fmt" |
| 5 | + "strconv" |
| 6 | + |
| 7 | + "go.mongodb.org/atlas/mongodbatlas" |
| 8 | + "go.uber.org/zap" |
| 9 | + |
| 10 | + "github.com/mongodb/mongodb-atlas-kubernetes/pkg/util" |
| 11 | + "github.com/mongodb/mongodb-atlas-kubernetes/pkg/util/compat" |
| 12 | +) |
| 13 | + |
| 14 | +type AlertConfiguration struct { |
| 15 | + // If omitted, the configuration is disabled. |
| 16 | + Enabled bool `json:"enabled,omitempty"` |
| 17 | + // The type of event that will trigger an alert. |
| 18 | + EventTypeName string `json:"eventTypeName,omitempty"` |
| 19 | + // You can filter using the matchers array only when the EventTypeName specifies an event for a host, replica set, or sharded cluster. |
| 20 | + Matchers []Matcher `json:"matchers,omitempty"` |
| 21 | + // Threshold causes an alert to be triggered. |
| 22 | + Threshold *Threshold `json:"threshold,omitempty"` |
| 23 | + // Notifications are sending when an alert condition is detected. |
| 24 | + Notifications []Notification `json:"notifications,omitempty"` |
| 25 | + // MetricThreshold causes an alert to be triggered. |
| 26 | + MetricThreshold *MetricThreshold `json:"metricThreshold,omitempty"` |
| 27 | +} |
| 28 | + |
| 29 | +func (in *AlertConfiguration) ToAtlas() (*mongodbatlas.AlertConfiguration, error) { |
| 30 | + if in == nil { |
| 31 | + return nil, nil |
| 32 | + } |
| 33 | + // Some field can be converted directly |
| 34 | + result := &mongodbatlas.AlertConfiguration{ |
| 35 | + Enabled: &in.Enabled, |
| 36 | + EventTypeName: in.EventTypeName, |
| 37 | + } |
| 38 | + |
| 39 | + for _, m := range in.Matchers { |
| 40 | + matcher := mongodbatlas.Matcher{ |
| 41 | + FieldName: m.FieldName, |
| 42 | + Operator: m.Operator, |
| 43 | + Value: m.Value, |
| 44 | + } |
| 45 | + result.Matchers = append(result.Matchers, matcher) |
| 46 | + } |
| 47 | + |
| 48 | + for _, n := range in.Notifications { |
| 49 | + notification := &mongodbatlas.Notification{} |
| 50 | + err := compat.JSONCopy(¬ification, n) |
| 51 | + if err != nil { |
| 52 | + return nil, err |
| 53 | + } |
| 54 | + result.Notifications = append(result.Notifications, *notification) |
| 55 | + } |
| 56 | + |
| 57 | + // Some fields require special conversion |
| 58 | + tr, err := in.Threshold.ToAtlas() |
| 59 | + if err != nil { |
| 60 | + return nil, err |
| 61 | + } |
| 62 | + result.Threshold = tr |
| 63 | + metricThreshold, err := in.MetricThreshold.ToAtlas() |
| 64 | + if err != nil { |
| 65 | + return nil, err |
| 66 | + } |
| 67 | + result.MetricThreshold = metricThreshold |
| 68 | + return result, err |
| 69 | +} |
| 70 | + |
| 71 | +type Matcher struct { |
| 72 | + // Name of the field in the target object to match on. |
| 73 | + FieldName string `json:"fieldName,omitempty"` |
| 74 | + // The operator to test the field’s value. |
| 75 | + Operator string `json:"operator,omitempty"` |
| 76 | + // Value to test with the specified operator. |
| 77 | + Value string `json:"value,omitempty"` |
| 78 | +} |
| 79 | + |
| 80 | +func (in *Matcher) IsEqual(matcher mongodbatlas.Matcher) bool { |
| 81 | + if in == nil { |
| 82 | + return false |
| 83 | + } |
| 84 | + return in.FieldName == matcher.FieldName && |
| 85 | + in.Operator == matcher.Operator && |
| 86 | + in.Value == matcher.Value |
| 87 | +} |
| 88 | + |
| 89 | +type Threshold struct { |
| 90 | + // Operator to apply when checking the current metric value against the threshold value. it accepts the following values: GREATER_THAN, LESS_THAN |
| 91 | + Operator string `json:"operator,omitempty"` |
| 92 | + // The units for the threshold value |
| 93 | + Units string `json:"units,omitempty"` |
| 94 | + // Threshold value outside which an alert will be triggered. |
| 95 | + Threshold string `json:"threshold,omitempty"` |
| 96 | +} |
| 97 | + |
| 98 | +func (in *Threshold) IsEqual(threshold *mongodbatlas.Threshold) bool { |
| 99 | + logger := zap.NewExample().Sugar() |
| 100 | + if in == nil { |
| 101 | + return threshold == nil |
| 102 | + } |
| 103 | + if threshold == nil { |
| 104 | + return false |
| 105 | + } |
| 106 | + logger.Debugf("threshold: %v", threshold) |
| 107 | + if in.Operator != threshold.Operator { |
| 108 | + logger.Debugf("operator: %s != %s", in.Operator, threshold.Operator) |
| 109 | + return false |
| 110 | + } |
| 111 | + if in.Units != threshold.Units { |
| 112 | + logger.Debugf("units: %s != %s", in.Units, threshold.Units) |
| 113 | + return false |
| 114 | + } |
| 115 | + if in.Threshold != strconv.FormatFloat(threshold.Threshold, 'f', -1, 64) { |
| 116 | + logger.Debugf("threshold: %s != %s", in.Threshold, strconv.FormatFloat(threshold.Threshold, 'f', -1, 64)) |
| 117 | + return false |
| 118 | + } |
| 119 | + return true |
| 120 | +} |
| 121 | + |
| 122 | +func (in *Threshold) ToAtlas() (*mongodbatlas.Threshold, error) { |
| 123 | + if in == nil { |
| 124 | + return nil, nil |
| 125 | + } |
| 126 | + tr, err := strconv.ParseFloat(in.Threshold, 64) |
| 127 | + if err != nil { |
| 128 | + return nil, fmt.Errorf("failed to parse threshold value: %w. should be float", err) |
| 129 | + } |
| 130 | + result := &mongodbatlas.Threshold{ |
| 131 | + Operator: in.Operator, |
| 132 | + Units: in.Units, |
| 133 | + Threshold: tr, |
| 134 | + } |
| 135 | + return result, nil |
| 136 | +} |
| 137 | + |
| 138 | +type Notification struct { |
| 139 | + // Slack API token or Bot token. Populated for the SLACK notifications type. If the token later becomes invalid, Atlas sends an email to the project owner and eventually removes the token. |
| 140 | + APIToken string `json:"apiToken,omitempty"` |
| 141 | + // Slack channel name. Populated for the SLACK notifications type. |
| 142 | + ChannelName string `json:"channelName,omitempty"` |
| 143 | + // Datadog API Key. Found in the Datadog dashboard. Populated for the DATADOG notifications type. |
| 144 | + DatadogAPIKey string `json:"datadogApiKey,omitempty"` |
| 145 | + // Region that indicates which API URL to use |
| 146 | + DatadogRegion string `json:"datadogRegion,omitempty"` |
| 147 | + // Number of minutes to wait after an alert condition is detected before sending out the first notification. |
| 148 | + DelayMin *int `json:"delayMin,omitempty"` |
| 149 | + // Email address to which alert notifications are sent. Populated for the EMAIL notifications type. |
| 150 | + EmailAddress string `json:"emailAddress,omitempty"` |
| 151 | + // Flag indicating if email notifications should be sent. Populated for ORG, GROUP, and USER notifications types. |
| 152 | + EmailEnabled *bool `json:"emailEnabled,omitempty"` |
| 153 | + // The Flowdock personal API token. Populated for the FLOWDOCK notifications type. If the token later becomes invalid, Atlas sends an email to the project owner and eventually removes the token. |
| 154 | + FlowdockAPIToken string `json:"flowdockApiToken,omitempty"` |
| 155 | + // Flowdock flow namse in lower-case letters. |
| 156 | + FlowName string `json:"flowName,omitempty"` |
| 157 | + // Number of minutes to wait between successive notifications for unacknowledged alerts that are not resolved. |
| 158 | + IntervalMin int `json:"intervalMin,omitempty"` |
| 159 | + // Mobile number to which alert notifications are sent. Populated for the SMS notifications type. |
| 160 | + MobileNumber string `json:"mobileNumber,omitempty"` |
| 161 | + // Opsgenie API Key. Populated for the OPS_GENIE notifications type. If the key later becomes invalid, Atlas sends an email to the project owner and eventually removes the token. |
| 162 | + OpsGenieAPIKey string `json:"opsGenieApiKey,omitempty"` |
| 163 | + // Region that indicates which API URL to use. |
| 164 | + OpsGenieRegion string `json:"opsGenieRegion,omitempty"` |
| 165 | + // Flowdock organization name in lower-case letters. This is the name that appears after www.flowdock.com/app/ in the URL string. Populated for the FLOWDOCK notifications type. |
| 166 | + OrgName string `json:"orgName,omitempty"` |
| 167 | + // PagerDuty service key. Populated for the PAGER_DUTY notifications type. If the key later becomes invalid, Atlas sends an email to the project owner and eventually removes the key. |
| 168 | + ServiceKey string `json:"serviceKey,omitempty"` |
| 169 | + // Flag indicating if text message notifications should be sent. Populated for ORG, GROUP, and USER notifications types. |
| 170 | + SMSEnabled *bool `json:"smsEnabled,omitempty"` |
| 171 | + // Unique identifier of a team. |
| 172 | + TeamID string `json:"teamId,omitempty"` |
| 173 | + // Label for the team that receives this notification. |
| 174 | + TeamName string `json:"teamName,omitempty"` |
| 175 | + // Type of alert notification. |
| 176 | + TypeName string `json:"typeName,omitempty"` |
| 177 | + // Name of the Atlas user to which to send notifications. Only a user in the project that owns the alert configuration is allowed here. Populated for the USER notifications type. |
| 178 | + Username string `json:"username,omitempty"` |
| 179 | + // VictorOps API key. Populated for the VICTOR_OPS notifications type. If the key later becomes invalid, Atlas sends an email to the project owner and eventually removes the key. |
| 180 | + VictorOpsAPIKey string `json:"victorOpsApiKey,omitempty"` |
| 181 | + // VictorOps routing key. Populated for the VICTOR_OPS notifications type. If the key later becomes invalid, Atlas sends an email to the project owner and eventually removes the key. |
| 182 | + VictorOpsRoutingKey string `json:"victorOpsRoutingKey,omitempty"` |
| 183 | + // The following roles grant privileges within a project. |
| 184 | + Roles []string `json:"roles,omitempty"` |
| 185 | +} |
| 186 | + |
| 187 | +func (in *Notification) IsEqual(notification mongodbatlas.Notification) bool { |
| 188 | + if in == nil { |
| 189 | + return false |
| 190 | + } |
| 191 | + if in.APIToken != notification.APIToken || |
| 192 | + in.ChannelName != notification.ChannelName || |
| 193 | + in.DatadogAPIKey != notification.DatadogAPIKey || |
| 194 | + in.DatadogRegion != notification.DatadogRegion || |
| 195 | + !util.PtrValuesEqual(in.DelayMin, notification.DelayMin) || |
| 196 | + in.EmailAddress != notification.EmailAddress || |
| 197 | + !util.PtrValuesEqual(in.EmailEnabled, notification.EmailEnabled) || |
| 198 | + in.FlowdockAPIToken != notification.FlowdockAPIToken || |
| 199 | + in.FlowName != notification.FlowName || |
| 200 | + in.IntervalMin != notification.IntervalMin || |
| 201 | + in.MobileNumber != notification.MobileNumber || |
| 202 | + in.OpsGenieAPIKey != notification.OpsGenieAPIKey || |
| 203 | + in.OpsGenieRegion != notification.OpsGenieRegion || |
| 204 | + in.OrgName != notification.OrgName || |
| 205 | + in.ServiceKey != notification.ServiceKey || |
| 206 | + !util.PtrValuesEqual(in.SMSEnabled, notification.SMSEnabled) || |
| 207 | + in.TeamID != notification.TeamID || |
| 208 | + in.TeamName != notification.TeamName || |
| 209 | + in.TypeName != notification.TypeName || |
| 210 | + in.Username != notification.Username || |
| 211 | + in.VictorOpsAPIKey != notification.VictorOpsAPIKey || |
| 212 | + in.VictorOpsRoutingKey != notification.VictorOpsRoutingKey { |
| 213 | + return false |
| 214 | + } |
| 215 | + |
| 216 | + if !util.IsEqualWithoutOrder(in.Roles, notification.Roles) { |
| 217 | + return false |
| 218 | + } |
| 219 | + |
| 220 | + return true |
| 221 | +} |
| 222 | + |
| 223 | +// MetricThreshold causes an alert to be triggered. Required if "eventTypeName" : "OUTSIDE_METRIC_THRESHOLD". |
| 224 | +type MetricThreshold struct { |
| 225 | + // Name of the metric to check. |
| 226 | + MetricName string `json:"metricName,omitempty"` |
| 227 | + // Operator to apply when checking the current metric value against the threshold value. |
| 228 | + Operator string `json:"operator,omitempty"` |
| 229 | + // Threshold value outside which an alert will be triggered. |
| 230 | + Threshold string `json:"threshold"` |
| 231 | + // The units for the threshold value. |
| 232 | + Units string `json:"units,omitempty"` |
| 233 | + // This must be set to AVERAGE. Atlas computes the current metric value as an average. |
| 234 | + Mode string `json:"mode,omitempty"` |
| 235 | +} |
| 236 | + |
| 237 | +func (in *MetricThreshold) IsEqual(threshold *mongodbatlas.MetricThreshold) bool { |
| 238 | + if in == nil { |
| 239 | + return threshold == nil |
| 240 | + } |
| 241 | + if threshold == nil { |
| 242 | + return false |
| 243 | + } |
| 244 | + return in.MetricName == threshold.MetricName && |
| 245 | + in.Operator == threshold.Operator && |
| 246 | + in.Threshold == strconv.FormatFloat(threshold.Threshold, 'f', -1, 64) && |
| 247 | + in.Units == threshold.Units && |
| 248 | + in.Mode == threshold.Mode |
| 249 | +} |
| 250 | + |
| 251 | +func (in *MetricThreshold) ToAtlas() (*mongodbatlas.MetricThreshold, error) { |
| 252 | + if in == nil { |
| 253 | + return nil, nil |
| 254 | + } |
| 255 | + tr, err := strconv.ParseFloat(in.Threshold, 64) |
| 256 | + if err != nil { |
| 257 | + return nil, fmt.Errorf("failed to parse threshold value: %w. should be float", err) |
| 258 | + } |
| 259 | + result := &mongodbatlas.MetricThreshold{ |
| 260 | + MetricName: in.MetricName, |
| 261 | + Operator: in.Operator, |
| 262 | + Threshold: tr, |
| 263 | + Units: in.Units, |
| 264 | + Mode: in.Mode, |
| 265 | + } |
| 266 | + return result, nil |
| 267 | +} |
0 commit comments