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 }