github.com/vtorhonen/terraform@v0.9.0-beta2.0.20170307220345-5d894e4ffda7/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": &schema.Schema{
    26  				Type:     schema.TypeString,
    27  				Required: true,
    28  				ForceNew: false,
    29  			},
    30  			"id": &schema.Schema{
    31  				Type:     schema.TypeInt,
    32  				Computed: true,
    33  			},
    34  			"description": &schema.Schema{
    35  				Type:     schema.TypeString,
    36  				Optional: true,
    37  			},
    38  			"active": &schema.Schema{
    39  				Type:     schema.TypeBool,
    40  				Optional: true,
    41  				Default:  true,
    42  			},
    43  			"rearm_seconds": &schema.Schema{
    44  				Type:     schema.TypeInt,
    45  				Optional: true,
    46  				Default:  600,
    47  			},
    48  			"services": &schema.Schema{
    49  				Type:     schema.TypeSet,
    50  				Optional: true,
    51  				Elem:     &schema.Schema{Type: schema.TypeString},
    52  				Set:      schema.HashString,
    53  			},
    54  			"condition": &schema.Schema{
    55  				Type:     schema.TypeSet,
    56  				Optional: true,
    57  				Elem: &schema.Resource{
    58  					Schema: map[string]*schema.Schema{
    59  						"type": &schema.Schema{
    60  							Type:     schema.TypeString,
    61  							Required: true,
    62  						},
    63  						"metric_name": &schema.Schema{
    64  							Type:     schema.TypeString,
    65  							Required: true,
    66  						},
    67  						"source": &schema.Schema{
    68  							Type:     schema.TypeString,
    69  							Optional: true,
    70  						},
    71  						"detect_reset": &schema.Schema{
    72  							Type:     schema.TypeBool,
    73  							Optional: true,
    74  						},
    75  						"duration": &schema.Schema{
    76  							Type:     schema.TypeInt,
    77  							Optional: true,
    78  						},
    79  						"threshold": &schema.Schema{
    80  							Type:     schema.TypeFloat,
    81  							Optional: true,
    82  						},
    83  						"summary_function": &schema.Schema{
    84  							Type:     schema.TypeString,
    85  							Optional: true,
    86  						},
    87  					},
    88  				},
    89  				Set: resourceLibratoAlertConditionsHash,
    90  			},
    91  			"attributes": &schema.Schema{
    92  				Type:     schema.TypeList,
    93  				Optional: true,
    94  				Elem: &schema.Resource{
    95  					Schema: map[string]*schema.Schema{
    96  						"runbook_url": &schema.Schema{
    97  							Type:     schema.TypeString,
    98  							Optional: true,
    99  						},
   100  					},
   101  				},
   102  			},
   103  		},
   104  	}
   105  }
   106  
   107  func resourceLibratoAlertConditionsHash(v interface{}) int {
   108  	var buf bytes.Buffer
   109  	m := v.(map[string]interface{})
   110  	buf.WriteString(fmt.Sprintf("%s-", m["type"].(string)))
   111  	buf.WriteString(fmt.Sprintf("%s-", m["metric_name"].(string)))
   112  
   113  	source, present := m["source"]
   114  	if present {
   115  		buf.WriteString(fmt.Sprintf("%s-", source.(string)))
   116  	}
   117  
   118  	detect_reset, present := m["detect_reset"]
   119  	if present {
   120  		buf.WriteString(fmt.Sprintf("%t-", detect_reset.(bool)))
   121  	}
   122  
   123  	duration, present := m["duration"]
   124  	if present {
   125  		buf.WriteString(fmt.Sprintf("%d-", duration.(int)))
   126  	}
   127  
   128  	threshold, present := m["threshold"]
   129  	if present {
   130  		buf.WriteString(fmt.Sprintf("%f-", threshold.(float64)))
   131  	}
   132  
   133  	summary_function, present := m["summary_function"]
   134  	if present {
   135  		buf.WriteString(fmt.Sprintf("%s-", summary_function.(string)))
   136  	}
   137  
   138  	return hashcode.String(buf.String())
   139  }
   140  
   141  func resourceLibratoAlertCreate(d *schema.ResourceData, meta interface{}) error {
   142  	client := meta.(*librato.Client)
   143  
   144  	alert := new(librato.Alert)
   145  	if v, ok := d.GetOk("name"); ok {
   146  		alert.Name = librato.String(v.(string))
   147  	}
   148  	if v, ok := d.GetOk("description"); ok {
   149  		alert.Description = librato.String(v.(string))
   150  	}
   151  	// GetOK returns not OK for false boolean values, use Get
   152  	alert.Active = librato.Bool(d.Get("active").(bool))
   153  	if v, ok := d.GetOk("rearm_seconds"); ok {
   154  		alert.RearmSeconds = librato.Uint(uint(v.(int)))
   155  	}
   156  	if v, ok := d.GetOk("services"); ok {
   157  		vs := v.(*schema.Set)
   158  		services := make([]*string, vs.Len())
   159  		for i, serviceData := range vs.List() {
   160  			services[i] = librato.String(serviceData.(string))
   161  		}
   162  		alert.Services = services
   163  	}
   164  	if v, ok := d.GetOk("condition"); ok {
   165  		vs := v.(*schema.Set)
   166  		conditions := make([]librato.AlertCondition, vs.Len())
   167  		for i, conditionDataM := range vs.List() {
   168  			conditionData := conditionDataM.(map[string]interface{})
   169  			var condition librato.AlertCondition
   170  			if v, ok := conditionData["type"].(string); ok && v != "" {
   171  				condition.Type = librato.String(v)
   172  			}
   173  			if v, ok := conditionData["threshold"].(float64); ok && !math.IsNaN(v) {
   174  				condition.Threshold = librato.Float(v)
   175  			}
   176  			if v, ok := conditionData["metric_name"].(string); ok && v != "" {
   177  				condition.MetricName = librato.String(v)
   178  			}
   179  			if v, ok := conditionData["source"].(string); ok && v != "" {
   180  				condition.Source = librato.String(v)
   181  			}
   182  			if v, ok := conditionData["detect_reset"].(bool); ok {
   183  				condition.DetectReset = librato.Bool(v)
   184  			}
   185  			if v, ok := conditionData["duration"].(int); ok {
   186  				condition.Duration = librato.Uint(uint(v))
   187  			}
   188  			if v, ok := conditionData["summary_function"].(string); ok && v != "" {
   189  				condition.SummaryFunction = librato.String(v)
   190  			}
   191  			conditions[i] = condition
   192  		}
   193  		alert.Conditions = conditions
   194  	}
   195  	if v, ok := d.GetOk("attributes"); ok {
   196  		attributeData := v.([]interface{})
   197  		if len(attributeData) > 1 {
   198  			return fmt.Errorf("Only one set of attributes per alert is supported")
   199  		} else if len(attributeData) == 1 {
   200  			if attributeData[0] == nil {
   201  				return fmt.Errorf("No attributes found in attributes block")
   202  			}
   203  			attributeDataMap := attributeData[0].(map[string]interface{})
   204  			attributes := new(librato.AlertAttributes)
   205  			if v, ok := attributeDataMap["runbook_url"].(string); ok && v != "" {
   206  				attributes.RunbookURL = librato.String(v)
   207  			}
   208  			alert.Attributes = attributes
   209  		}
   210  	}
   211  
   212  	alertResult, _, err := client.Alerts.Create(alert)
   213  
   214  	if err != nil {
   215  		return fmt.Errorf("Error creating Librato alert %s: %s", *alert.Name, err)
   216  	}
   217  
   218  	resource.Retry(1*time.Minute, func() *resource.RetryError {
   219  		_, _, err := client.Alerts.Get(*alertResult.ID)
   220  		if err != nil {
   221  			if errResp, ok := err.(*librato.ErrorResponse); ok && errResp.Response.StatusCode == 404 {
   222  				return resource.RetryableError(err)
   223  			}
   224  			return resource.NonRetryableError(err)
   225  		}
   226  		return nil
   227  	})
   228  
   229  	return resourceLibratoAlertReadResult(d, alertResult)
   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  	alert, _, err := client.Alerts.Get(uint(id))
   240  	if err != nil {
   241  		if errResp, ok := err.(*librato.ErrorResponse); ok && errResp.Response.StatusCode == 404 {
   242  			d.SetId("")
   243  			return nil
   244  		}
   245  		return fmt.Errorf("Error reading Librato Alert %s: %s", d.Id(), err)
   246  	}
   247  
   248  	return resourceLibratoAlertReadResult(d, alert)
   249  }
   250  
   251  func resourceLibratoAlertReadResult(d *schema.ResourceData, alert *librato.Alert) error {
   252  	d.SetId(strconv.FormatUint(uint64(*alert.ID), 10))
   253  	d.Set("id", *alert.ID)
   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", services)
   261  
   262  	conditions := resourceLibratoAlertConditionsGather(d, alert.Conditions)
   263  	d.Set("condition", 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{}) []string {
   272  	retServices := make([]string, 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) []map[string]interface{} {
   284  	retConditions := make([]map[string]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"] = *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  	alert := new(librato.Alert)
   338  	alert.Name = librato.String(d.Get("name").(string))
   339  	if d.HasChange("description") {
   340  		alert.Description = librato.String(d.Get("description").(string))
   341  	}
   342  	if d.HasChange("active") {
   343  		alert.Active = librato.Bool(d.Get("active").(bool))
   344  	}
   345  	if d.HasChange("rearm_seconds") {
   346  		alert.RearmSeconds = librato.Uint(uint(d.Get("rearm_seconds").(int)))
   347  	}
   348  	if d.HasChange("services") {
   349  		vs := d.Get("services").(*schema.Set)
   350  		services := make([]*string, vs.Len())
   351  		for i, serviceData := range vs.List() {
   352  			services[i] = librato.String(serviceData.(string))
   353  		}
   354  		alert.Services = services
   355  	}
   356  
   357  	vs := d.Get("condition").(*schema.Set)
   358  	conditions := make([]librato.AlertCondition, vs.Len())
   359  	for i, conditionDataM := range vs.List() {
   360  		conditionData := conditionDataM.(map[string]interface{})
   361  		var condition librato.AlertCondition
   362  		if v, ok := conditionData["type"].(string); ok && v != "" {
   363  			condition.Type = librato.String(v)
   364  		}
   365  		if v, ok := conditionData["threshold"].(float64); ok && !math.IsNaN(v) {
   366  			condition.Threshold = librato.Float(v)
   367  		}
   368  		if v, ok := conditionData["metric_name"].(string); ok && v != "" {
   369  			condition.MetricName = librato.String(v)
   370  		}
   371  		if v, ok := conditionData["source"].(string); ok && v != "" {
   372  			condition.Source = librato.String(v)
   373  		}
   374  		if v, ok := conditionData["detect_reset"].(bool); ok {
   375  			condition.DetectReset = librato.Bool(v)
   376  		}
   377  		if v, ok := conditionData["duration"].(int); ok {
   378  			condition.Duration = librato.Uint(uint(v))
   379  		}
   380  		if v, ok := conditionData["summary_function"].(string); ok && v != "" {
   381  			condition.SummaryFunction = librato.String(v)
   382  		}
   383  		conditions[i] = condition
   384  		alert.Conditions = conditions
   385  	}
   386  	if d.HasChange("attributes") {
   387  		attributeData := d.Get("attributes").([]interface{})
   388  		if len(attributeData) > 1 {
   389  			return fmt.Errorf("Only one set of attributes per alert is supported")
   390  		} else if len(attributeData) == 1 {
   391  			if attributeData[0] == nil {
   392  				return fmt.Errorf("No attributes found in attributes block")
   393  			}
   394  			attributeDataMap := attributeData[0].(map[string]interface{})
   395  			attributes := new(librato.AlertAttributes)
   396  			if v, ok := attributeDataMap["runbook_url"].(string); ok && v != "" {
   397  				attributes.RunbookURL = librato.String(v)
   398  			}
   399  			alert.Attributes = attributes
   400  		}
   401  	}
   402  
   403  	_, err = client.Alerts.Edit(uint(alertID), alert)
   404  	if err != nil {
   405  		return fmt.Errorf("Error updating Librato alert: %s", err)
   406  	}
   407  
   408  	return resourceLibratoAlertRead(d, meta)
   409  }
   410  
   411  func resourceLibratoAlertDelete(d *schema.ResourceData, meta interface{}) error {
   412  	client := meta.(*librato.Client)
   413  	id, err := strconv.ParseUint(d.Id(), 10, 0)
   414  	if err != nil {
   415  		return err
   416  	}
   417  
   418  	log.Printf("[INFO] Deleting Alert: %d", id)
   419  	_, err = client.Alerts.Delete(uint(id))
   420  	if err != nil {
   421  		return fmt.Errorf("Error deleting Alert: %s", err)
   422  	}
   423  
   424  	resource.Retry(1*time.Minute, func() *resource.RetryError {
   425  		_, _, err := client.Alerts.Get(uint(id))
   426  		if err != nil {
   427  			if errResp, ok := err.(*librato.ErrorResponse); ok && errResp.Response.StatusCode == 404 {
   428  				return nil
   429  			}
   430  			return resource.NonRetryableError(err)
   431  		}
   432  		return resource.RetryableError(fmt.Errorf("alert still exists"))
   433  	})
   434  
   435  	d.SetId("")
   436  	return nil
   437  }