github.com/koding/terraform@v0.6.4-0.20170608090606-5d7e0339779d/builtin/providers/librato/resource_librato_metric.go (about)

     1  package librato
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"log"
     7  	"time"
     8  
     9  	"github.com/hashicorp/terraform/helper/resource"
    10  	"github.com/hashicorp/terraform/helper/schema"
    11  	"github.com/henrikhodne/go-librato/librato"
    12  )
    13  
    14  func resourceLibratoMetric() *schema.Resource {
    15  	return &schema.Resource{
    16  		Create: resourceLibratoMetricCreate,
    17  		Read:   resourceLibratoMetricRead,
    18  		Update: resourceLibratoMetricUpdate,
    19  		Delete: resourceLibratoMetricDelete,
    20  
    21  		Schema: map[string]*schema.Schema{
    22  			"name": {
    23  				Type:     schema.TypeString,
    24  				Required: true,
    25  				ForceNew: false,
    26  			},
    27  			"type": {
    28  				Type:     schema.TypeString,
    29  				Required: true,
    30  			},
    31  			"display_name": {
    32  				Type:     schema.TypeString,
    33  				Optional: true,
    34  			},
    35  			"description": {
    36  				Type:     schema.TypeString,
    37  				Optional: true,
    38  			},
    39  			"period": {
    40  				Type:     schema.TypeInt,
    41  				Optional: true,
    42  			},
    43  			"composite": {
    44  				Type:     schema.TypeString,
    45  				Optional: true,
    46  			},
    47  			"attributes": {
    48  				Type:     schema.TypeList,
    49  				Optional: true,
    50  				MaxItems: 1,
    51  				Elem: &schema.Resource{
    52  					Schema: map[string]*schema.Schema{
    53  						"color": {
    54  							Type:     schema.TypeString,
    55  							Optional: true,
    56  						},
    57  						"display_max": {
    58  							Type:     schema.TypeString,
    59  							Optional: true,
    60  						},
    61  						"display_min": {
    62  							Type:     schema.TypeString,
    63  							Optional: true,
    64  						},
    65  						"display_units_long": {
    66  							Type:     schema.TypeString,
    67  							Optional: true,
    68  						},
    69  						"display_units_short": {
    70  							Type:     schema.TypeString,
    71  							Optional: true,
    72  						},
    73  						"display_stacked": {
    74  							Type:     schema.TypeBool,
    75  							Optional: true,
    76  							Default:  false,
    77  						},
    78  						"created_by_ua": {
    79  							Type:     schema.TypeString,
    80  							Computed: true,
    81  						},
    82  						"gap_detection": {
    83  							Type:     schema.TypeBool,
    84  							Optional: true,
    85  						},
    86  						"aggregate": {
    87  							Type:     schema.TypeBool,
    88  							Optional: true,
    89  						},
    90  					},
    91  				},
    92  			},
    93  		},
    94  	}
    95  }
    96  
    97  func resourceLibratoMetricCreate(d *schema.ResourceData, meta interface{}) error {
    98  	client := meta.(*librato.Client)
    99  
   100  	metric := librato.Metric{
   101  		Name: librato.String(d.Get("name").(string)),
   102  		Type: librato.String(d.Get("type").(string)),
   103  	}
   104  	if a, ok := d.GetOk("display_name"); ok {
   105  		metric.DisplayName = librato.String(a.(string))
   106  	}
   107  	if a, ok := d.GetOk("description"); ok {
   108  		metric.Description = librato.String(a.(string))
   109  	}
   110  	if a, ok := d.GetOk("period"); ok {
   111  		metric.Period = librato.Uint(uint(a.(int)))
   112  	}
   113  	if a, ok := d.GetOk("composite"); ok {
   114  		metric.Composite = librato.String(a.(string))
   115  	}
   116  
   117  	if a, ok := d.GetOk("attributes"); ok {
   118  
   119  		attributeData := a.([]interface{})
   120  		attributeDataMap := attributeData[0].(map[string]interface{})
   121  		attributes := new(librato.MetricAttributes)
   122  
   123  		if v, ok := attributeDataMap["color"].(string); ok && v != "" {
   124  			attributes.Color = librato.String(v)
   125  		}
   126  		if v, ok := attributeDataMap["display_max"].(string); ok && v != "" {
   127  			attributes.DisplayMax = librato.String(v)
   128  		}
   129  		if v, ok := attributeDataMap["display_min"].(string); ok && v != "" {
   130  			attributes.DisplayMin = librato.String(v)
   131  		}
   132  		if v, ok := attributeDataMap["display_units_long"].(string); ok && v != "" {
   133  			attributes.DisplayUnitsLong = *librato.String(v)
   134  		}
   135  		if v, ok := attributeDataMap["display_units_short"].(string); ok && v != "" {
   136  			attributes.DisplayUnitsShort = *librato.String(v)
   137  		}
   138  		if v, ok := attributeDataMap["created_by_ua"].(string); ok && v != "" {
   139  			attributes.CreatedByUA = *librato.String(v)
   140  		}
   141  		if v, ok := attributeDataMap["display_stacked"].(bool); ok {
   142  			attributes.DisplayStacked = *librato.Bool(v)
   143  		}
   144  		if v, ok := attributeDataMap["gap_detection"].(bool); ok {
   145  			attributes.GapDetection = *librato.Bool(v)
   146  		}
   147  		if v, ok := attributeDataMap["aggregate"].(bool); ok {
   148  			attributes.Aggregate = *librato.Bool(v)
   149  		}
   150  
   151  		metric.Attributes = attributes
   152  	}
   153  
   154  	_, err := client.Metrics.Update(&metric)
   155  	if err != nil {
   156  		log.Printf("[INFO] ERROR creating Metric: %s", err)
   157  		return fmt.Errorf("Error creating Librato metric: %s", err)
   158  	}
   159  
   160  	retryErr := resource.Retry(1*time.Minute, func() *resource.RetryError {
   161  		_, _, err := client.Metrics.Get(*metric.Name)
   162  		if err != nil {
   163  			if errResp, ok := err.(*librato.ErrorResponse); ok && errResp.Response.StatusCode == 404 {
   164  				return resource.RetryableError(err)
   165  			}
   166  			return resource.NonRetryableError(err)
   167  		}
   168  		return nil
   169  	})
   170  	if retryErr != nil {
   171  		return fmt.Errorf("Error creating Librato metric: %s", retryErr)
   172  	}
   173  
   174  	d.SetId(*metric.Name)
   175  	return resourceLibratoMetricRead(d, meta)
   176  }
   177  
   178  func resourceLibratoMetricRead(d *schema.ResourceData, meta interface{}) error {
   179  	client := meta.(*librato.Client)
   180  
   181  	id := d.Id()
   182  
   183  	log.Printf("[INFO] Reading Librato Metric: %s", id)
   184  	metric, _, err := client.Metrics.Get(id)
   185  	if err != nil {
   186  		if errResp, ok := err.(*librato.ErrorResponse); ok && errResp.Response.StatusCode == 404 {
   187  			d.SetId("")
   188  			return nil
   189  		}
   190  		return fmt.Errorf("Error reading Librato Metric %s: %s", id, err)
   191  	}
   192  
   193  	d.Set("name", metric.Name)
   194  	d.Set("type", metric.Type)
   195  
   196  	if metric.Description != nil {
   197  		d.Set("description", metric.Description)
   198  	}
   199  
   200  	if metric.DisplayName != nil {
   201  		d.Set("display_name", metric.DisplayName)
   202  	}
   203  
   204  	if metric.Period != nil {
   205  		d.Set("period", metric.Period)
   206  	}
   207  
   208  	if metric.Composite != nil {
   209  		d.Set("composite", metric.Composite)
   210  	}
   211  
   212  	attributes := metricAttributesGather(d, metric.Attributes)
   213  
   214  	// Since attributes isn't a simple terraform type (TypeList), it's best to
   215  	// catch the error returned from the d.Set() function, and handle accordingly.
   216  	if err := d.Set("attributes", attributes); err != nil {
   217  		return err
   218  	}
   219  
   220  	return nil
   221  }
   222  
   223  func resourceLibratoMetricUpdate(d *schema.ResourceData, meta interface{}) error {
   224  	client := meta.(*librato.Client)
   225  
   226  	id := d.Id()
   227  
   228  	metric := new(librato.Metric)
   229  	metric.Name = librato.String(id)
   230  
   231  	if d.HasChange("type") {
   232  		metric.Type = librato.String(d.Get("type").(string))
   233  	}
   234  	if d.HasChange("description") {
   235  		metric.Description = librato.String(d.Get("description").(string))
   236  	}
   237  	if d.HasChange("display_name") {
   238  		metric.DisplayName = librato.String(d.Get("display_name").(string))
   239  	}
   240  	if d.HasChange("period") {
   241  		metric.Period = librato.Uint(uint(d.Get("period").(int)))
   242  	}
   243  	if d.HasChange("composite") {
   244  		metric.Composite = librato.String(d.Get("composite").(string))
   245  	}
   246  	if d.HasChange("attributes") {
   247  		attributeData := d.Get("attributes").([]interface{})
   248  		attributeDataMap := attributeData[0].(map[string]interface{})
   249  		attributes := new(librato.MetricAttributes)
   250  
   251  		if v, ok := attributeDataMap["color"].(string); ok && v != "" {
   252  			attributes.Color = librato.String(v)
   253  		}
   254  		if v, ok := attributeDataMap["display_max"].(string); ok && v != "" {
   255  			attributes.DisplayMax = librato.String(v)
   256  		}
   257  		if v, ok := attributeDataMap["display_min"].(string); ok && v != "" {
   258  			attributes.DisplayMin = librato.String(v)
   259  		}
   260  		if v, ok := attributeDataMap["display_units_long"].(string); ok && v != "" {
   261  			attributes.DisplayUnitsLong = *librato.String(v)
   262  		}
   263  		if v, ok := attributeDataMap["display_units_short"].(string); ok && v != "" {
   264  			attributes.DisplayUnitsShort = *librato.String(v)
   265  		}
   266  		if v, ok := attributeDataMap["created_by_ua"].(string); ok && v != "" {
   267  			attributes.CreatedByUA = *librato.String(v)
   268  		}
   269  		if v, ok := attributeDataMap["display_stacked"].(bool); ok {
   270  			attributes.DisplayStacked = *librato.Bool(v)
   271  		}
   272  		if v, ok := attributeDataMap["gap_detection"].(bool); ok {
   273  			attributes.GapDetection = *librato.Bool(v)
   274  		}
   275  		if v, ok := attributeDataMap["aggregate"].(bool); ok {
   276  			attributes.Aggregate = *librato.Bool(v)
   277  		}
   278  		metric.Attributes = attributes
   279  	}
   280  
   281  	log.Printf("[INFO] Updating Librato metric: %v", structToString(metric))
   282  
   283  	_, err := client.Metrics.Update(metric)
   284  	if err != nil {
   285  		return fmt.Errorf("Error updating Librato metric: %s", err)
   286  	}
   287  
   288  	log.Printf("[INFO] Updated Librato metric %s", id)
   289  
   290  	// Wait for propagation since Librato updates are eventually consistent
   291  	wait := resource.StateChangeConf{
   292  		Pending:                   []string{fmt.Sprintf("%t", false)},
   293  		Target:                    []string{fmt.Sprintf("%t", true)},
   294  		Timeout:                   5 * time.Minute,
   295  		MinTimeout:                2 * time.Second,
   296  		ContinuousTargetOccurence: 5,
   297  		Refresh: func() (interface{}, string, error) {
   298  			log.Printf("[INFO] Checking if Librato Metric %s was updated yet", id)
   299  			changedMetric, _, getErr := client.Metrics.Get(id)
   300  			if getErr != nil {
   301  				return changedMetric, "", getErr
   302  			}
   303  			return changedMetric, "true", nil
   304  		},
   305  	}
   306  
   307  	_, err = wait.WaitForState()
   308  	if err != nil {
   309  		log.Printf("[INFO] ERROR - Failed updating Librato Metric %s: %s", id, err)
   310  		return fmt.Errorf("Failed updating Librato Metric %s: %s", id, err)
   311  	}
   312  
   313  	return resourceLibratoMetricRead(d, meta)
   314  }
   315  
   316  func resourceLibratoMetricDelete(d *schema.ResourceData, meta interface{}) error {
   317  	client := meta.(*librato.Client)
   318  
   319  	id := d.Id()
   320  
   321  	log.Printf("[INFO] Deleting Metric: %s", id)
   322  	_, err := client.Metrics.Delete(id)
   323  	if err != nil {
   324  		return fmt.Errorf("Error deleting Metric: %s", err)
   325  	}
   326  
   327  	log.Printf("[INFO] Verifying Metric %s deleted", id)
   328  	retryErr := resource.Retry(1*time.Minute, func() *resource.RetryError {
   329  
   330  		log.Printf("[INFO] Getting Metric %s", id)
   331  		_, _, err := client.Metrics.Get(id)
   332  		if err != nil {
   333  			if errResp, ok := err.(*librato.ErrorResponse); ok && errResp.Response.StatusCode == 404 {
   334  				log.Printf("[INFO] Metric %s not found, removing from state", id)
   335  				return nil
   336  			}
   337  			log.Printf("[INFO] non-retryable error attempting to Get metric: %s", err)
   338  			return resource.NonRetryableError(err)
   339  		}
   340  
   341  		log.Printf("[INFO] retryable error attempting to Get metric: %s", id)
   342  		return resource.RetryableError(fmt.Errorf("metric still exists"))
   343  	})
   344  	if retryErr != nil {
   345  		return fmt.Errorf("Error deleting librato metric: %s", retryErr)
   346  	}
   347  
   348  	return nil
   349  }
   350  
   351  // Flattens an attributes hash into something that flatmap.Flatten() can handle
   352  func metricAttributesGather(d *schema.ResourceData, attributes *librato.MetricAttributes) []map[string]interface{} {
   353  	result := make([]map[string]interface{}, 0, 1)
   354  
   355  	if attributes != nil {
   356  		retAttributes := make(map[string]interface{})
   357  		if attributes.Color != nil {
   358  			retAttributes["color"] = *attributes.Color
   359  		}
   360  		if attributes.DisplayMax != nil {
   361  			retAttributes["display_max"] = attributes.DisplayMax
   362  		}
   363  		if attributes.DisplayMin != nil {
   364  			retAttributes["display_min"] = attributes.DisplayMin
   365  		}
   366  		if attributes.DisplayUnitsLong != "" {
   367  			retAttributes["display_units_long"] = attributes.DisplayUnitsLong
   368  		}
   369  		if attributes.DisplayUnitsShort != "" {
   370  			retAttributes["display_units_short"] = attributes.DisplayUnitsShort
   371  		}
   372  		if attributes.CreatedByUA != "" {
   373  			retAttributes["created_by_ua"] = attributes.CreatedByUA
   374  		}
   375  		retAttributes["display_stacked"] = attributes.DisplayStacked || false
   376  		retAttributes["gap_detection"] = attributes.GapDetection || false
   377  		retAttributes["aggregate"] = attributes.Aggregate || false
   378  
   379  		result = append(result, retAttributes)
   380  	}
   381  
   382  	return result
   383  }
   384  
   385  func structToString(i interface{}) string {
   386  	s, _ := json.Marshal(i)
   387  	return string(s)
   388  }