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  }