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  }