github.com/danp/terraform@v0.9.5-0.20170426144147-39d740081351/builtin/providers/circonus/resource_circonus_contact.go (about)

     1  package circonus
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/json"
     6  	"fmt"
     7  	"log"
     8  	"strings"
     9  	"time"
    10  
    11  	"github.com/circonus-labs/circonus-gometrics/api"
    12  	"github.com/circonus-labs/circonus-gometrics/api/config"
    13  	"github.com/hashicorp/errwrap"
    14  	"github.com/hashicorp/terraform/helper/hashcode"
    15  	"github.com/hashicorp/terraform/helper/schema"
    16  )
    17  
    18  const (
    19  	// circonus_contact attributes
    20  	contactAggregationWindowAttr = "aggregation_window"
    21  	contactAlertOptionAttr       = "alert_option"
    22  	contactEmailAttr             = "email"
    23  	contactHTTPAttr              = "http"
    24  	contactIRCAttr               = "irc"
    25  	contactLongMessageAttr       = "long_message"
    26  	contactLongSubjectAttr       = "long_subject"
    27  	contactLongSummaryAttr       = "long_summary"
    28  	contactNameAttr              = "name"
    29  	contactPagerDutyAttr         = "pager_duty"
    30  	contactSMSAttr               = "sms"
    31  	contactShortMessageAttr      = "short_message"
    32  	contactShortSummaryAttr      = "short_summary"
    33  	contactSlackAttr             = "slack"
    34  	contactTagsAttr              = "tags"
    35  	contactVictorOpsAttr         = "victorops"
    36  	contactXMPPAttr              = "xmpp"
    37  
    38  	// circonus_contact.alert_option attributes
    39  	contactEscalateAfterAttr = "escalate_after"
    40  	contactEscalateToAttr    = "escalate_to"
    41  	contactReminderAttr      = "reminder"
    42  	contactSeverityAttr      = "severity"
    43  
    44  	// circonus_contact.email attributes
    45  	contactEmailAddressAttr = "address"
    46  	//contactUserCIDAttr
    47  
    48  	// circonus_contact.http attributes
    49  	contactHTTPAddressAttr schemaAttr = "address"
    50  	contactHTTPFormatAttr             = "format"
    51  	contactHTTPMethodAttr             = "method"
    52  
    53  	// circonus_contact.irc attributes
    54  	//contactUserCIDAttr
    55  
    56  	// circonus_contact.pager_duty attributes
    57  	//contactContactGroupFallbackAttr
    58  	contactPagerDutyServiceKeyAttr schemaAttr = "service_key"
    59  	contactPagerDutyWebhookURLAttr schemaAttr = "webhook_url"
    60  
    61  	// circonus_contact.slack attributes
    62  	//contactContactGroupFallbackAttr
    63  	contactSlackButtonsAttr  = "buttons"
    64  	contactSlackChannelAttr  = "channel"
    65  	contactSlackTeamAttr     = "team"
    66  	contactSlackUsernameAttr = "username"
    67  
    68  	// circonus_contact.sms attributes
    69  	contactSMSAddressAttr = "address"
    70  	//contactUserCIDAttr
    71  
    72  	// circonus_contact.victorops attributes
    73  	//contactContactGroupFallbackAttr
    74  	contactVictorOpsAPIKeyAttr   = "api_key"
    75  	contactVictorOpsCriticalAttr = "critical"
    76  	contactVictorOpsInfoAttr     = "info"
    77  	contactVictorOpsTeamAttr     = "team"
    78  	contactVictorOpsWarningAttr  = "warning"
    79  
    80  	// circonus_contact.victorops attributes
    81  	//contactUserCIDAttr
    82  	contactXMPPAddressAttr = "address"
    83  
    84  	// circonus_contact read-only attributes
    85  	contactLastModifiedAttr   = "last_modified"
    86  	contactLastModifiedByAttr = "last_modified_by"
    87  
    88  	// circonus_contact.* shared attributes
    89  	contactContactGroupFallbackAttr = "contact_group_fallback"
    90  	contactUserCIDAttr              = "user"
    91  )
    92  
    93  const (
    94  	// Contact methods from Circonus
    95  	circonusMethodEmail     = "email"
    96  	circonusMethodHTTP      = "http"
    97  	circonusMethodIRC       = "irc"
    98  	circonusMethodPagerDuty = "pagerduty"
    99  	circonusMethodSlack     = "slack"
   100  	circonusMethodSMS       = "sms"
   101  	circonusMethodVictorOps = "victorops"
   102  	circonusMethodXMPP      = "xmpp"
   103  )
   104  
   105  type contactHTTPInfo struct {
   106  	Address string `json:"url"`
   107  	Format  string `json:"params"`
   108  	Method  string `json:"method"`
   109  }
   110  
   111  type contactPagerDutyInfo struct {
   112  	FallbackGroupCID int    `json:"failover_group,string"`
   113  	ServiceKey       string `json:"service_key"`
   114  	WebhookURL       string `json:"webhook_url"`
   115  }
   116  
   117  type contactSlackInfo struct {
   118  	Buttons          int    `json:"buttons,string"`
   119  	Channel          string `json:"channel"`
   120  	FallbackGroupCID int    `json:"failover_group,string"`
   121  	Team             string `json:"team"`
   122  	Username         string `json:"username"`
   123  }
   124  
   125  type contactVictorOpsInfo struct {
   126  	APIKey           string `json:"api_key"`
   127  	Critical         int    `json:"critical,string"`
   128  	FallbackGroupCID int    `json:"failover_group,string"`
   129  	Info             int    `json:"info,string"`
   130  	Team             string `json:"team"`
   131  	Warning          int    `json:"warning,string"`
   132  }
   133  
   134  var contactGroupDescriptions = attrDescrs{
   135  	contactAggregationWindowAttr:    "",
   136  	contactAlertOptionAttr:          "",
   137  	contactContactGroupFallbackAttr: "",
   138  	contactEmailAttr:                "",
   139  	contactHTTPAttr:                 "",
   140  	contactIRCAttr:                  "",
   141  	contactLastModifiedAttr:         "",
   142  	contactLastModifiedByAttr:       "",
   143  	contactLongMessageAttr:          "",
   144  	contactLongSubjectAttr:          "",
   145  	contactLongSummaryAttr:          "",
   146  	contactNameAttr:                 "",
   147  	contactPagerDutyAttr:            "",
   148  	contactSMSAttr:                  "",
   149  	contactShortMessageAttr:         "",
   150  	contactShortSummaryAttr:         "",
   151  	contactSlackAttr:                "",
   152  	contactTagsAttr:                 "",
   153  	contactVictorOpsAttr:            "",
   154  	contactXMPPAttr:                 "",
   155  }
   156  
   157  var contactAlertDescriptions = attrDescrs{
   158  	contactEscalateAfterAttr: "",
   159  	contactEscalateToAttr:    "",
   160  	contactReminderAttr:      "",
   161  	contactSeverityAttr:      "",
   162  }
   163  
   164  var contactEmailDescriptions = attrDescrs{
   165  	contactEmailAddressAttr: "",
   166  	contactUserCIDAttr:      "",
   167  }
   168  
   169  var contactHTTPDescriptions = attrDescrs{
   170  	contactHTTPAddressAttr: "",
   171  	contactHTTPFormatAttr:  "",
   172  	contactHTTPMethodAttr:  "",
   173  }
   174  
   175  var contactPagerDutyDescriptions = attrDescrs{
   176  	contactContactGroupFallbackAttr: "",
   177  	contactPagerDutyServiceKeyAttr:  "",
   178  	contactPagerDutyWebhookURLAttr:  "",
   179  }
   180  
   181  var contactSlackDescriptions = attrDescrs{
   182  	contactContactGroupFallbackAttr: "",
   183  	contactSlackButtonsAttr:         "",
   184  	contactSlackChannelAttr:         "",
   185  	contactSlackTeamAttr:            "",
   186  	contactSlackUsernameAttr:        "Username Slackbot uses in Slack to deliver a notification",
   187  }
   188  
   189  var contactSMSDescriptions = attrDescrs{
   190  	contactSMSAddressAttr: "",
   191  	contactUserCIDAttr:    "",
   192  }
   193  
   194  var contactVictorOpsDescriptions = attrDescrs{
   195  	contactContactGroupFallbackAttr: "",
   196  	contactVictorOpsAPIKeyAttr:      "",
   197  	contactVictorOpsCriticalAttr:    "",
   198  	contactVictorOpsInfoAttr:        "",
   199  	contactVictorOpsTeamAttr:        "",
   200  	contactVictorOpsWarningAttr:     "",
   201  }
   202  
   203  var contactXMPPDescriptions = attrDescrs{
   204  	contactUserCIDAttr:     "",
   205  	contactXMPPAddressAttr: "",
   206  }
   207  
   208  func resourceContactGroup() *schema.Resource {
   209  	return &schema.Resource{
   210  		Create: contactGroupCreate,
   211  		Read:   contactGroupRead,
   212  		Update: contactGroupUpdate,
   213  		Delete: contactGroupDelete,
   214  		Exists: contactGroupExists,
   215  		Importer: &schema.ResourceImporter{
   216  			State: schema.ImportStatePassthrough,
   217  		},
   218  
   219  		Schema: convertToHelperSchema(contactGroupDescriptions, map[schemaAttr]*schema.Schema{
   220  			contactAggregationWindowAttr: &schema.Schema{
   221  				Type:             schema.TypeString,
   222  				Optional:         true,
   223  				Default:          defaultCirconusAggregationWindow,
   224  				DiffSuppressFunc: suppressEquivalentTimeDurations,
   225  				StateFunc:        normalizeTimeDurationStringToSeconds,
   226  				ValidateFunc: validateFuncs(
   227  					validateDurationMin(contactAggregationWindowAttr, "0s"),
   228  				),
   229  			},
   230  			contactAlertOptionAttr: &schema.Schema{
   231  				Type:     schema.TypeSet,
   232  				Optional: true,
   233  				Set:      contactGroupAlertOptionsChecksum,
   234  				Elem: &schema.Resource{
   235  					Schema: convertToHelperSchema(contactAlertDescriptions, map[schemaAttr]*schema.Schema{
   236  						contactEscalateAfterAttr: &schema.Schema{
   237  							Type:             schema.TypeString,
   238  							Optional:         true,
   239  							DiffSuppressFunc: suppressEquivalentTimeDurations,
   240  							StateFunc:        normalizeTimeDurationStringToSeconds,
   241  							ValidateFunc: validateFuncs(
   242  								validateDurationMin(contactEscalateAfterAttr, defaultCirconusAlertMinEscalateAfter),
   243  							),
   244  						},
   245  						contactEscalateToAttr: &schema.Schema{
   246  							Type:         schema.TypeString,
   247  							Optional:     true,
   248  							ValidateFunc: validateContactGroupCID(contactEscalateToAttr),
   249  						},
   250  						contactReminderAttr: &schema.Schema{
   251  							Type:             schema.TypeString,
   252  							Optional:         true,
   253  							DiffSuppressFunc: suppressEquivalentTimeDurations,
   254  							StateFunc:        normalizeTimeDurationStringToSeconds,
   255  							ValidateFunc: validateFuncs(
   256  								validateDurationMin(contactReminderAttr, "0s"),
   257  							),
   258  						},
   259  						contactSeverityAttr: &schema.Schema{
   260  							Type:     schema.TypeInt,
   261  							Required: true,
   262  							ValidateFunc: validateFuncs(
   263  								validateIntMin(contactSeverityAttr, minSeverity),
   264  								validateIntMax(contactSeverityAttr, maxSeverity),
   265  							),
   266  						},
   267  					}),
   268  				},
   269  			},
   270  			contactEmailAttr: &schema.Schema{
   271  				Type:     schema.TypeSet,
   272  				Optional: true,
   273  				Elem: &schema.Resource{
   274  					Schema: convertToHelperSchema(contactEmailDescriptions, map[schemaAttr]*schema.Schema{
   275  						contactEmailAddressAttr: &schema.Schema{
   276  							Type:          schema.TypeString,
   277  							Optional:      true,
   278  							ConflictsWith: []string{contactEmailAttr + "." + contactUserCIDAttr},
   279  						},
   280  						contactUserCIDAttr: &schema.Schema{
   281  							Type:          schema.TypeString,
   282  							Optional:      true,
   283  							ValidateFunc:  validateUserCID(contactUserCIDAttr),
   284  							ConflictsWith: []string{contactEmailAttr + "." + contactEmailAddressAttr},
   285  						},
   286  					}),
   287  				},
   288  			},
   289  			contactHTTPAttr: &schema.Schema{
   290  				Type:     schema.TypeSet,
   291  				Optional: true,
   292  				Elem: &schema.Resource{
   293  					Schema: convertToHelperSchema(contactHTTPDescriptions, map[schemaAttr]*schema.Schema{
   294  						contactHTTPAddressAttr: &schema.Schema{
   295  							Type:         schema.TypeString,
   296  							Required:     true,
   297  							ValidateFunc: validateHTTPURL(contactHTTPAddressAttr, urlBasicCheck),
   298  						},
   299  						contactHTTPFormatAttr: &schema.Schema{
   300  							Type:         schema.TypeString,
   301  							Optional:     true,
   302  							Default:      defaultCirconusHTTPFormat,
   303  							ValidateFunc: validateStringIn(contactHTTPFormatAttr, validContactHTTPFormats),
   304  						},
   305  						contactHTTPMethodAttr: &schema.Schema{
   306  							Type:         schema.TypeString,
   307  							Optional:     true,
   308  							Default:      defaultCirconusHTTPMethod,
   309  							ValidateFunc: validateStringIn(contactHTTPMethodAttr, validContactHTTPMethods),
   310  						},
   311  					}),
   312  				},
   313  			},
   314  			contactIRCAttr: &schema.Schema{
   315  				Type:     schema.TypeSet,
   316  				Optional: true,
   317  				Elem: &schema.Resource{
   318  					Schema: map[string]*schema.Schema{
   319  						contactUserCIDAttr: &schema.Schema{
   320  							Type:         schema.TypeString,
   321  							Required:     true,
   322  							ValidateFunc: validateUserCID(contactUserCIDAttr),
   323  						},
   324  					},
   325  				},
   326  			},
   327  			contactLongMessageAttr: &schema.Schema{
   328  				Type:      schema.TypeString,
   329  				Optional:  true,
   330  				StateFunc: suppressWhitespace,
   331  			},
   332  			contactLongSubjectAttr: &schema.Schema{
   333  				Type:      schema.TypeString,
   334  				Optional:  true,
   335  				StateFunc: suppressWhitespace,
   336  			},
   337  			contactLongSummaryAttr: &schema.Schema{
   338  				Type:      schema.TypeString,
   339  				Optional:  true,
   340  				StateFunc: suppressWhitespace,
   341  			},
   342  			contactNameAttr: &schema.Schema{
   343  				Type:     schema.TypeString,
   344  				Required: true,
   345  			},
   346  			contactPagerDutyAttr: &schema.Schema{
   347  				Type:     schema.TypeSet,
   348  				Optional: true,
   349  				Elem: &schema.Resource{
   350  					Schema: convertToHelperSchema(contactPagerDutyDescriptions, map[schemaAttr]*schema.Schema{
   351  						contactContactGroupFallbackAttr: &schema.Schema{
   352  							Type:         schema.TypeString,
   353  							Optional:     true,
   354  							ValidateFunc: validateContactGroupCID(contactContactGroupFallbackAttr),
   355  						},
   356  						contactPagerDutyServiceKeyAttr: &schema.Schema{
   357  							Type:         schema.TypeString,
   358  							Required:     true,
   359  							Sensitive:    true,
   360  							ValidateFunc: validateRegexp(contactPagerDutyServiceKeyAttr, `^[a-zA-Z0-9]{32}$`),
   361  						},
   362  						contactPagerDutyWebhookURLAttr: &schema.Schema{
   363  							Type:         schema.TypeString,
   364  							Required:     true,
   365  							ValidateFunc: validateHTTPURL(contactPagerDutyWebhookURLAttr, urlIsAbs),
   366  						},
   367  					}),
   368  				},
   369  			},
   370  			contactShortMessageAttr: &schema.Schema{
   371  				Type:      schema.TypeString,
   372  				Optional:  true,
   373  				StateFunc: suppressWhitespace,
   374  			},
   375  			contactShortSummaryAttr: &schema.Schema{
   376  				Type:      schema.TypeString,
   377  				Optional:  true,
   378  				StateFunc: suppressWhitespace,
   379  			},
   380  			contactSlackAttr: &schema.Schema{
   381  				Type:     schema.TypeSet,
   382  				Optional: true,
   383  				Elem: &schema.Resource{
   384  					Schema: convertToHelperSchema(contactSlackDescriptions, map[schemaAttr]*schema.Schema{
   385  						contactContactGroupFallbackAttr: &schema.Schema{
   386  							Type:         schema.TypeString,
   387  							Optional:     true,
   388  							ValidateFunc: validateContactGroupCID(contactContactGroupFallbackAttr),
   389  						},
   390  						contactSlackButtonsAttr: &schema.Schema{
   391  							Type:     schema.TypeBool,
   392  							Optional: true,
   393  							Default:  true,
   394  						},
   395  						contactSlackChannelAttr: &schema.Schema{
   396  							Type:     schema.TypeString,
   397  							Required: true,
   398  							ValidateFunc: validateFuncs(
   399  								validateRegexp(contactSlackChannelAttr, `^#[\S]+$`),
   400  							),
   401  						},
   402  						contactSlackTeamAttr: &schema.Schema{
   403  							Type:     schema.TypeString,
   404  							Required: true,
   405  						},
   406  						contactSlackUsernameAttr: &schema.Schema{
   407  							Type:     schema.TypeString,
   408  							Optional: true,
   409  							Default:  defaultCirconusSlackUsername,
   410  							ValidateFunc: validateFuncs(
   411  								validateRegexp(contactSlackChannelAttr, `^[\S]+$`),
   412  							),
   413  						},
   414  					}),
   415  				},
   416  			},
   417  			contactSMSAttr: &schema.Schema{
   418  				Type:     schema.TypeSet,
   419  				Optional: true,
   420  				Elem: &schema.Resource{
   421  					Schema: convertToHelperSchema(contactSMSDescriptions, map[schemaAttr]*schema.Schema{
   422  						contactSMSAddressAttr: &schema.Schema{
   423  							Type:          schema.TypeString,
   424  							Optional:      true,
   425  							ConflictsWith: []string{contactSMSAttr + "." + contactUserCIDAttr},
   426  						},
   427  						contactUserCIDAttr: &schema.Schema{
   428  							Type:          schema.TypeString,
   429  							Optional:      true,
   430  							ValidateFunc:  validateUserCID(contactUserCIDAttr),
   431  							ConflictsWith: []string{contactSMSAttr + "." + contactSMSAddressAttr},
   432  						},
   433  					}),
   434  				},
   435  			},
   436  			contactTagsAttr: tagMakeConfigSchema(contactTagsAttr),
   437  			contactVictorOpsAttr: &schema.Schema{
   438  				Type:     schema.TypeSet,
   439  				Optional: true,
   440  				Elem: &schema.Resource{
   441  					Schema: convertToHelperSchema(contactVictorOpsDescriptions, map[schemaAttr]*schema.Schema{
   442  						contactContactGroupFallbackAttr: &schema.Schema{
   443  							Type:         schema.TypeString,
   444  							Optional:     true,
   445  							ValidateFunc: validateContactGroupCID(contactContactGroupFallbackAttr),
   446  						},
   447  						contactVictorOpsAPIKeyAttr: &schema.Schema{
   448  							Type:      schema.TypeString,
   449  							Required:  true,
   450  							Sensitive: true,
   451  						},
   452  						contactVictorOpsCriticalAttr: &schema.Schema{
   453  							Type:     schema.TypeInt,
   454  							Required: true,
   455  							ValidateFunc: validateFuncs(
   456  								validateIntMin(contactVictorOpsCriticalAttr, 1),
   457  								validateIntMax(contactVictorOpsCriticalAttr, 5),
   458  							),
   459  						},
   460  						contactVictorOpsInfoAttr: &schema.Schema{
   461  							Type:     schema.TypeInt,
   462  							Required: true,
   463  							ValidateFunc: validateFuncs(
   464  								validateIntMin(contactVictorOpsInfoAttr, 1),
   465  								validateIntMax(contactVictorOpsInfoAttr, 5),
   466  							),
   467  						},
   468  						contactVictorOpsTeamAttr: &schema.Schema{
   469  							Type:     schema.TypeString,
   470  							Required: true,
   471  						},
   472  						contactVictorOpsWarningAttr: &schema.Schema{
   473  							Type:     schema.TypeInt,
   474  							Required: true,
   475  							ValidateFunc: validateFuncs(
   476  								validateIntMin(contactVictorOpsWarningAttr, 1),
   477  								validateIntMax(contactVictorOpsWarningAttr, 5),
   478  							),
   479  						},
   480  					}),
   481  				},
   482  			},
   483  			contactXMPPAttr: &schema.Schema{
   484  				Type:     schema.TypeSet,
   485  				Optional: true,
   486  				Elem: &schema.Resource{
   487  					Schema: convertToHelperSchema(contactXMPPDescriptions, map[schemaAttr]*schema.Schema{
   488  						contactXMPPAddressAttr: &schema.Schema{
   489  							Type:          schema.TypeString,
   490  							Optional:      true,
   491  							ConflictsWith: []string{contactXMPPAttr + "." + contactUserCIDAttr},
   492  						},
   493  						contactUserCIDAttr: &schema.Schema{
   494  							Type:          schema.TypeString,
   495  							Optional:      true,
   496  							ValidateFunc:  validateUserCID(contactUserCIDAttr),
   497  							ConflictsWith: []string{contactXMPPAttr + "." + contactXMPPAddressAttr},
   498  						},
   499  					}),
   500  				},
   501  			},
   502  
   503  			// OUT parameters
   504  			contactLastModifiedAttr: &schema.Schema{
   505  				Type:     schema.TypeInt,
   506  				Computed: true,
   507  			},
   508  			contactLastModifiedByAttr: &schema.Schema{
   509  				Type:     schema.TypeString,
   510  				Computed: true,
   511  			},
   512  		}),
   513  	}
   514  }
   515  
   516  func contactGroupCreate(d *schema.ResourceData, meta interface{}) error {
   517  	ctxt := meta.(*providerContext)
   518  
   519  	in, err := getContactGroupInput(d)
   520  	if err != nil {
   521  		return err
   522  	}
   523  
   524  	cg, err := ctxt.client.CreateContactGroup(in)
   525  	if err != nil {
   526  		return err
   527  	}
   528  
   529  	d.SetId(cg.CID)
   530  
   531  	return contactGroupRead(d, meta)
   532  }
   533  
   534  func contactGroupExists(d *schema.ResourceData, meta interface{}) (bool, error) {
   535  	c := meta.(*providerContext)
   536  
   537  	cid := d.Id()
   538  	cg, err := c.client.FetchContactGroup(api.CIDType(&cid))
   539  	if err != nil {
   540  		if strings.Contains(err.Error(), defaultCirconus404ErrorString) {
   541  			return false, nil
   542  		}
   543  
   544  		return false, err
   545  	}
   546  
   547  	if cg.CID == "" {
   548  		return false, nil
   549  	}
   550  
   551  	return true, nil
   552  }
   553  
   554  func contactGroupRead(d *schema.ResourceData, meta interface{}) error {
   555  	c := meta.(*providerContext)
   556  
   557  	cid := d.Id()
   558  
   559  	cg, err := c.client.FetchContactGroup(api.CIDType(&cid))
   560  	if err != nil {
   561  		return err
   562  	}
   563  
   564  	if cg.CID == "" {
   565  		return nil
   566  	}
   567  
   568  	d.SetId(cg.CID)
   569  
   570  	httpState, err := contactGroupHTTPToState(cg)
   571  	if err != nil {
   572  		return err
   573  	}
   574  
   575  	pagerDutyState, err := contactGroupPagerDutyToState(cg)
   576  	if err != nil {
   577  		return err
   578  	}
   579  
   580  	slackState, err := contactGroupSlackToState(cg)
   581  	if err != nil {
   582  		return err
   583  	}
   584  
   585  	smsState, err := contactGroupSMSToState(cg)
   586  	if err != nil {
   587  		return err
   588  	}
   589  
   590  	victorOpsState, err := contactGroupVictorOpsToState(cg)
   591  	if err != nil {
   592  		return err
   593  	}
   594  
   595  	xmppState, err := contactGroupXMPPToState(cg)
   596  	if err != nil {
   597  		return err
   598  	}
   599  
   600  	d.Set(contactAggregationWindowAttr, fmt.Sprintf("%ds", cg.AggregationWindow))
   601  
   602  	if err := d.Set(contactAlertOptionAttr, contactGroupAlertOptionsToState(cg)); err != nil {
   603  		return errwrap.Wrapf(fmt.Sprintf("Unable to store contact %q attribute: {{err}}", contactAlertOptionAttr), err)
   604  	}
   605  
   606  	if err := d.Set(contactEmailAttr, contactGroupEmailToState(cg)); err != nil {
   607  		return errwrap.Wrapf(fmt.Sprintf("Unable to store contact %q attribute: {{err}}", contactEmailAttr), err)
   608  	}
   609  
   610  	if err := d.Set(contactHTTPAttr, httpState); err != nil {
   611  		return errwrap.Wrapf(fmt.Sprintf("Unable to store contact %q attribute: {{err}}", contactHTTPAttr), err)
   612  	}
   613  
   614  	if err := d.Set(contactIRCAttr, contactGroupIRCToState(cg)); err != nil {
   615  		return errwrap.Wrapf(fmt.Sprintf("Unable to store contact %q attribute: {{err}}", contactIRCAttr), err)
   616  	}
   617  
   618  	d.Set(contactLongMessageAttr, cg.AlertFormats.LongMessage)
   619  	d.Set(contactLongSubjectAttr, cg.AlertFormats.LongSubject)
   620  	d.Set(contactLongSummaryAttr, cg.AlertFormats.LongSummary)
   621  	d.Set(contactNameAttr, cg.Name)
   622  
   623  	if err := d.Set(contactPagerDutyAttr, pagerDutyState); err != nil {
   624  		return errwrap.Wrapf(fmt.Sprintf("Unable to store contact %q attribute: {{err}}", contactPagerDutyAttr), err)
   625  	}
   626  
   627  	d.Set(contactShortMessageAttr, cg.AlertFormats.ShortMessage)
   628  	d.Set(contactShortSummaryAttr, cg.AlertFormats.ShortSummary)
   629  
   630  	if err := d.Set(contactSlackAttr, slackState); err != nil {
   631  		return errwrap.Wrapf(fmt.Sprintf("Unable to store contact %q attribute: {{err}}", contactSlackAttr), err)
   632  	}
   633  
   634  	if err := d.Set(contactSMSAttr, smsState); err != nil {
   635  		return errwrap.Wrapf(fmt.Sprintf("Unable to store contact %q attribute: {{err}}", contactSMSAttr), err)
   636  	}
   637  
   638  	if err := d.Set(contactTagsAttr, cg.Tags); err != nil {
   639  		return errwrap.Wrapf(fmt.Sprintf("Unable to store contact %q attribute: {{err}}", contactTagsAttr), err)
   640  	}
   641  
   642  	if err := d.Set(contactVictorOpsAttr, victorOpsState); err != nil {
   643  		return errwrap.Wrapf(fmt.Sprintf("Unable to store contact %q attribute: {{err}}", contactVictorOpsAttr), err)
   644  	}
   645  
   646  	if err := d.Set(contactXMPPAttr, xmppState); err != nil {
   647  		return errwrap.Wrapf(fmt.Sprintf("Unable to store contact %q attribute: {{err}}", contactXMPPAttr), err)
   648  	}
   649  
   650  	// Out parameters
   651  	d.Set(contactLastModifiedAttr, cg.LastModified)
   652  	d.Set(contactLastModifiedByAttr, cg.LastModifiedBy)
   653  
   654  	return nil
   655  }
   656  
   657  func contactGroupUpdate(d *schema.ResourceData, meta interface{}) error {
   658  	c := meta.(*providerContext)
   659  
   660  	in, err := getContactGroupInput(d)
   661  	if err != nil {
   662  		return err
   663  	}
   664  
   665  	in.CID = d.Id()
   666  
   667  	if _, err := c.client.UpdateContactGroup(in); err != nil {
   668  		return errwrap.Wrapf(fmt.Sprintf("unable to update contact group %q: {{err}}", d.Id()), err)
   669  	}
   670  
   671  	return contactGroupRead(d, meta)
   672  }
   673  
   674  func contactGroupDelete(d *schema.ResourceData, meta interface{}) error {
   675  	c := meta.(*providerContext)
   676  
   677  	cid := d.Id()
   678  	if _, err := c.client.DeleteContactGroupByCID(api.CIDType(&cid)); err != nil {
   679  		return errwrap.Wrapf(fmt.Sprintf("unable to delete contact group %q: {{err}}", d.Id()), err)
   680  	}
   681  
   682  	d.SetId("")
   683  
   684  	return nil
   685  }
   686  
   687  func contactGroupAlertOptionsToState(cg *api.ContactGroup) []interface{} {
   688  	if config.NumSeverityLevels != len(cg.Reminders) {
   689  		log.Printf("[FATAL] PROVIDER BUG: Need to update constants in contactGroupAlertOptionsToState re: reminders")
   690  		return nil
   691  	}
   692  	if config.NumSeverityLevels != len(cg.Escalations) {
   693  		log.Printf("[FATAL] PROVIDER BUG: Need to update constants in contactGroupAlertOptionsToState re: escalations")
   694  		return nil
   695  	}
   696  
   697  	// Populate all alert options for every severity level.  We'll prune empty
   698  	// values at the end of this function.
   699  	const defaultNumAlertOptions = 4
   700  	alertOptions := make([]*map[string]interface{}, config.NumSeverityLevels)
   701  	for severityIndex := 0; severityIndex < config.NumSeverityLevels; severityIndex++ {
   702  		sevAction := make(map[string]interface{}, defaultNumAlertOptions)
   703  		sevAction[string(contactSeverityAttr)] = severityIndex + 1
   704  		alertOptions[severityIndex] = &sevAction
   705  	}
   706  
   707  	for severityIndex, reminder := range cg.Reminders {
   708  		if reminder != 0 {
   709  			(*alertOptions[severityIndex])[string(contactReminderAttr)] = fmt.Sprintf("%ds", reminder)
   710  		}
   711  	}
   712  
   713  	for severityIndex, escalate := range cg.Escalations {
   714  		if escalate == nil {
   715  			continue
   716  		}
   717  
   718  		(*alertOptions[severityIndex])[string(contactEscalateAfterAttr)] = fmt.Sprintf("%ds", escalate.After)
   719  		(*alertOptions[severityIndex])[string(contactEscalateToAttr)] = escalate.ContactGroupCID
   720  	}
   721  
   722  	alertOptionsList := make([]interface{}, 0, config.NumSeverityLevels)
   723  	for i := 0; i < config.NumSeverityLevels; i++ {
   724  		// NOTE: the 1 is from the always-populated contactSeverityAttr which is
   725  		// always set.
   726  		if len(*alertOptions[i]) > 1 {
   727  			alertOptionsList = append(alertOptionsList, *alertOptions[i])
   728  		}
   729  	}
   730  
   731  	return alertOptionsList
   732  }
   733  
   734  func contactGroupEmailToState(cg *api.ContactGroup) []interface{} {
   735  	emailContacts := make([]interface{}, 0, len(cg.Contacts.Users)+len(cg.Contacts.External))
   736  
   737  	for _, ext := range cg.Contacts.External {
   738  		switch ext.Method {
   739  		case circonusMethodEmail:
   740  			emailContacts = append(emailContacts, map[string]interface{}{
   741  				contactEmailAddressAttr: ext.Info,
   742  			})
   743  		}
   744  	}
   745  
   746  	for _, user := range cg.Contacts.Users {
   747  		switch user.Method {
   748  		case circonusMethodEmail:
   749  			emailContacts = append(emailContacts, map[string]interface{}{
   750  				contactUserCIDAttr: user.UserCID,
   751  			})
   752  		}
   753  	}
   754  
   755  	return emailContacts
   756  }
   757  
   758  func contactGroupHTTPToState(cg *api.ContactGroup) ([]interface{}, error) {
   759  	httpContacts := make([]interface{}, 0, len(cg.Contacts.External))
   760  
   761  	for _, ext := range cg.Contacts.External {
   762  		switch ext.Method {
   763  		case circonusMethodHTTP:
   764  			url := contactHTTPInfo{}
   765  			if err := json.Unmarshal([]byte(ext.Info), &url); err != nil {
   766  				return nil, errwrap.Wrapf(fmt.Sprintf("unable to decode external %s JSON (%q): {{err}}", contactHTTPAttr, ext.Info), err)
   767  			}
   768  
   769  			httpContacts = append(httpContacts, map[string]interface{}{
   770  				string(contactHTTPAddressAttr): url.Address,
   771  				string(contactHTTPFormatAttr):  url.Format,
   772  				string(contactHTTPMethodAttr):  url.Method,
   773  			})
   774  		}
   775  	}
   776  
   777  	return httpContacts, nil
   778  }
   779  
   780  func getContactGroupInput(d *schema.ResourceData) (*api.ContactGroup, error) {
   781  	cg := api.NewContactGroup()
   782  	if v, ok := d.GetOk(contactAggregationWindowAttr); ok {
   783  		aggWindow, _ := time.ParseDuration(v.(string))
   784  		cg.AggregationWindow = uint(aggWindow.Seconds())
   785  	}
   786  
   787  	if v, ok := d.GetOk(contactAlertOptionAttr); ok {
   788  		alertOptionsRaw := v.(*schema.Set).List()
   789  
   790  		ensureEscalationSeverity := func(severity int) {
   791  			if cg.Escalations[severity] == nil {
   792  				cg.Escalations[severity] = &api.ContactGroupEscalation{}
   793  			}
   794  		}
   795  
   796  		for _, alertOptionRaw := range alertOptionsRaw {
   797  			alertOptionsMap := alertOptionRaw.(map[string]interface{})
   798  
   799  			severityIndex := -1
   800  
   801  			if optRaw, ok := alertOptionsMap[contactSeverityAttr]; ok {
   802  				severityIndex = optRaw.(int) - 1
   803  			}
   804  
   805  			if optRaw, ok := alertOptionsMap[contactEscalateAfterAttr]; ok {
   806  				if optRaw.(string) != "" {
   807  					d, _ := time.ParseDuration(optRaw.(string))
   808  					if d != 0 {
   809  						ensureEscalationSeverity(severityIndex)
   810  						cg.Escalations[severityIndex].After = uint(d.Seconds())
   811  					}
   812  				}
   813  			}
   814  
   815  			if optRaw, ok := alertOptionsMap[contactEscalateToAttr]; ok && optRaw.(string) != "" {
   816  				ensureEscalationSeverity(severityIndex)
   817  				cg.Escalations[severityIndex].ContactGroupCID = optRaw.(string)
   818  			}
   819  
   820  			if optRaw, ok := alertOptionsMap[contactReminderAttr]; ok {
   821  				if optRaw.(string) == "" {
   822  					optRaw = "0s"
   823  				}
   824  
   825  				d, _ := time.ParseDuration(optRaw.(string))
   826  				cg.Reminders[severityIndex] = uint(d.Seconds())
   827  			}
   828  		}
   829  	}
   830  
   831  	if v, ok := d.GetOk(contactNameAttr); ok {
   832  		cg.Name = v.(string)
   833  	}
   834  
   835  	if v, ok := d.GetOk(contactEmailAttr); ok {
   836  		emailListRaw := v.(*schema.Set).List()
   837  		for _, emailMapRaw := range emailListRaw {
   838  			emailMap := emailMapRaw.(map[string]interface{})
   839  
   840  			var requiredAttrFound bool
   841  			if v, ok := emailMap[contactEmailAddressAttr]; ok && v.(string) != "" {
   842  				requiredAttrFound = true
   843  				cg.Contacts.External = append(cg.Contacts.External, api.ContactGroupContactsExternal{
   844  					Info:   v.(string),
   845  					Method: circonusMethodEmail,
   846  				})
   847  			}
   848  
   849  			if v, ok := emailMap[contactUserCIDAttr]; ok && v.(string) != "" {
   850  				requiredAttrFound = true
   851  				cg.Contacts.Users = append(cg.Contacts.Users, api.ContactGroupContactsUser{
   852  					Method:  circonusMethodEmail,
   853  					UserCID: v.(string),
   854  				})
   855  			}
   856  
   857  			// Can't mark two attributes that are conflicting as required so we do our
   858  			// own validation check here.
   859  			if !requiredAttrFound {
   860  				return nil, fmt.Errorf("In type %s, either %s or %s must be specified", contactEmailAttr, contactEmailAddressAttr, contactUserCIDAttr)
   861  			}
   862  		}
   863  	}
   864  
   865  	if v, ok := d.GetOk(contactHTTPAttr); ok {
   866  		httpListRaw := v.(*schema.Set).List()
   867  		for _, httpMapRaw := range httpListRaw {
   868  			httpMap := httpMapRaw.(map[string]interface{})
   869  
   870  			httpInfo := contactHTTPInfo{}
   871  
   872  			if v, ok := httpMap[string(contactHTTPAddressAttr)]; ok {
   873  				httpInfo.Address = v.(string)
   874  			}
   875  
   876  			if v, ok := httpMap[string(contactHTTPFormatAttr)]; ok {
   877  				httpInfo.Format = v.(string)
   878  			}
   879  
   880  			if v, ok := httpMap[string(contactHTTPMethodAttr)]; ok {
   881  				httpInfo.Method = v.(string)
   882  			}
   883  
   884  			js, err := json.Marshal(httpInfo)
   885  			if err != nil {
   886  				return nil, errwrap.Wrapf(fmt.Sprintf("error marshalling %s JSON config string: {{err}}", contactHTTPAttr), err)
   887  			}
   888  
   889  			cg.Contacts.External = append(cg.Contacts.External, api.ContactGroupContactsExternal{
   890  				Info:   string(js),
   891  				Method: circonusMethodHTTP,
   892  			})
   893  		}
   894  	}
   895  
   896  	if v, ok := d.GetOk(contactIRCAttr); ok {
   897  		ircListRaw := v.(*schema.Set).List()
   898  		for _, ircMapRaw := range ircListRaw {
   899  			ircMap := ircMapRaw.(map[string]interface{})
   900  
   901  			if v, ok := ircMap[contactUserCIDAttr]; ok && v.(string) != "" {
   902  				cg.Contacts.Users = append(cg.Contacts.Users, api.ContactGroupContactsUser{
   903  					Method:  circonusMethodIRC,
   904  					UserCID: v.(string),
   905  				})
   906  			}
   907  		}
   908  	}
   909  
   910  	if v, ok := d.GetOk(contactPagerDutyAttr); ok {
   911  		pagerDutyListRaw := v.(*schema.Set).List()
   912  		for _, pagerDutyMapRaw := range pagerDutyListRaw {
   913  			pagerDutyMap := pagerDutyMapRaw.(map[string]interface{})
   914  
   915  			pagerDutyInfo := contactPagerDutyInfo{}
   916  
   917  			if v, ok := pagerDutyMap[contactContactGroupFallbackAttr]; ok && v.(string) != "" {
   918  				cid := v.(string)
   919  				contactGroupID, err := failoverGroupCIDToID(api.CIDType(&cid))
   920  				if err != nil {
   921  					return nil, errwrap.Wrapf("error reading contact group CID: {{err}}", err)
   922  				}
   923  				pagerDutyInfo.FallbackGroupCID = contactGroupID
   924  			}
   925  
   926  			if v, ok := pagerDutyMap[string(contactPagerDutyServiceKeyAttr)]; ok {
   927  				pagerDutyInfo.ServiceKey = v.(string)
   928  			}
   929  
   930  			if v, ok := pagerDutyMap[string(contactPagerDutyWebhookURLAttr)]; ok {
   931  				pagerDutyInfo.WebhookURL = v.(string)
   932  			}
   933  
   934  			js, err := json.Marshal(pagerDutyInfo)
   935  			if err != nil {
   936  				return nil, errwrap.Wrapf(fmt.Sprintf("error marshalling %s JSON config string: {{err}}", contactPagerDutyAttr), err)
   937  			}
   938  
   939  			cg.Contacts.External = append(cg.Contacts.External, api.ContactGroupContactsExternal{
   940  				Info:   string(js),
   941  				Method: circonusMethodPagerDuty,
   942  			})
   943  		}
   944  	}
   945  
   946  	if v, ok := d.GetOk(contactSlackAttr); ok {
   947  		slackListRaw := v.(*schema.Set).List()
   948  		for _, slackMapRaw := range slackListRaw {
   949  			slackMap := slackMapRaw.(map[string]interface{})
   950  
   951  			slackInfo := contactSlackInfo{}
   952  
   953  			var buttons int
   954  			if v, ok := slackMap[contactSlackButtonsAttr]; ok {
   955  				if v.(bool) {
   956  					buttons = 1
   957  				}
   958  				slackInfo.Buttons = buttons
   959  			}
   960  
   961  			if v, ok := slackMap[contactSlackChannelAttr]; ok {
   962  				slackInfo.Channel = v.(string)
   963  			}
   964  
   965  			if v, ok := slackMap[contactContactGroupFallbackAttr]; ok && v.(string) != "" {
   966  				cid := v.(string)
   967  				contactGroupID, err := failoverGroupCIDToID(api.CIDType(&cid))
   968  				if err != nil {
   969  					return nil, errwrap.Wrapf("error reading contact group CID: {{err}}", err)
   970  				}
   971  				slackInfo.FallbackGroupCID = contactGroupID
   972  			}
   973  
   974  			if v, ok := slackMap[contactSlackTeamAttr]; ok {
   975  				slackInfo.Team = v.(string)
   976  			}
   977  
   978  			if v, ok := slackMap[contactSlackUsernameAttr]; ok {
   979  				slackInfo.Username = v.(string)
   980  			}
   981  
   982  			js, err := json.Marshal(slackInfo)
   983  			if err != nil {
   984  				return nil, errwrap.Wrapf(fmt.Sprintf("error marshalling %s JSON config string: {{err}}", contactSlackAttr), err)
   985  			}
   986  
   987  			cg.Contacts.External = append(cg.Contacts.External, api.ContactGroupContactsExternal{
   988  				Info:   string(js),
   989  				Method: circonusMethodSlack,
   990  			})
   991  		}
   992  	}
   993  
   994  	if v, ok := d.GetOk(contactSMSAttr); ok {
   995  		smsListRaw := v.(*schema.Set).List()
   996  		for _, smsMapRaw := range smsListRaw {
   997  			smsMap := smsMapRaw.(map[string]interface{})
   998  
   999  			var requiredAttrFound bool
  1000  			if v, ok := smsMap[contactSMSAddressAttr]; ok && v.(string) != "" {
  1001  				requiredAttrFound = true
  1002  				cg.Contacts.External = append(cg.Contacts.External, api.ContactGroupContactsExternal{
  1003  					Info:   v.(string),
  1004  					Method: circonusMethodSMS,
  1005  				})
  1006  			}
  1007  
  1008  			if v, ok := smsMap[contactUserCIDAttr]; ok && v.(string) != "" {
  1009  				requiredAttrFound = true
  1010  				cg.Contacts.Users = append(cg.Contacts.Users, api.ContactGroupContactsUser{
  1011  					Method:  circonusMethodSMS,
  1012  					UserCID: v.(string),
  1013  				})
  1014  			}
  1015  
  1016  			// Can't mark two attributes that are conflicting as required so we do our
  1017  			// own validation check here.
  1018  			if !requiredAttrFound {
  1019  				return nil, fmt.Errorf("In type %s, either %s or %s must be specified", contactEmailAttr, contactEmailAddressAttr, contactUserCIDAttr)
  1020  			}
  1021  		}
  1022  	}
  1023  
  1024  	if v, ok := d.GetOk(contactVictorOpsAttr); ok {
  1025  		victorOpsListRaw := v.(*schema.Set).List()
  1026  		for _, victorOpsMapRaw := range victorOpsListRaw {
  1027  			victorOpsMap := victorOpsMapRaw.(map[string]interface{})
  1028  
  1029  			victorOpsInfo := contactVictorOpsInfo{}
  1030  
  1031  			if v, ok := victorOpsMap[contactContactGroupFallbackAttr]; ok && v.(string) != "" {
  1032  				cid := v.(string)
  1033  				contactGroupID, err := failoverGroupCIDToID(api.CIDType(&cid))
  1034  				if err != nil {
  1035  					return nil, errwrap.Wrapf("error reading contact group CID: {{err}}", err)
  1036  				}
  1037  				victorOpsInfo.FallbackGroupCID = contactGroupID
  1038  			}
  1039  
  1040  			if v, ok := victorOpsMap[contactVictorOpsAPIKeyAttr]; ok {
  1041  				victorOpsInfo.APIKey = v.(string)
  1042  			}
  1043  
  1044  			if v, ok := victorOpsMap[contactVictorOpsCriticalAttr]; ok {
  1045  				victorOpsInfo.Critical = v.(int)
  1046  			}
  1047  
  1048  			if v, ok := victorOpsMap[contactVictorOpsInfoAttr]; ok {
  1049  				victorOpsInfo.Info = v.(int)
  1050  			}
  1051  
  1052  			if v, ok := victorOpsMap[contactVictorOpsTeamAttr]; ok {
  1053  				victorOpsInfo.Team = v.(string)
  1054  			}
  1055  
  1056  			if v, ok := victorOpsMap[contactVictorOpsWarningAttr]; ok {
  1057  				victorOpsInfo.Warning = v.(int)
  1058  			}
  1059  
  1060  			js, err := json.Marshal(victorOpsInfo)
  1061  			if err != nil {
  1062  				return nil, errwrap.Wrapf(fmt.Sprintf("error marshalling %s JSON config string: {{err}}", contactVictorOpsAttr), err)
  1063  			}
  1064  
  1065  			cg.Contacts.External = append(cg.Contacts.External, api.ContactGroupContactsExternal{
  1066  				Info:   string(js),
  1067  				Method: circonusMethodVictorOps,
  1068  			})
  1069  		}
  1070  	}
  1071  
  1072  	if v, ok := d.GetOk(contactXMPPAttr); ok {
  1073  		xmppListRaw := v.(*schema.Set).List()
  1074  		for _, xmppMapRaw := range xmppListRaw {
  1075  			xmppMap := xmppMapRaw.(map[string]interface{})
  1076  
  1077  			if v, ok := xmppMap[contactXMPPAddressAttr]; ok && v.(string) != "" {
  1078  				cg.Contacts.External = append(cg.Contacts.External, api.ContactGroupContactsExternal{
  1079  					Info:   v.(string),
  1080  					Method: circonusMethodXMPP,
  1081  				})
  1082  			}
  1083  
  1084  			if v, ok := xmppMap[contactUserCIDAttr]; ok && v.(string) != "" {
  1085  				cg.Contacts.Users = append(cg.Contacts.Users, api.ContactGroupContactsUser{
  1086  					Method:  circonusMethodXMPP,
  1087  					UserCID: v.(string),
  1088  				})
  1089  			}
  1090  		}
  1091  	}
  1092  
  1093  	if v, ok := d.GetOk(contactLongMessageAttr); ok {
  1094  		msg := v.(string)
  1095  		cg.AlertFormats.LongMessage = &msg
  1096  	}
  1097  
  1098  	if v, ok := d.GetOk(contactLongSubjectAttr); ok {
  1099  		msg := v.(string)
  1100  		cg.AlertFormats.LongSubject = &msg
  1101  	}
  1102  
  1103  	if v, ok := d.GetOk(contactLongSummaryAttr); ok {
  1104  		msg := v.(string)
  1105  		cg.AlertFormats.LongSummary = &msg
  1106  	}
  1107  
  1108  	if v, ok := d.GetOk(contactShortMessageAttr); ok {
  1109  		msg := v.(string)
  1110  		cg.AlertFormats.ShortMessage = &msg
  1111  	}
  1112  
  1113  	if v, ok := d.GetOk(contactShortSummaryAttr); ok {
  1114  		msg := v.(string)
  1115  		cg.AlertFormats.ShortSummary = &msg
  1116  	}
  1117  
  1118  	if v, ok := d.GetOk(contactShortMessageAttr); ok {
  1119  		msg := v.(string)
  1120  		cg.AlertFormats.ShortMessage = &msg
  1121  	}
  1122  
  1123  	if v, found := d.GetOk(checkTagsAttr); found {
  1124  		cg.Tags = derefStringList(flattenSet(v.(*schema.Set)))
  1125  	}
  1126  
  1127  	if err := validateContactGroup(cg); err != nil {
  1128  		return nil, err
  1129  	}
  1130  
  1131  	return cg, nil
  1132  }
  1133  
  1134  func contactGroupIRCToState(cg *api.ContactGroup) []interface{} {
  1135  	ircContacts := make([]interface{}, 0, len(cg.Contacts.Users))
  1136  
  1137  	for _, user := range cg.Contacts.Users {
  1138  		switch user.Method {
  1139  		case circonusMethodIRC:
  1140  			ircContacts = append(ircContacts, map[string]interface{}{
  1141  				contactUserCIDAttr: user.UserCID,
  1142  			})
  1143  		}
  1144  	}
  1145  
  1146  	return ircContacts
  1147  }
  1148  
  1149  func contactGroupPagerDutyToState(cg *api.ContactGroup) ([]interface{}, error) {
  1150  	pdContacts := make([]interface{}, 0, len(cg.Contacts.External))
  1151  
  1152  	for _, ext := range cg.Contacts.External {
  1153  		switch ext.Method {
  1154  		case circonusMethodPagerDuty:
  1155  			pdInfo := contactPagerDutyInfo{}
  1156  			if err := json.Unmarshal([]byte(ext.Info), &pdInfo); err != nil {
  1157  				return nil, errwrap.Wrapf(fmt.Sprintf("unable to decode external %s JSON (%q): {{err}}", contactPagerDutyAttr, ext.Info), err)
  1158  			}
  1159  
  1160  			pdContacts = append(pdContacts, map[string]interface{}{
  1161  				string(contactContactGroupFallbackAttr): failoverGroupIDToCID(pdInfo.FallbackGroupCID),
  1162  				string(contactPagerDutyServiceKeyAttr):  pdInfo.ServiceKey,
  1163  				string(contactPagerDutyWebhookURLAttr):  pdInfo.WebhookURL,
  1164  			})
  1165  		}
  1166  	}
  1167  
  1168  	return pdContacts, nil
  1169  }
  1170  
  1171  func contactGroupSlackToState(cg *api.ContactGroup) ([]interface{}, error) {
  1172  	slackContacts := make([]interface{}, 0, len(cg.Contacts.External))
  1173  
  1174  	for _, ext := range cg.Contacts.External {
  1175  		switch ext.Method {
  1176  		case circonusMethodSlack:
  1177  			slackInfo := contactSlackInfo{}
  1178  			if err := json.Unmarshal([]byte(ext.Info), &slackInfo); err != nil {
  1179  				return nil, errwrap.Wrapf(fmt.Sprintf("unable to decode external %s JSON (%q): {{err}}", contactSlackAttr, ext.Info), err)
  1180  			}
  1181  
  1182  			slackContacts = append(slackContacts, map[string]interface{}{
  1183  				contactContactGroupFallbackAttr: failoverGroupIDToCID(slackInfo.FallbackGroupCID),
  1184  				contactSlackButtonsAttr:         int(slackInfo.Buttons) == int(1),
  1185  				contactSlackChannelAttr:         slackInfo.Channel,
  1186  				contactSlackTeamAttr:            slackInfo.Team,
  1187  				contactSlackUsernameAttr:        slackInfo.Username,
  1188  			})
  1189  		}
  1190  	}
  1191  
  1192  	return slackContacts, nil
  1193  }
  1194  
  1195  func contactGroupSMSToState(cg *api.ContactGroup) ([]interface{}, error) {
  1196  	smsContacts := make([]interface{}, 0, len(cg.Contacts.Users)+len(cg.Contacts.External))
  1197  
  1198  	for _, ext := range cg.Contacts.External {
  1199  		switch ext.Method {
  1200  		case circonusMethodSMS:
  1201  			smsContacts = append(smsContacts, map[string]interface{}{
  1202  				contactSMSAddressAttr: ext.Info,
  1203  			})
  1204  		}
  1205  	}
  1206  
  1207  	for _, user := range cg.Contacts.Users {
  1208  		switch user.Method {
  1209  		case circonusMethodSMS:
  1210  			smsContacts = append(smsContacts, map[string]interface{}{
  1211  				contactUserCIDAttr: user.UserCID,
  1212  			})
  1213  		}
  1214  	}
  1215  
  1216  	return smsContacts, nil
  1217  }
  1218  
  1219  func contactGroupVictorOpsToState(cg *api.ContactGroup) ([]interface{}, error) {
  1220  	victorOpsContacts := make([]interface{}, 0, len(cg.Contacts.External))
  1221  
  1222  	for _, ext := range cg.Contacts.External {
  1223  		switch ext.Method {
  1224  		case circonusMethodVictorOps:
  1225  			victorOpsInfo := contactVictorOpsInfo{}
  1226  			if err := json.Unmarshal([]byte(ext.Info), &victorOpsInfo); err != nil {
  1227  				return nil, errwrap.Wrapf(fmt.Sprintf("unable to decode external %s JSON (%q): {{err}}", contactVictorOpsInfoAttr, ext.Info), err)
  1228  			}
  1229  
  1230  			victorOpsContacts = append(victorOpsContacts, map[string]interface{}{
  1231  				contactContactGroupFallbackAttr: failoverGroupIDToCID(victorOpsInfo.FallbackGroupCID),
  1232  				contactVictorOpsAPIKeyAttr:      victorOpsInfo.APIKey,
  1233  				contactVictorOpsCriticalAttr:    victorOpsInfo.Critical,
  1234  				contactVictorOpsInfoAttr:        victorOpsInfo.Info,
  1235  				contactVictorOpsTeamAttr:        victorOpsInfo.Team,
  1236  				contactVictorOpsWarningAttr:     victorOpsInfo.Warning,
  1237  			})
  1238  		}
  1239  	}
  1240  
  1241  	return victorOpsContacts, nil
  1242  }
  1243  
  1244  func contactGroupXMPPToState(cg *api.ContactGroup) ([]interface{}, error) {
  1245  	xmppContacts := make([]interface{}, 0, len(cg.Contacts.Users)+len(cg.Contacts.External))
  1246  
  1247  	for _, ext := range cg.Contacts.External {
  1248  		switch ext.Method {
  1249  		case circonusMethodXMPP:
  1250  			xmppContacts = append(xmppContacts, map[string]interface{}{
  1251  				contactXMPPAddressAttr: ext.Info,
  1252  			})
  1253  		}
  1254  	}
  1255  
  1256  	for _, user := range cg.Contacts.Users {
  1257  		switch user.Method {
  1258  		case circonusMethodXMPP:
  1259  			xmppContacts = append(xmppContacts, map[string]interface{}{
  1260  				contactUserCIDAttr: user.UserCID,
  1261  			})
  1262  		}
  1263  	}
  1264  
  1265  	return xmppContacts, nil
  1266  }
  1267  
  1268  // contactGroupAlertOptionsChecksum creates a stable hash of the normalized values
  1269  func contactGroupAlertOptionsChecksum(v interface{}) int {
  1270  	m := v.(map[string]interface{})
  1271  	b := &bytes.Buffer{}
  1272  	b.Grow(defaultHashBufSize)
  1273  	fmt.Fprintf(b, "%x", m[contactSeverityAttr].(int))
  1274  	fmt.Fprint(b, normalizeTimeDurationStringToSeconds(m[contactEscalateAfterAttr]))
  1275  	fmt.Fprint(b, m[contactEscalateToAttr])
  1276  	fmt.Fprint(b, normalizeTimeDurationStringToSeconds(m[contactReminderAttr]))
  1277  	return hashcode.String(b.String())
  1278  }