package circonus import ( "bytes" "fmt" "strings" "github.com/circonus-labs/circonus-gometrics/api" "github.com/circonus-labs/circonus-gometrics/api/config" "github.com/hashicorp/errwrap" "github.com/hashicorp/terraform/helper/hashcode" "github.com/hashicorp/terraform/helper/schema" ) const ( // circonus_metric_cluster.* resource attribute names metricClusterDescriptionAttr = "description" metricClusterNameAttr = "name" metricClusterQueryAttr = "query" metricClusterTagsAttr = "tags" // circonus_metric_cluster.* out parameters metricClusterIDAttr = "id" // circonus_metric_cluster.query.* resource attribute names metricClusterDefinitionAttr = "definition" metricClusterTypeAttr = "type" ) var metricClusterDescriptions = attrDescrs{ metricClusterDescriptionAttr: "A description of the metric cluster", metricClusterIDAttr: "The ID of this metric cluster", metricClusterNameAttr: "The name of the metric cluster", metricClusterQueryAttr: "A metric cluster query definition", metricClusterTagsAttr: "A list of tags assigned to the metric cluster", } var metricClusterQueryDescriptions = attrDescrs{ metricClusterDefinitionAttr: "A query to select a collection of metric streams", metricClusterTypeAttr: "The operation to perform on the matching metric streams", } func resourceMetricCluster() *schema.Resource { return &schema.Resource{ Create: metricClusterCreate, Read: metricClusterRead, Update: metricClusterUpdate, Delete: metricClusterDelete, Exists: metricClusterExists, Importer: &schema.ResourceImporter{ State: schema.ImportStatePassthrough, }, Schema: convertToHelperSchema(metricClusterDescriptions, map[schemaAttr]*schema.Schema{ metricClusterDescriptionAttr: &schema.Schema{ Type: schema.TypeString, Optional: true, Computed: true, StateFunc: suppressWhitespace, }, metricClusterNameAttr: &schema.Schema{ Type: schema.TypeString, Required: true, }, metricClusterQueryAttr: &schema.Schema{ Type: schema.TypeSet, Optional: true, MinItems: 1, Elem: &schema.Resource{ Schema: convertToHelperSchema(metricClusterQueryDescriptions, map[schemaAttr]*schema.Schema{ metricClusterDefinitionAttr: &schema.Schema{ Type: schema.TypeString, Required: true, ValidateFunc: validateRegexp(metricClusterDefinitionAttr, `.+`), }, metricClusterTypeAttr: &schema.Schema{ Type: schema.TypeString, Required: true, ValidateFunc: validateStringIn(metricClusterTypeAttr, supportedMetricClusterTypes), }, }), }, }, metricClusterTagsAttr: tagMakeConfigSchema(metricClusterTagsAttr), // Out parameters metricClusterIDAttr: &schema.Schema{ Computed: true, Type: schema.TypeString, ValidateFunc: validateRegexp(metricClusterIDAttr, config.MetricClusterCIDRegex), }, }), } } func metricClusterCreate(d *schema.ResourceData, meta interface{}) error { ctxt := meta.(*providerContext) mc := newMetricCluster() if err := mc.ParseConfig(d); err != nil { return errwrap.Wrapf("error parsing metric cluster schema during create: {{err}}", err) } if err := mc.Create(ctxt); err != nil { return errwrap.Wrapf("error creating metric cluster: {{err}}", err) } d.SetId(mc.CID) return metricClusterRead(d, meta) } func metricClusterExists(d *schema.ResourceData, meta interface{}) (bool, error) { ctxt := meta.(*providerContext) cid := d.Id() mc, err := ctxt.client.FetchMetricCluster(api.CIDType(&cid), "") if err != nil { if strings.Contains(err.Error(), defaultCirconus404ErrorString) { return false, nil } return false, err } if mc.CID == "" { return false, nil } return true, nil } // metricClusterRead pulls data out of the MetricCluster object and stores it // into the appropriate place in the statefile. func metricClusterRead(d *schema.ResourceData, meta interface{}) error { ctxt := meta.(*providerContext) cid := d.Id() mc, err := loadMetricCluster(ctxt, api.CIDType(&cid)) if err != nil { return err } d.SetId(mc.CID) queries := schema.NewSet(metricClusterQueryChecksum, nil) for _, query := range mc.Queries { queryAttrs := map[string]interface{}{ string(metricClusterDefinitionAttr): query.Query, string(metricClusterTypeAttr): query.Type, } queries.Add(queryAttrs) } d.Set(metricClusterDescriptionAttr, mc.Description) d.Set(metricClusterNameAttr, mc.Name) if err := d.Set(metricClusterTagsAttr, tagsToState(apiToTags(mc.Tags))); err != nil { return errwrap.Wrapf(fmt.Sprintf("Unable to store metric cluster %q attribute: {{err}}", metricClusterTagsAttr), err) } d.Set(metricClusterIDAttr, mc.CID) return nil } func metricClusterUpdate(d *schema.ResourceData, meta interface{}) error { ctxt := meta.(*providerContext) mc := newMetricCluster() if err := mc.ParseConfig(d); err != nil { return err } mc.CID = d.Id() if err := mc.Update(ctxt); err != nil { return errwrap.Wrapf(fmt.Sprintf("unable to update metric cluster %q: {{err}}", d.Id()), err) } return metricClusterRead(d, meta) } func metricClusterDelete(d *schema.ResourceData, meta interface{}) error { ctxt := meta.(*providerContext) cid := d.Id() if _, err := ctxt.client.DeleteMetricClusterByCID(api.CIDType(&cid)); err != nil { return errwrap.Wrapf(fmt.Sprintf("unable to delete metric cluster %q: {{err}}", d.Id()), err) } d.SetId("") return nil } func metricClusterQueryChecksum(v interface{}) int { m := v.(map[string]interface{}) b := &bytes.Buffer{} b.Grow(defaultHashBufSize) // Order writes to the buffer using lexically sorted list for easy visual // reconciliation with other lists. if v, found := m[metricClusterDefinitionAttr]; found { fmt.Fprint(b, v.(string)) } if v, found := m[metricClusterTypeAttr]; found { fmt.Fprint(b, v.(string)) } s := b.String() return hashcode.String(s) } // ParseConfig reads Terraform config data and stores the information into a // Circonus MetricCluster object. func (mc *circonusMetricCluster) ParseConfig(d *schema.ResourceData) error { if v, found := d.GetOk(metricClusterDescriptionAttr); found { mc.Description = v.(string) } if v, found := d.GetOk(metricClusterNameAttr); found { mc.Name = v.(string) } if queryListRaw, found := d.GetOk(metricClusterQueryAttr); found { queryList := queryListRaw.(*schema.Set).List() mc.Queries = make([]api.MetricQuery, 0, len(queryList)) for _, queryRaw := range queryList { queryAttrs := newInterfaceMap(queryRaw) var query string if v, found := queryAttrs[metricClusterDefinitionAttr]; found { query = v.(string) } var queryType string if v, found := queryAttrs[metricClusterTypeAttr]; found { queryType = v.(string) } mc.Queries = append(mc.Queries, api.MetricQuery{ Query: query, Type: queryType, }) } } if v, found := d.GetOk(metricClusterTagsAttr); found { mc.Tags = derefStringList(flattenSet(v.(*schema.Set))) } if err := mc.Validate(); err != nil { return err } return nil }