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