github.com/danp/terraform@v0.9.5-0.20170426144147-39d740081351/builtin/providers/librato/resource_librato_alert.go (about) 1 package librato 2 3 import ( 4 "bytes" 5 "fmt" 6 "log" 7 "math" 8 "reflect" 9 "strconv" 10 "time" 11 12 "github.com/hashicorp/terraform/helper/hashcode" 13 "github.com/hashicorp/terraform/helper/resource" 14 "github.com/hashicorp/terraform/helper/schema" 15 "github.com/henrikhodne/go-librato/librato" 16 ) 17 18 func resourceLibratoAlert() *schema.Resource { 19 return &schema.Resource{ 20 Create: resourceLibratoAlertCreate, 21 Read: resourceLibratoAlertRead, 22 Update: resourceLibratoAlertUpdate, 23 Delete: resourceLibratoAlertDelete, 24 25 Schema: map[string]*schema.Schema{ 26 "name": &schema.Schema{ 27 Type: schema.TypeString, 28 Required: true, 29 ForceNew: false, 30 }, 31 "description": &schema.Schema{ 32 Type: schema.TypeString, 33 Optional: true, 34 }, 35 "active": &schema.Schema{ 36 Type: schema.TypeBool, 37 Optional: true, 38 Default: true, 39 }, 40 "rearm_seconds": &schema.Schema{ 41 Type: schema.TypeInt, 42 Optional: true, 43 Default: 600, 44 }, 45 "services": &schema.Schema{ 46 Type: schema.TypeSet, 47 Optional: true, 48 Elem: &schema.Schema{Type: schema.TypeString}, 49 Set: schema.HashString, 50 }, 51 "condition": &schema.Schema{ 52 Type: schema.TypeSet, 53 Optional: true, 54 Elem: &schema.Resource{ 55 Schema: map[string]*schema.Schema{ 56 "type": &schema.Schema{ 57 Type: schema.TypeString, 58 Required: true, 59 }, 60 "metric_name": &schema.Schema{ 61 Type: schema.TypeString, 62 Required: true, 63 }, 64 "source": &schema.Schema{ 65 Type: schema.TypeString, 66 Optional: true, 67 }, 68 "detect_reset": &schema.Schema{ 69 Type: schema.TypeBool, 70 Optional: true, 71 }, 72 "duration": &schema.Schema{ 73 Type: schema.TypeInt, 74 Optional: true, 75 }, 76 "threshold": &schema.Schema{ 77 Type: schema.TypeFloat, 78 Optional: true, 79 }, 80 "summary_function": &schema.Schema{ 81 Type: schema.TypeString, 82 Optional: true, 83 }, 84 }, 85 }, 86 Set: resourceLibratoAlertConditionsHash, 87 }, 88 "attributes": &schema.Schema{ 89 Type: schema.TypeList, 90 Optional: true, 91 Elem: &schema.Resource{ 92 Schema: map[string]*schema.Schema{ 93 "runbook_url": &schema.Schema{ 94 Type: schema.TypeString, 95 Optional: true, 96 }, 97 }, 98 }, 99 }, 100 }, 101 } 102 } 103 104 func resourceLibratoAlertConditionsHash(v interface{}) int { 105 var buf bytes.Buffer 106 m := v.(map[string]interface{}) 107 buf.WriteString(fmt.Sprintf("%s-", m["type"].(string))) 108 buf.WriteString(fmt.Sprintf("%s-", m["metric_name"].(string))) 109 110 source, present := m["source"] 111 if present { 112 buf.WriteString(fmt.Sprintf("%s-", source.(string))) 113 } 114 115 detect_reset, present := m["detect_reset"] 116 if present { 117 buf.WriteString(fmt.Sprintf("%t-", detect_reset.(bool))) 118 } 119 120 duration, present := m["duration"] 121 if present { 122 buf.WriteString(fmt.Sprintf("%d-", duration.(int))) 123 } 124 125 threshold, present := m["threshold"] 126 if present { 127 buf.WriteString(fmt.Sprintf("%f-", threshold.(float64))) 128 } 129 130 summary_function, present := m["summary_function"] 131 if present { 132 buf.WriteString(fmt.Sprintf("%s-", summary_function.(string))) 133 } 134 135 return hashcode.String(buf.String()) 136 } 137 138 func resourceLibratoAlertCreate(d *schema.ResourceData, meta interface{}) error { 139 client := meta.(*librato.Client) 140 141 alert := new(librato.Alert) 142 if v, ok := d.GetOk("name"); ok { 143 alert.Name = librato.String(v.(string)) 144 } 145 if v, ok := d.GetOk("description"); ok { 146 alert.Description = librato.String(v.(string)) 147 } 148 // GetOK returns not OK for false boolean values, use Get 149 alert.Active = librato.Bool(d.Get("active").(bool)) 150 if v, ok := d.GetOk("rearm_seconds"); ok { 151 alert.RearmSeconds = librato.Uint(uint(v.(int))) 152 } 153 if v, ok := d.GetOk("services"); ok { 154 vs := v.(*schema.Set) 155 services := make([]*string, vs.Len()) 156 for i, serviceData := range vs.List() { 157 services[i] = librato.String(serviceData.(string)) 158 } 159 alert.Services = services 160 } 161 if v, ok := d.GetOk("condition"); ok { 162 vs := v.(*schema.Set) 163 conditions := make([]librato.AlertCondition, vs.Len()) 164 for i, conditionDataM := range vs.List() { 165 conditionData := conditionDataM.(map[string]interface{}) 166 var condition librato.AlertCondition 167 if v, ok := conditionData["type"].(string); ok && v != "" { 168 condition.Type = librato.String(v) 169 } 170 if v, ok := conditionData["threshold"].(float64); ok && !math.IsNaN(v) { 171 condition.Threshold = librato.Float(v) 172 } 173 if v, ok := conditionData["metric_name"].(string); ok && v != "" { 174 condition.MetricName = librato.String(v) 175 } 176 if v, ok := conditionData["source"].(string); ok && v != "" { 177 condition.Source = librato.String(v) 178 } 179 if v, ok := conditionData["detect_reset"].(bool); ok { 180 condition.DetectReset = librato.Bool(v) 181 } 182 if v, ok := conditionData["duration"].(int); ok { 183 condition.Duration = librato.Uint(uint(v)) 184 } 185 if v, ok := conditionData["summary_function"].(string); ok && v != "" { 186 condition.SummaryFunction = librato.String(v) 187 } 188 conditions[i] = condition 189 } 190 alert.Conditions = conditions 191 } 192 if v, ok := d.GetOk("attributes"); ok { 193 attributeData := v.([]interface{}) 194 if len(attributeData) > 1 { 195 return fmt.Errorf("Only one set of attributes per alert is supported") 196 } else if len(attributeData) == 1 { 197 if attributeData[0] == nil { 198 return fmt.Errorf("No attributes found in attributes block") 199 } 200 attributeDataMap := attributeData[0].(map[string]interface{}) 201 attributes := new(librato.AlertAttributes) 202 if v, ok := attributeDataMap["runbook_url"].(string); ok && v != "" { 203 attributes.RunbookURL = librato.String(v) 204 } 205 alert.Attributes = attributes 206 } 207 } 208 209 alertResult, _, err := client.Alerts.Create(alert) 210 211 if err != nil { 212 return fmt.Errorf("Error creating Librato alert %s: %s", *alert.Name, err) 213 } 214 log.Printf("[INFO] Created Librato alert: %s", *alertResult) 215 216 resource.Retry(1*time.Minute, func() *resource.RetryError { 217 _, _, err := client.Alerts.Get(*alertResult.ID) 218 if err != nil { 219 if errResp, ok := err.(*librato.ErrorResponse); ok && errResp.Response.StatusCode == 404 { 220 return resource.RetryableError(err) 221 } 222 return resource.NonRetryableError(err) 223 } 224 return nil 225 }) 226 227 d.SetId(strconv.FormatUint(uint64(*alertResult.ID), 10)) 228 229 return resourceLibratoAlertRead(d, meta) 230 } 231 232 func resourceLibratoAlertRead(d *schema.ResourceData, meta interface{}) error { 233 client := meta.(*librato.Client) 234 id, err := strconv.ParseUint(d.Id(), 10, 0) 235 if err != nil { 236 return err 237 } 238 239 log.Printf("[INFO] Reading Librato Alert: %d", id) 240 alert, _, err := client.Alerts.Get(uint(id)) 241 if err != nil { 242 if errResp, ok := err.(*librato.ErrorResponse); ok && errResp.Response.StatusCode == 404 { 243 d.SetId("") 244 return nil 245 } 246 return fmt.Errorf("Error reading Librato Alert %s: %s", d.Id(), err) 247 } 248 log.Printf("[INFO] Received Librato Alert: %s", *alert) 249 250 return resourceLibratoAlertReadResult(d, alert) 251 } 252 253 func resourceLibratoAlertReadResult(d *schema.ResourceData, alert *librato.Alert) error { 254 d.Set("name", *alert.Name) 255 d.Set("description", *alert.Description) 256 d.Set("active", *alert.Active) 257 d.Set("rearm_seconds", *alert.RearmSeconds) 258 259 services := resourceLibratoAlertServicesGather(d, alert.Services.([]interface{})) 260 d.Set("services", schema.NewSet(schema.HashString, services)) 261 262 conditions := resourceLibratoAlertConditionsGather(d, alert.Conditions) 263 d.Set("condition", schema.NewSet(resourceLibratoAlertConditionsHash, conditions)) 264 265 attributes := resourceLibratoAlertAttributesGather(d, alert.Attributes) 266 d.Set("attributes", attributes) 267 268 return nil 269 } 270 271 func resourceLibratoAlertServicesGather(d *schema.ResourceData, services []interface{}) []interface{} { 272 retServices := make([]interface{}, 0, len(services)) 273 274 for _, s := range services { 275 serviceData := s.(map[string]interface{}) 276 // ID field is returned as float64, for whatever reason 277 retServices = append(retServices, fmt.Sprintf("%.f", serviceData["id"])) 278 } 279 280 return retServices 281 } 282 283 func resourceLibratoAlertConditionsGather(d *schema.ResourceData, conditions []librato.AlertCondition) []interface{} { 284 retConditions := make([]interface{}, 0, len(conditions)) 285 for _, c := range conditions { 286 condition := make(map[string]interface{}) 287 if c.Type != nil { 288 condition["type"] = *c.Type 289 } 290 if c.Threshold != nil { 291 condition["threshold"] = *c.Threshold 292 } 293 if c.MetricName != nil { 294 condition["metric_name"] = *c.MetricName 295 } 296 if c.Source != nil { 297 condition["source"] = *c.Source 298 } 299 if c.DetectReset != nil { 300 condition["detect_reset"] = *c.MetricName 301 } 302 if c.Duration != nil { 303 condition["duration"] = int(*c.Duration) 304 } 305 if c.SummaryFunction != nil { 306 condition["summary_function"] = *c.SummaryFunction 307 } 308 retConditions = append(retConditions, condition) 309 } 310 311 return retConditions 312 } 313 314 // Flattens an attributes hash into something that flatmap.Flatten() can handle 315 func resourceLibratoAlertAttributesGather(d *schema.ResourceData, attributes *librato.AlertAttributes) []map[string]interface{} { 316 result := make([]map[string]interface{}, 0, 1) 317 318 if attributes != nil { 319 retAttributes := make(map[string]interface{}) 320 if attributes.RunbookURL != nil { 321 retAttributes["runbook_url"] = *attributes.RunbookURL 322 } 323 result = append(result, retAttributes) 324 } 325 326 return result 327 } 328 329 func resourceLibratoAlertUpdate(d *schema.ResourceData, meta interface{}) error { 330 client := meta.(*librato.Client) 331 332 alertID, err := strconv.ParseUint(d.Id(), 10, 0) 333 if err != nil { 334 return err 335 } 336 337 // Just to have whole object for comparison before/after update 338 fullAlert, _, err := client.Alerts.Get(uint(alertID)) 339 if err != nil { 340 return err 341 } 342 343 alert := new(librato.Alert) 344 alert.Name = librato.String(d.Get("name").(string)) 345 if d.HasChange("description") { 346 alert.Description = librato.String(d.Get("description").(string)) 347 fullAlert.Description = alert.Description 348 } 349 if d.HasChange("active") { 350 alert.Active = librato.Bool(d.Get("active").(bool)) 351 fullAlert.Active = alert.Active 352 } 353 if d.HasChange("rearm_seconds") { 354 alert.RearmSeconds = librato.Uint(uint(d.Get("rearm_seconds").(int))) 355 fullAlert.RearmSeconds = alert.RearmSeconds 356 } 357 if d.HasChange("services") { 358 vs := d.Get("services").(*schema.Set) 359 services := make([]*string, vs.Len()) 360 for i, serviceData := range vs.List() { 361 services[i] = librato.String(serviceData.(string)) 362 } 363 alert.Services = services 364 fullAlert.RearmSeconds = alert.RearmSeconds 365 } 366 367 vs := d.Get("condition").(*schema.Set) 368 conditions := make([]librato.AlertCondition, vs.Len()) 369 for i, conditionDataM := range vs.List() { 370 conditionData := conditionDataM.(map[string]interface{}) 371 var condition librato.AlertCondition 372 if v, ok := conditionData["type"].(string); ok && v != "" { 373 condition.Type = librato.String(v) 374 } 375 if v, ok := conditionData["threshold"].(float64); ok && !math.IsNaN(v) { 376 condition.Threshold = librato.Float(v) 377 } 378 if v, ok := conditionData["metric_name"].(string); ok && v != "" { 379 condition.MetricName = librato.String(v) 380 } 381 if v, ok := conditionData["source"].(string); ok && v != "" { 382 condition.Source = librato.String(v) 383 } 384 if v, ok := conditionData["detect_reset"].(bool); ok { 385 condition.DetectReset = librato.Bool(v) 386 } 387 if v, ok := conditionData["duration"].(int); ok { 388 condition.Duration = librato.Uint(uint(v)) 389 } 390 if v, ok := conditionData["summary_function"].(string); ok && v != "" { 391 condition.SummaryFunction = librato.String(v) 392 } 393 conditions[i] = condition 394 alert.Conditions = conditions 395 fullAlert.Conditions = conditions 396 } 397 if d.HasChange("attributes") { 398 attributeData := d.Get("attributes").([]interface{}) 399 if len(attributeData) > 1 { 400 return fmt.Errorf("Only one set of attributes per alert is supported") 401 } else if len(attributeData) == 1 { 402 if attributeData[0] == nil { 403 return fmt.Errorf("No attributes found in attributes block") 404 } 405 attributeDataMap := attributeData[0].(map[string]interface{}) 406 attributes := new(librato.AlertAttributes) 407 if v, ok := attributeDataMap["runbook_url"].(string); ok && v != "" { 408 attributes.RunbookURL = librato.String(v) 409 } 410 alert.Attributes = attributes 411 fullAlert.Attributes = attributes 412 } 413 } 414 415 log.Printf("[INFO] Updating Librato alert: %s", alert) 416 _, err = client.Alerts.Edit(uint(alertID), alert) 417 if err != nil { 418 return fmt.Errorf("Error updating Librato alert: %s", err) 419 } 420 421 log.Printf("[INFO] Updated Librato alert %d", alertID) 422 423 // Wait for propagation since Librato updates are eventually consistent 424 wait := resource.StateChangeConf{ 425 Pending: []string{fmt.Sprintf("%t", false)}, 426 Target: []string{fmt.Sprintf("%t", true)}, 427 Timeout: 5 * time.Minute, 428 MinTimeout: 2 * time.Second, 429 ContinuousTargetOccurence: 5, 430 Refresh: func() (interface{}, string, error) { 431 log.Printf("[DEBUG] Checking if Librato Alert %d was updated yet", alertID) 432 changedAlert, _, err := client.Alerts.Get(uint(alertID)) 433 if err != nil { 434 return changedAlert, "", err 435 } 436 isEqual := reflect.DeepEqual(*fullAlert, *changedAlert) 437 log.Printf("[DEBUG] Updated Librato Alert %d match: %t", alertID, isEqual) 438 return changedAlert, fmt.Sprintf("%t", isEqual), nil 439 }, 440 } 441 442 _, err = wait.WaitForState() 443 if err != nil { 444 return fmt.Errorf("Failed updating Librato Alert %d: %s", alertID, err) 445 } 446 447 return resourceLibratoAlertRead(d, meta) 448 } 449 450 func resourceLibratoAlertDelete(d *schema.ResourceData, meta interface{}) error { 451 client := meta.(*librato.Client) 452 id, err := strconv.ParseUint(d.Id(), 10, 0) 453 if err != nil { 454 return err 455 } 456 457 log.Printf("[INFO] Deleting Alert: %d", id) 458 _, err = client.Alerts.Delete(uint(id)) 459 if err != nil { 460 return fmt.Errorf("Error deleting Alert: %s", err) 461 } 462 463 resource.Retry(1*time.Minute, func() *resource.RetryError { 464 _, _, err := client.Alerts.Get(uint(id)) 465 if err != nil { 466 if errResp, ok := err.(*librato.ErrorResponse); ok && errResp.Response.StatusCode == 404 { 467 return nil 468 } 469 return resource.NonRetryableError(err) 470 } 471 return resource.RetryableError(fmt.Errorf("alert still exists")) 472 }) 473 474 d.SetId("") 475 return nil 476 }