terraform/builtin/providers/circonus/resource_circonus_contact.go

1279 lines
38 KiB
Go

package circonus
import (
"bytes"
"encoding/json"
"fmt"
"log"
"strings"
"time"
"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_contact attributes
contactAggregationWindowAttr = "aggregation_window"
contactAlertOptionAttr = "alert_option"
contactEmailAttr = "email"
contactHTTPAttr = "http"
contactIRCAttr = "irc"
contactLongMessageAttr = "long_message"
contactLongSubjectAttr = "long_subject"
contactLongSummaryAttr = "long_summary"
contactNameAttr = "name"
contactPagerDutyAttr = "pager_duty"
contactSMSAttr = "sms"
contactShortMessageAttr = "short_message"
contactShortSummaryAttr = "short_summary"
contactSlackAttr = "slack"
contactTagsAttr = "tags"
contactVictorOpsAttr = "victorops"
contactXMPPAttr = "xmpp"
// circonus_contact.alert_option attributes
contactEscalateAfterAttr = "escalate_after"
contactEscalateToAttr = "escalate_to"
contactReminderAttr = "reminder"
contactSeverityAttr = "severity"
// circonus_contact.email attributes
contactEmailAddressAttr = "address"
//contactUserCIDAttr
// circonus_contact.http attributes
contactHTTPAddressAttr schemaAttr = "address"
contactHTTPFormatAttr = "format"
contactHTTPMethodAttr = "method"
// circonus_contact.irc attributes
//contactUserCIDAttr
// circonus_contact.pager_duty attributes
//contactContactGroupFallbackAttr
contactPagerDutyServiceKeyAttr schemaAttr = "service_key"
contactPagerDutyWebhookURLAttr schemaAttr = "webhook_url"
// circonus_contact.slack attributes
//contactContactGroupFallbackAttr
contactSlackButtonsAttr = "buttons"
contactSlackChannelAttr = "channel"
contactSlackTeamAttr = "team"
contactSlackUsernameAttr = "username"
// circonus_contact.sms attributes
contactSMSAddressAttr = "address"
//contactUserCIDAttr
// circonus_contact.victorops attributes
//contactContactGroupFallbackAttr
contactVictorOpsAPIKeyAttr = "api_key"
contactVictorOpsCriticalAttr = "critical"
contactVictorOpsInfoAttr = "info"
contactVictorOpsTeamAttr = "team"
contactVictorOpsWarningAttr = "warning"
// circonus_contact.victorops attributes
//contactUserCIDAttr
contactXMPPAddressAttr = "address"
// circonus_contact read-only attributes
contactLastModifiedAttr = "last_modified"
contactLastModifiedByAttr = "last_modified_by"
// circonus_contact.* shared attributes
contactContactGroupFallbackAttr = "contact_group_fallback"
contactUserCIDAttr = "user"
)
const (
// Contact methods from Circonus
circonusMethodEmail = "email"
circonusMethodHTTP = "http"
circonusMethodIRC = "irc"
circonusMethodPagerDuty = "pagerduty"
circonusMethodSlack = "slack"
circonusMethodSMS = "sms"
circonusMethodVictorOps = "victorops"
circonusMethodXMPP = "xmpp"
)
type contactHTTPInfo struct {
Address string `json:"url"`
Format string `json:"params"`
Method string `json:"method"`
}
type contactPagerDutyInfo struct {
FallbackGroupCID int `json:"failover_group,string"`
ServiceKey string `json:"service_key"`
WebhookURL string `json:"webhook_url"`
}
type contactSlackInfo struct {
Buttons int `json:"buttons,string"`
Channel string `json:"channel"`
FallbackGroupCID int `json:"failover_group,string"`
Team string `json:"team"`
Username string `json:"username"`
}
type contactVictorOpsInfo struct {
APIKey string `json:"api_key"`
Critical int `json:"critical,string"`
FallbackGroupCID int `json:"failover_group,string"`
Info int `json:"info,string"`
Team string `json:"team"`
Warning int `json:"warning,string"`
}
var contactGroupDescriptions = attrDescrs{
contactAggregationWindowAttr: "",
contactAlertOptionAttr: "",
contactContactGroupFallbackAttr: "",
contactEmailAttr: "",
contactHTTPAttr: "",
contactIRCAttr: "",
contactLastModifiedAttr: "",
contactLastModifiedByAttr: "",
contactLongMessageAttr: "",
contactLongSubjectAttr: "",
contactLongSummaryAttr: "",
contactNameAttr: "",
contactPagerDutyAttr: "",
contactSMSAttr: "",
contactShortMessageAttr: "",
contactShortSummaryAttr: "",
contactSlackAttr: "",
contactTagsAttr: "",
contactVictorOpsAttr: "",
contactXMPPAttr: "",
}
var contactAlertDescriptions = attrDescrs{
contactEscalateAfterAttr: "",
contactEscalateToAttr: "",
contactReminderAttr: "",
contactSeverityAttr: "",
}
var contactEmailDescriptions = attrDescrs{
contactEmailAddressAttr: "",
contactUserCIDAttr: "",
}
var contactHTTPDescriptions = attrDescrs{
contactHTTPAddressAttr: "",
contactHTTPFormatAttr: "",
contactHTTPMethodAttr: "",
}
var contactPagerDutyDescriptions = attrDescrs{
contactContactGroupFallbackAttr: "",
contactPagerDutyServiceKeyAttr: "",
contactPagerDutyWebhookURLAttr: "",
}
var contactSlackDescriptions = attrDescrs{
contactContactGroupFallbackAttr: "",
contactSlackButtonsAttr: "",
contactSlackChannelAttr: "",
contactSlackTeamAttr: "",
contactSlackUsernameAttr: "Username Slackbot uses in Slack to deliver a notification",
}
var contactSMSDescriptions = attrDescrs{
contactSMSAddressAttr: "",
contactUserCIDAttr: "",
}
var contactVictorOpsDescriptions = attrDescrs{
contactContactGroupFallbackAttr: "",
contactVictorOpsAPIKeyAttr: "",
contactVictorOpsCriticalAttr: "",
contactVictorOpsInfoAttr: "",
contactVictorOpsTeamAttr: "",
contactVictorOpsWarningAttr: "",
}
var contactXMPPDescriptions = attrDescrs{
contactUserCIDAttr: "",
contactXMPPAddressAttr: "",
}
func resourceContactGroup() *schema.Resource {
return &schema.Resource{
Create: contactGroupCreate,
Read: contactGroupRead,
Update: contactGroupUpdate,
Delete: contactGroupDelete,
Exists: contactGroupExists,
Importer: &schema.ResourceImporter{
State: schema.ImportStatePassthrough,
},
Schema: convertToHelperSchema(contactGroupDescriptions, map[schemaAttr]*schema.Schema{
contactAggregationWindowAttr: &schema.Schema{
Type: schema.TypeString,
Optional: true,
Default: defaultCirconusAggregationWindow,
DiffSuppressFunc: suppressEquivalentTimeDurations,
StateFunc: normalizeTimeDurationStringToSeconds,
ValidateFunc: validateFuncs(
validateDurationMin(contactAggregationWindowAttr, "0s"),
),
},
contactAlertOptionAttr: &schema.Schema{
Type: schema.TypeSet,
Optional: true,
Set: contactGroupAlertOptionsChecksum,
Elem: &schema.Resource{
Schema: convertToHelperSchema(contactAlertDescriptions, map[schemaAttr]*schema.Schema{
contactEscalateAfterAttr: &schema.Schema{
Type: schema.TypeString,
Optional: true,
DiffSuppressFunc: suppressEquivalentTimeDurations,
StateFunc: normalizeTimeDurationStringToSeconds,
ValidateFunc: validateFuncs(
validateDurationMin(contactEscalateAfterAttr, defaultCirconusAlertMinEscalateAfter),
),
},
contactEscalateToAttr: &schema.Schema{
Type: schema.TypeString,
Optional: true,
ValidateFunc: validateContactGroupCID(contactEscalateToAttr),
},
contactReminderAttr: &schema.Schema{
Type: schema.TypeString,
Optional: true,
DiffSuppressFunc: suppressEquivalentTimeDurations,
StateFunc: normalizeTimeDurationStringToSeconds,
ValidateFunc: validateFuncs(
validateDurationMin(contactReminderAttr, "0s"),
),
},
contactSeverityAttr: &schema.Schema{
Type: schema.TypeInt,
Required: true,
ValidateFunc: validateFuncs(
validateIntMin(contactSeverityAttr, minSeverity),
validateIntMax(contactSeverityAttr, maxSeverity),
),
},
}),
},
},
contactEmailAttr: &schema.Schema{
Type: schema.TypeSet,
Optional: true,
Elem: &schema.Resource{
Schema: convertToHelperSchema(contactEmailDescriptions, map[schemaAttr]*schema.Schema{
contactEmailAddressAttr: &schema.Schema{
Type: schema.TypeString,
Optional: true,
ConflictsWith: []string{contactEmailAttr + "." + contactUserCIDAttr},
},
contactUserCIDAttr: &schema.Schema{
Type: schema.TypeString,
Optional: true,
ValidateFunc: validateUserCID(contactUserCIDAttr),
ConflictsWith: []string{contactEmailAttr + "." + contactEmailAddressAttr},
},
}),
},
},
contactHTTPAttr: &schema.Schema{
Type: schema.TypeSet,
Optional: true,
Elem: &schema.Resource{
Schema: convertToHelperSchema(contactHTTPDescriptions, map[schemaAttr]*schema.Schema{
contactHTTPAddressAttr: &schema.Schema{
Type: schema.TypeString,
Required: true,
ValidateFunc: validateHTTPURL(contactHTTPAddressAttr, urlBasicCheck),
},
contactHTTPFormatAttr: &schema.Schema{
Type: schema.TypeString,
Optional: true,
Default: defaultCirconusHTTPFormat,
ValidateFunc: validateStringIn(contactHTTPFormatAttr, validContactHTTPFormats),
},
contactHTTPMethodAttr: &schema.Schema{
Type: schema.TypeString,
Optional: true,
Default: defaultCirconusHTTPMethod,
ValidateFunc: validateStringIn(contactHTTPMethodAttr, validContactHTTPMethods),
},
}),
},
},
contactIRCAttr: &schema.Schema{
Type: schema.TypeSet,
Optional: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
contactUserCIDAttr: &schema.Schema{
Type: schema.TypeString,
Required: true,
ValidateFunc: validateUserCID(contactUserCIDAttr),
},
},
},
},
contactLongMessageAttr: &schema.Schema{
Type: schema.TypeString,
Optional: true,
StateFunc: suppressWhitespace,
},
contactLongSubjectAttr: &schema.Schema{
Type: schema.TypeString,
Optional: true,
StateFunc: suppressWhitespace,
},
contactLongSummaryAttr: &schema.Schema{
Type: schema.TypeString,
Optional: true,
StateFunc: suppressWhitespace,
},
contactNameAttr: &schema.Schema{
Type: schema.TypeString,
Required: true,
},
contactPagerDutyAttr: &schema.Schema{
Type: schema.TypeSet,
Optional: true,
Elem: &schema.Resource{
Schema: convertToHelperSchema(contactPagerDutyDescriptions, map[schemaAttr]*schema.Schema{
contactContactGroupFallbackAttr: &schema.Schema{
Type: schema.TypeString,
Optional: true,
ValidateFunc: validateContactGroupCID(contactContactGroupFallbackAttr),
},
contactPagerDutyServiceKeyAttr: &schema.Schema{
Type: schema.TypeString,
Required: true,
Sensitive: true,
ValidateFunc: validateRegexp(contactPagerDutyServiceKeyAttr, `^[a-zA-Z0-9]{32}$`),
},
contactPagerDutyWebhookURLAttr: &schema.Schema{
Type: schema.TypeString,
Required: true,
ValidateFunc: validateHTTPURL(contactPagerDutyWebhookURLAttr, urlIsAbs),
},
}),
},
},
contactShortMessageAttr: &schema.Schema{
Type: schema.TypeString,
Optional: true,
StateFunc: suppressWhitespace,
},
contactShortSummaryAttr: &schema.Schema{
Type: schema.TypeString,
Optional: true,
StateFunc: suppressWhitespace,
},
contactSlackAttr: &schema.Schema{
Type: schema.TypeSet,
Optional: true,
Elem: &schema.Resource{
Schema: convertToHelperSchema(contactSlackDescriptions, map[schemaAttr]*schema.Schema{
contactContactGroupFallbackAttr: &schema.Schema{
Type: schema.TypeString,
Optional: true,
ValidateFunc: validateContactGroupCID(contactContactGroupFallbackAttr),
},
contactSlackButtonsAttr: &schema.Schema{
Type: schema.TypeBool,
Optional: true,
Default: true,
},
contactSlackChannelAttr: &schema.Schema{
Type: schema.TypeString,
Required: true,
ValidateFunc: validateFuncs(
validateRegexp(contactSlackChannelAttr, `^#[\S]+$`),
),
},
contactSlackTeamAttr: &schema.Schema{
Type: schema.TypeString,
Required: true,
},
contactSlackUsernameAttr: &schema.Schema{
Type: schema.TypeString,
Optional: true,
Default: defaultCirconusSlackUsername,
ValidateFunc: validateFuncs(
validateRegexp(contactSlackChannelAttr, `^[\S]+$`),
),
},
}),
},
},
contactSMSAttr: &schema.Schema{
Type: schema.TypeSet,
Optional: true,
Elem: &schema.Resource{
Schema: convertToHelperSchema(contactSMSDescriptions, map[schemaAttr]*schema.Schema{
contactSMSAddressAttr: &schema.Schema{
Type: schema.TypeString,
Optional: true,
ConflictsWith: []string{contactSMSAttr + "." + contactUserCIDAttr},
},
contactUserCIDAttr: &schema.Schema{
Type: schema.TypeString,
Optional: true,
ValidateFunc: validateUserCID(contactUserCIDAttr),
ConflictsWith: []string{contactSMSAttr + "." + contactSMSAddressAttr},
},
}),
},
},
contactTagsAttr: tagMakeConfigSchema(contactTagsAttr),
contactVictorOpsAttr: &schema.Schema{
Type: schema.TypeSet,
Optional: true,
Elem: &schema.Resource{
Schema: convertToHelperSchema(contactVictorOpsDescriptions, map[schemaAttr]*schema.Schema{
contactContactGroupFallbackAttr: &schema.Schema{
Type: schema.TypeString,
Optional: true,
ValidateFunc: validateContactGroupCID(contactContactGroupFallbackAttr),
},
contactVictorOpsAPIKeyAttr: &schema.Schema{
Type: schema.TypeString,
Required: true,
Sensitive: true,
},
contactVictorOpsCriticalAttr: &schema.Schema{
Type: schema.TypeInt,
Required: true,
ValidateFunc: validateFuncs(
validateIntMin(contactVictorOpsCriticalAttr, 1),
validateIntMax(contactVictorOpsCriticalAttr, 5),
),
},
contactVictorOpsInfoAttr: &schema.Schema{
Type: schema.TypeInt,
Required: true,
ValidateFunc: validateFuncs(
validateIntMin(contactVictorOpsInfoAttr, 1),
validateIntMax(contactVictorOpsInfoAttr, 5),
),
},
contactVictorOpsTeamAttr: &schema.Schema{
Type: schema.TypeString,
Required: true,
},
contactVictorOpsWarningAttr: &schema.Schema{
Type: schema.TypeInt,
Required: true,
ValidateFunc: validateFuncs(
validateIntMin(contactVictorOpsWarningAttr, 1),
validateIntMax(contactVictorOpsWarningAttr, 5),
),
},
}),
},
},
contactXMPPAttr: &schema.Schema{
Type: schema.TypeSet,
Optional: true,
Elem: &schema.Resource{
Schema: convertToHelperSchema(contactXMPPDescriptions, map[schemaAttr]*schema.Schema{
contactXMPPAddressAttr: &schema.Schema{
Type: schema.TypeString,
Optional: true,
ConflictsWith: []string{contactXMPPAttr + "." + contactUserCIDAttr},
},
contactUserCIDAttr: &schema.Schema{
Type: schema.TypeString,
Optional: true,
ValidateFunc: validateUserCID(contactUserCIDAttr),
ConflictsWith: []string{contactXMPPAttr + "." + contactXMPPAddressAttr},
},
}),
},
},
// OUT parameters
contactLastModifiedAttr: &schema.Schema{
Type: schema.TypeInt,
Computed: true,
},
contactLastModifiedByAttr: &schema.Schema{
Type: schema.TypeString,
Computed: true,
},
}),
}
}
func contactGroupCreate(d *schema.ResourceData, meta interface{}) error {
ctxt := meta.(*providerContext)
in, err := getContactGroupInput(d)
if err != nil {
return err
}
cg, err := ctxt.client.CreateContactGroup(in)
if err != nil {
return err
}
d.SetId(cg.CID)
return contactGroupRead(d, meta)
}
func contactGroupExists(d *schema.ResourceData, meta interface{}) (bool, error) {
c := meta.(*providerContext)
cid := d.Id()
cg, err := c.client.FetchContactGroup(api.CIDType(&cid))
if err != nil {
if strings.Contains(err.Error(), defaultCirconus404ErrorString) {
return false, nil
}
return false, err
}
if cg.CID == "" {
return false, nil
}
return true, nil
}
func contactGroupRead(d *schema.ResourceData, meta interface{}) error {
c := meta.(*providerContext)
cid := d.Id()
cg, err := c.client.FetchContactGroup(api.CIDType(&cid))
if err != nil {
return err
}
if cg.CID == "" {
return nil
}
d.SetId(cg.CID)
httpState, err := contactGroupHTTPToState(cg)
if err != nil {
return err
}
pagerDutyState, err := contactGroupPagerDutyToState(cg)
if err != nil {
return err
}
slackState, err := contactGroupSlackToState(cg)
if err != nil {
return err
}
smsState, err := contactGroupSMSToState(cg)
if err != nil {
return err
}
victorOpsState, err := contactGroupVictorOpsToState(cg)
if err != nil {
return err
}
xmppState, err := contactGroupXMPPToState(cg)
if err != nil {
return err
}
d.Set(contactAggregationWindowAttr, fmt.Sprintf("%ds", cg.AggregationWindow))
if err := d.Set(contactAlertOptionAttr, contactGroupAlertOptionsToState(cg)); err != nil {
return errwrap.Wrapf(fmt.Sprintf("Unable to store contact %q attribute: {{err}}", contactAlertOptionAttr), err)
}
if err := d.Set(contactEmailAttr, contactGroupEmailToState(cg)); err != nil {
return errwrap.Wrapf(fmt.Sprintf("Unable to store contact %q attribute: {{err}}", contactEmailAttr), err)
}
if err := d.Set(contactHTTPAttr, httpState); err != nil {
return errwrap.Wrapf(fmt.Sprintf("Unable to store contact %q attribute: {{err}}", contactHTTPAttr), err)
}
if err := d.Set(contactIRCAttr, contactGroupIRCToState(cg)); err != nil {
return errwrap.Wrapf(fmt.Sprintf("Unable to store contact %q attribute: {{err}}", contactIRCAttr), err)
}
d.Set(contactLongMessageAttr, cg.AlertFormats.LongMessage)
d.Set(contactLongSubjectAttr, cg.AlertFormats.LongSubject)
d.Set(contactLongSummaryAttr, cg.AlertFormats.LongSummary)
d.Set(contactNameAttr, cg.Name)
if err := d.Set(contactPagerDutyAttr, pagerDutyState); err != nil {
return errwrap.Wrapf(fmt.Sprintf("Unable to store contact %q attribute: {{err}}", contactPagerDutyAttr), err)
}
d.Set(contactShortMessageAttr, cg.AlertFormats.ShortMessage)
d.Set(contactShortSummaryAttr, cg.AlertFormats.ShortSummary)
if err := d.Set(contactSlackAttr, slackState); err != nil {
return errwrap.Wrapf(fmt.Sprintf("Unable to store contact %q attribute: {{err}}", contactSlackAttr), err)
}
if err := d.Set(contactSMSAttr, smsState); err != nil {
return errwrap.Wrapf(fmt.Sprintf("Unable to store contact %q attribute: {{err}}", contactSMSAttr), err)
}
if err := d.Set(contactTagsAttr, cg.Tags); err != nil {
return errwrap.Wrapf(fmt.Sprintf("Unable to store contact %q attribute: {{err}}", contactTagsAttr), err)
}
if err := d.Set(contactVictorOpsAttr, victorOpsState); err != nil {
return errwrap.Wrapf(fmt.Sprintf("Unable to store contact %q attribute: {{err}}", contactVictorOpsAttr), err)
}
if err := d.Set(contactXMPPAttr, xmppState); err != nil {
return errwrap.Wrapf(fmt.Sprintf("Unable to store contact %q attribute: {{err}}", contactXMPPAttr), err)
}
// Out parameters
d.Set(contactLastModifiedAttr, cg.LastModified)
d.Set(contactLastModifiedByAttr, cg.LastModifiedBy)
return nil
}
func contactGroupUpdate(d *schema.ResourceData, meta interface{}) error {
c := meta.(*providerContext)
in, err := getContactGroupInput(d)
if err != nil {
return err
}
in.CID = d.Id()
if _, err := c.client.UpdateContactGroup(in); err != nil {
return errwrap.Wrapf(fmt.Sprintf("unable to update contact group %q: {{err}}", d.Id()), err)
}
return contactGroupRead(d, meta)
}
func contactGroupDelete(d *schema.ResourceData, meta interface{}) error {
c := meta.(*providerContext)
cid := d.Id()
if _, err := c.client.DeleteContactGroupByCID(api.CIDType(&cid)); err != nil {
return errwrap.Wrapf(fmt.Sprintf("unable to delete contact group %q: {{err}}", d.Id()), err)
}
d.SetId("")
return nil
}
func contactGroupAlertOptionsToState(cg *api.ContactGroup) []interface{} {
if config.NumSeverityLevels != len(cg.Reminders) {
log.Printf("[FATAL] PROVIDER BUG: Need to update constants in contactGroupAlertOptionsToState re: reminders")
return nil
}
if config.NumSeverityLevels != len(cg.Escalations) {
log.Printf("[FATAL] PROVIDER BUG: Need to update constants in contactGroupAlertOptionsToState re: escalations")
return nil
}
// Populate all alert options for every severity level. We'll prune empty
// values at the end of this function.
const defaultNumAlertOptions = 4
alertOptions := make([]*map[string]interface{}, config.NumSeverityLevels)
for severityIndex := 0; severityIndex < config.NumSeverityLevels; severityIndex++ {
sevAction := make(map[string]interface{}, defaultNumAlertOptions)
sevAction[string(contactSeverityAttr)] = severityIndex + 1
alertOptions[severityIndex] = &sevAction
}
for severityIndex, reminder := range cg.Reminders {
if reminder != 0 {
(*alertOptions[severityIndex])[string(contactReminderAttr)] = fmt.Sprintf("%ds", reminder)
}
}
for severityIndex, escalate := range cg.Escalations {
if escalate == nil {
continue
}
(*alertOptions[severityIndex])[string(contactEscalateAfterAttr)] = fmt.Sprintf("%ds", escalate.After)
(*alertOptions[severityIndex])[string(contactEscalateToAttr)] = escalate.ContactGroupCID
}
alertOptionsList := make([]interface{}, 0, config.NumSeverityLevels)
for i := 0; i < config.NumSeverityLevels; i++ {
// NOTE: the 1 is from the always-populated contactSeverityAttr which is
// always set.
if len(*alertOptions[i]) > 1 {
alertOptionsList = append(alertOptionsList, *alertOptions[i])
}
}
return alertOptionsList
}
func contactGroupEmailToState(cg *api.ContactGroup) []interface{} {
emailContacts := make([]interface{}, 0, len(cg.Contacts.Users)+len(cg.Contacts.External))
for _, ext := range cg.Contacts.External {
switch ext.Method {
case circonusMethodEmail:
emailContacts = append(emailContacts, map[string]interface{}{
contactEmailAddressAttr: ext.Info,
})
}
}
for _, user := range cg.Contacts.Users {
switch user.Method {
case circonusMethodEmail:
emailContacts = append(emailContacts, map[string]interface{}{
contactUserCIDAttr: user.UserCID,
})
}
}
return emailContacts
}
func contactGroupHTTPToState(cg *api.ContactGroup) ([]interface{}, error) {
httpContacts := make([]interface{}, 0, len(cg.Contacts.External))
for _, ext := range cg.Contacts.External {
switch ext.Method {
case circonusMethodHTTP:
url := contactHTTPInfo{}
if err := json.Unmarshal([]byte(ext.Info), &url); err != nil {
return nil, errwrap.Wrapf(fmt.Sprintf("unable to decode external %s JSON (%q): {{err}}", contactHTTPAttr, ext.Info), err)
}
httpContacts = append(httpContacts, map[string]interface{}{
string(contactHTTPAddressAttr): url.Address,
string(contactHTTPFormatAttr): url.Format,
string(contactHTTPMethodAttr): url.Method,
})
}
}
return httpContacts, nil
}
func getContactGroupInput(d *schema.ResourceData) (*api.ContactGroup, error) {
cg := api.NewContactGroup()
if v, ok := d.GetOk(contactAggregationWindowAttr); ok {
aggWindow, _ := time.ParseDuration(v.(string))
cg.AggregationWindow = uint(aggWindow.Seconds())
}
if v, ok := d.GetOk(contactAlertOptionAttr); ok {
alertOptionsRaw := v.(*schema.Set).List()
ensureEscalationSeverity := func(severity int) {
if cg.Escalations[severity] == nil {
cg.Escalations[severity] = &api.ContactGroupEscalation{}
}
}
for _, alertOptionRaw := range alertOptionsRaw {
alertOptionsMap := alertOptionRaw.(map[string]interface{})
severityIndex := -1
if optRaw, ok := alertOptionsMap[contactSeverityAttr]; ok {
severityIndex = optRaw.(int) - 1
}
if optRaw, ok := alertOptionsMap[contactEscalateAfterAttr]; ok {
if optRaw.(string) != "" {
d, _ := time.ParseDuration(optRaw.(string))
if d != 0 {
ensureEscalationSeverity(severityIndex)
cg.Escalations[severityIndex].After = uint(d.Seconds())
}
}
}
if optRaw, ok := alertOptionsMap[contactEscalateToAttr]; ok && optRaw.(string) != "" {
ensureEscalationSeverity(severityIndex)
cg.Escalations[severityIndex].ContactGroupCID = optRaw.(string)
}
if optRaw, ok := alertOptionsMap[contactReminderAttr]; ok {
if optRaw.(string) == "" {
optRaw = "0s"
}
d, _ := time.ParseDuration(optRaw.(string))
cg.Reminders[severityIndex] = uint(d.Seconds())
}
}
}
if v, ok := d.GetOk(contactNameAttr); ok {
cg.Name = v.(string)
}
if v, ok := d.GetOk(contactEmailAttr); ok {
emailListRaw := v.(*schema.Set).List()
for _, emailMapRaw := range emailListRaw {
emailMap := emailMapRaw.(map[string]interface{})
var requiredAttrFound bool
if v, ok := emailMap[contactEmailAddressAttr]; ok && v.(string) != "" {
requiredAttrFound = true
cg.Contacts.External = append(cg.Contacts.External, api.ContactGroupContactsExternal{
Info: v.(string),
Method: circonusMethodEmail,
})
}
if v, ok := emailMap[contactUserCIDAttr]; ok && v.(string) != "" {
requiredAttrFound = true
cg.Contacts.Users = append(cg.Contacts.Users, api.ContactGroupContactsUser{
Method: circonusMethodEmail,
UserCID: v.(string),
})
}
// Can't mark two attributes that are conflicting as required so we do our
// own validation check here.
if !requiredAttrFound {
return nil, fmt.Errorf("In type %s, either %s or %s must be specified", contactEmailAttr, contactEmailAddressAttr, contactUserCIDAttr)
}
}
}
if v, ok := d.GetOk(contactHTTPAttr); ok {
httpListRaw := v.(*schema.Set).List()
for _, httpMapRaw := range httpListRaw {
httpMap := httpMapRaw.(map[string]interface{})
httpInfo := contactHTTPInfo{}
if v, ok := httpMap[string(contactHTTPAddressAttr)]; ok {
httpInfo.Address = v.(string)
}
if v, ok := httpMap[string(contactHTTPFormatAttr)]; ok {
httpInfo.Format = v.(string)
}
if v, ok := httpMap[string(contactHTTPMethodAttr)]; ok {
httpInfo.Method = v.(string)
}
js, err := json.Marshal(httpInfo)
if err != nil {
return nil, errwrap.Wrapf(fmt.Sprintf("error marshalling %s JSON config string: {{err}}", contactHTTPAttr), err)
}
cg.Contacts.External = append(cg.Contacts.External, api.ContactGroupContactsExternal{
Info: string(js),
Method: circonusMethodHTTP,
})
}
}
if v, ok := d.GetOk(contactIRCAttr); ok {
ircListRaw := v.(*schema.Set).List()
for _, ircMapRaw := range ircListRaw {
ircMap := ircMapRaw.(map[string]interface{})
if v, ok := ircMap[contactUserCIDAttr]; ok && v.(string) != "" {
cg.Contacts.Users = append(cg.Contacts.Users, api.ContactGroupContactsUser{
Method: circonusMethodIRC,
UserCID: v.(string),
})
}
}
}
if v, ok := d.GetOk(contactPagerDutyAttr); ok {
pagerDutyListRaw := v.(*schema.Set).List()
for _, pagerDutyMapRaw := range pagerDutyListRaw {
pagerDutyMap := pagerDutyMapRaw.(map[string]interface{})
pagerDutyInfo := contactPagerDutyInfo{}
if v, ok := pagerDutyMap[contactContactGroupFallbackAttr]; ok && v.(string) != "" {
cid := v.(string)
contactGroupID, err := failoverGroupCIDToID(api.CIDType(&cid))
if err != nil {
return nil, errwrap.Wrapf("error reading contact group CID: {{err}}", err)
}
pagerDutyInfo.FallbackGroupCID = contactGroupID
}
if v, ok := pagerDutyMap[string(contactPagerDutyServiceKeyAttr)]; ok {
pagerDutyInfo.ServiceKey = v.(string)
}
if v, ok := pagerDutyMap[string(contactPagerDutyWebhookURLAttr)]; ok {
pagerDutyInfo.WebhookURL = v.(string)
}
js, err := json.Marshal(pagerDutyInfo)
if err != nil {
return nil, errwrap.Wrapf(fmt.Sprintf("error marshalling %s JSON config string: {{err}}", contactPagerDutyAttr), err)
}
cg.Contacts.External = append(cg.Contacts.External, api.ContactGroupContactsExternal{
Info: string(js),
Method: circonusMethodPagerDuty,
})
}
}
if v, ok := d.GetOk(contactSlackAttr); ok {
slackListRaw := v.(*schema.Set).List()
for _, slackMapRaw := range slackListRaw {
slackMap := slackMapRaw.(map[string]interface{})
slackInfo := contactSlackInfo{}
var buttons int
if v, ok := slackMap[contactSlackButtonsAttr]; ok {
if v.(bool) {
buttons = 1
}
slackInfo.Buttons = buttons
}
if v, ok := slackMap[contactSlackChannelAttr]; ok {
slackInfo.Channel = v.(string)
}
if v, ok := slackMap[contactContactGroupFallbackAttr]; ok && v.(string) != "" {
cid := v.(string)
contactGroupID, err := failoverGroupCIDToID(api.CIDType(&cid))
if err != nil {
return nil, errwrap.Wrapf("error reading contact group CID: {{err}}", err)
}
slackInfo.FallbackGroupCID = contactGroupID
}
if v, ok := slackMap[contactSlackTeamAttr]; ok {
slackInfo.Team = v.(string)
}
if v, ok := slackMap[contactSlackUsernameAttr]; ok {
slackInfo.Username = v.(string)
}
js, err := json.Marshal(slackInfo)
if err != nil {
return nil, errwrap.Wrapf(fmt.Sprintf("error marshalling %s JSON config string: {{err}}", contactSlackAttr), err)
}
cg.Contacts.External = append(cg.Contacts.External, api.ContactGroupContactsExternal{
Info: string(js),
Method: circonusMethodSlack,
})
}
}
if v, ok := d.GetOk(contactSMSAttr); ok {
smsListRaw := v.(*schema.Set).List()
for _, smsMapRaw := range smsListRaw {
smsMap := smsMapRaw.(map[string]interface{})
var requiredAttrFound bool
if v, ok := smsMap[contactSMSAddressAttr]; ok && v.(string) != "" {
requiredAttrFound = true
cg.Contacts.External = append(cg.Contacts.External, api.ContactGroupContactsExternal{
Info: v.(string),
Method: circonusMethodSMS,
})
}
if v, ok := smsMap[contactUserCIDAttr]; ok && v.(string) != "" {
requiredAttrFound = true
cg.Contacts.Users = append(cg.Contacts.Users, api.ContactGroupContactsUser{
Method: circonusMethodSMS,
UserCID: v.(string),
})
}
// Can't mark two attributes that are conflicting as required so we do our
// own validation check here.
if !requiredAttrFound {
return nil, fmt.Errorf("In type %s, either %s or %s must be specified", contactEmailAttr, contactEmailAddressAttr, contactUserCIDAttr)
}
}
}
if v, ok := d.GetOk(contactVictorOpsAttr); ok {
victorOpsListRaw := v.(*schema.Set).List()
for _, victorOpsMapRaw := range victorOpsListRaw {
victorOpsMap := victorOpsMapRaw.(map[string]interface{})
victorOpsInfo := contactVictorOpsInfo{}
if v, ok := victorOpsMap[contactContactGroupFallbackAttr]; ok && v.(string) != "" {
cid := v.(string)
contactGroupID, err := failoverGroupCIDToID(api.CIDType(&cid))
if err != nil {
return nil, errwrap.Wrapf("error reading contact group CID: {{err}}", err)
}
victorOpsInfo.FallbackGroupCID = contactGroupID
}
if v, ok := victorOpsMap[contactVictorOpsAPIKeyAttr]; ok {
victorOpsInfo.APIKey = v.(string)
}
if v, ok := victorOpsMap[contactVictorOpsCriticalAttr]; ok {
victorOpsInfo.Critical = v.(int)
}
if v, ok := victorOpsMap[contactVictorOpsInfoAttr]; ok {
victorOpsInfo.Info = v.(int)
}
if v, ok := victorOpsMap[contactVictorOpsTeamAttr]; ok {
victorOpsInfo.Team = v.(string)
}
if v, ok := victorOpsMap[contactVictorOpsWarningAttr]; ok {
victorOpsInfo.Warning = v.(int)
}
js, err := json.Marshal(victorOpsInfo)
if err != nil {
return nil, errwrap.Wrapf(fmt.Sprintf("error marshalling %s JSON config string: {{err}}", contactVictorOpsAttr), err)
}
cg.Contacts.External = append(cg.Contacts.External, api.ContactGroupContactsExternal{
Info: string(js),
Method: circonusMethodVictorOps,
})
}
}
if v, ok := d.GetOk(contactXMPPAttr); ok {
xmppListRaw := v.(*schema.Set).List()
for _, xmppMapRaw := range xmppListRaw {
xmppMap := xmppMapRaw.(map[string]interface{})
if v, ok := xmppMap[contactXMPPAddressAttr]; ok && v.(string) != "" {
cg.Contacts.External = append(cg.Contacts.External, api.ContactGroupContactsExternal{
Info: v.(string),
Method: circonusMethodXMPP,
})
}
if v, ok := xmppMap[contactUserCIDAttr]; ok && v.(string) != "" {
cg.Contacts.Users = append(cg.Contacts.Users, api.ContactGroupContactsUser{
Method: circonusMethodXMPP,
UserCID: v.(string),
})
}
}
}
if v, ok := d.GetOk(contactLongMessageAttr); ok {
msg := v.(string)
cg.AlertFormats.LongMessage = &msg
}
if v, ok := d.GetOk(contactLongSubjectAttr); ok {
msg := v.(string)
cg.AlertFormats.LongSubject = &msg
}
if v, ok := d.GetOk(contactLongSummaryAttr); ok {
msg := v.(string)
cg.AlertFormats.LongSummary = &msg
}
if v, ok := d.GetOk(contactShortMessageAttr); ok {
msg := v.(string)
cg.AlertFormats.ShortMessage = &msg
}
if v, ok := d.GetOk(contactShortSummaryAttr); ok {
msg := v.(string)
cg.AlertFormats.ShortSummary = &msg
}
if v, ok := d.GetOk(contactShortMessageAttr); ok {
msg := v.(string)
cg.AlertFormats.ShortMessage = &msg
}
if v, found := d.GetOk(checkTagsAttr); found {
cg.Tags = derefStringList(flattenSet(v.(*schema.Set)))
}
if err := validateContactGroup(cg); err != nil {
return nil, err
}
return cg, nil
}
func contactGroupIRCToState(cg *api.ContactGroup) []interface{} {
ircContacts := make([]interface{}, 0, len(cg.Contacts.Users))
for _, user := range cg.Contacts.Users {
switch user.Method {
case circonusMethodIRC:
ircContacts = append(ircContacts, map[string]interface{}{
contactUserCIDAttr: user.UserCID,
})
}
}
return ircContacts
}
func contactGroupPagerDutyToState(cg *api.ContactGroup) ([]interface{}, error) {
pdContacts := make([]interface{}, 0, len(cg.Contacts.External))
for _, ext := range cg.Contacts.External {
switch ext.Method {
case circonusMethodPagerDuty:
pdInfo := contactPagerDutyInfo{}
if err := json.Unmarshal([]byte(ext.Info), &pdInfo); err != nil {
return nil, errwrap.Wrapf(fmt.Sprintf("unable to decode external %s JSON (%q): {{err}}", contactPagerDutyAttr, ext.Info), err)
}
pdContacts = append(pdContacts, map[string]interface{}{
string(contactContactGroupFallbackAttr): failoverGroupIDToCID(pdInfo.FallbackGroupCID),
string(contactPagerDutyServiceKeyAttr): pdInfo.ServiceKey,
string(contactPagerDutyWebhookURLAttr): pdInfo.WebhookURL,
})
}
}
return pdContacts, nil
}
func contactGroupSlackToState(cg *api.ContactGroup) ([]interface{}, error) {
slackContacts := make([]interface{}, 0, len(cg.Contacts.External))
for _, ext := range cg.Contacts.External {
switch ext.Method {
case circonusMethodSlack:
slackInfo := contactSlackInfo{}
if err := json.Unmarshal([]byte(ext.Info), &slackInfo); err != nil {
return nil, errwrap.Wrapf(fmt.Sprintf("unable to decode external %s JSON (%q): {{err}}", contactSlackAttr, ext.Info), err)
}
slackContacts = append(slackContacts, map[string]interface{}{
contactContactGroupFallbackAttr: failoverGroupIDToCID(slackInfo.FallbackGroupCID),
contactSlackButtonsAttr: int(slackInfo.Buttons) == int(1),
contactSlackChannelAttr: slackInfo.Channel,
contactSlackTeamAttr: slackInfo.Team,
contactSlackUsernameAttr: slackInfo.Username,
})
}
}
return slackContacts, nil
}
func contactGroupSMSToState(cg *api.ContactGroup) ([]interface{}, error) {
smsContacts := make([]interface{}, 0, len(cg.Contacts.Users)+len(cg.Contacts.External))
for _, ext := range cg.Contacts.External {
switch ext.Method {
case circonusMethodSMS:
smsContacts = append(smsContacts, map[string]interface{}{
contactSMSAddressAttr: ext.Info,
})
}
}
for _, user := range cg.Contacts.Users {
switch user.Method {
case circonusMethodSMS:
smsContacts = append(smsContacts, map[string]interface{}{
contactUserCIDAttr: user.UserCID,
})
}
}
return smsContacts, nil
}
func contactGroupVictorOpsToState(cg *api.ContactGroup) ([]interface{}, error) {
victorOpsContacts := make([]interface{}, 0, len(cg.Contacts.External))
for _, ext := range cg.Contacts.External {
switch ext.Method {
case circonusMethodVictorOps:
victorOpsInfo := contactVictorOpsInfo{}
if err := json.Unmarshal([]byte(ext.Info), &victorOpsInfo); err != nil {
return nil, errwrap.Wrapf(fmt.Sprintf("unable to decode external %s JSON (%q): {{err}}", contactVictorOpsInfoAttr, ext.Info), err)
}
victorOpsContacts = append(victorOpsContacts, map[string]interface{}{
contactContactGroupFallbackAttr: failoverGroupIDToCID(victorOpsInfo.FallbackGroupCID),
contactVictorOpsAPIKeyAttr: victorOpsInfo.APIKey,
contactVictorOpsCriticalAttr: victorOpsInfo.Critical,
contactVictorOpsInfoAttr: victorOpsInfo.Info,
contactVictorOpsTeamAttr: victorOpsInfo.Team,
contactVictorOpsWarningAttr: victorOpsInfo.Warning,
})
}
}
return victorOpsContacts, nil
}
func contactGroupXMPPToState(cg *api.ContactGroup) ([]interface{}, error) {
xmppContacts := make([]interface{}, 0, len(cg.Contacts.Users)+len(cg.Contacts.External))
for _, ext := range cg.Contacts.External {
switch ext.Method {
case circonusMethodXMPP:
xmppContacts = append(xmppContacts, map[string]interface{}{
contactXMPPAddressAttr: ext.Info,
})
}
}
for _, user := range cg.Contacts.Users {
switch user.Method {
case circonusMethodXMPP:
xmppContacts = append(xmppContacts, map[string]interface{}{
contactUserCIDAttr: user.UserCID,
})
}
}
return xmppContacts, nil
}
// contactGroupAlertOptionsChecksum creates a stable hash of the normalized values
func contactGroupAlertOptionsChecksum(v interface{}) int {
m := v.(map[string]interface{})
b := &bytes.Buffer{}
b.Grow(defaultHashBufSize)
fmt.Fprintf(b, "%x", m[contactSeverityAttr].(int))
fmt.Fprint(b, normalizeTimeDurationStringToSeconds(m[contactEscalateAfterAttr]))
fmt.Fprint(b, m[contactEscalateToAttr])
fmt.Fprint(b, normalizeTimeDurationStringToSeconds(m[contactReminderAttr]))
return hashcode.String(b.String())
}