From 7b09a52589907b9628622c866b66eaad25d78e3e Mon Sep 17 00:00:00 2001 From: Carlos Gajardo Date: Fri, 4 Oct 2024 10:23:01 -0300 Subject: [PATCH] Add resource and data source for Alert Grouping Setting --- go.mod | 4 +- go.sum | 8 +- pagerduty/resource_pagerduty_service.go | 1 + ...source_pagerduty_alert_grouping_setting.go | 110 ++++ ...e_pagerduty_alert_grouping_setting_test.go | 131 +++++ pagerdutyplugin/provider.go | 2 + ...source_pagerduty_alert_grouping_setting.go | 511 ++++++++++++++++++ ...e_pagerduty_alert_grouping_setting_test.go | 409 ++++++++++++++ .../go-pagerduty/alert_grouping_setting.go | 215 ++++++++ .../setvalidator/all.go | 57 ++ .../setvalidator/also_requires.go | 26 + .../setvalidator/any.go | 65 +++ .../setvalidator/any_with_all_warnings.go | 67 +++ .../setvalidator/at_least_one_of.go | 27 + .../setvalidator/conflicts_with.go | 27 + .../setvalidator/doc.go | 5 + .../setvalidator/exactly_one_of.go | 28 + .../setvalidator/is_required.go | 44 ++ .../setvalidator/size_at_least.go | 59 ++ .../setvalidator/size_at_most.go | 59 ++ .../setvalidator/size_between.go | 62 +++ .../setvalidator/value_float64s_are.go | 119 ++++ .../setvalidator/value_int64s_are.go | 119 ++++ .../setvalidator/value_lists_are.go | 119 ++++ .../setvalidator/value_maps_are.go | 119 ++++ .../setvalidator/value_numbers_are.go | 119 ++++ .../setvalidator/value_sets_are.go | 119 ++++ .../setvalidator/value_strings_are.go | 119 ++++ vendor/modules.txt | 5 +- .../d/alert_grouping_setting.html.markdown | 43 ++ .../r/alert_grouping_setting.html.markdown | 226 ++++++++ website/docs/r/service.html.markdown | 4 +- 32 files changed, 3018 insertions(+), 10 deletions(-) create mode 100644 pagerdutyplugin/data_source_pagerduty_alert_grouping_setting.go create mode 100644 pagerdutyplugin/data_source_pagerduty_alert_grouping_setting_test.go create mode 100644 pagerdutyplugin/resource_pagerduty_alert_grouping_setting.go create mode 100644 pagerdutyplugin/resource_pagerduty_alert_grouping_setting_test.go create mode 100644 vendor/github.com/PagerDuty/go-pagerduty/alert_grouping_setting.go create mode 100644 vendor/github.com/hashicorp/terraform-plugin-framework-validators/setvalidator/all.go create mode 100644 vendor/github.com/hashicorp/terraform-plugin-framework-validators/setvalidator/also_requires.go create mode 100644 vendor/github.com/hashicorp/terraform-plugin-framework-validators/setvalidator/any.go create mode 100644 vendor/github.com/hashicorp/terraform-plugin-framework-validators/setvalidator/any_with_all_warnings.go create mode 100644 vendor/github.com/hashicorp/terraform-plugin-framework-validators/setvalidator/at_least_one_of.go create mode 100644 vendor/github.com/hashicorp/terraform-plugin-framework-validators/setvalidator/conflicts_with.go create mode 100644 vendor/github.com/hashicorp/terraform-plugin-framework-validators/setvalidator/doc.go create mode 100644 vendor/github.com/hashicorp/terraform-plugin-framework-validators/setvalidator/exactly_one_of.go create mode 100644 vendor/github.com/hashicorp/terraform-plugin-framework-validators/setvalidator/is_required.go create mode 100644 vendor/github.com/hashicorp/terraform-plugin-framework-validators/setvalidator/size_at_least.go create mode 100644 vendor/github.com/hashicorp/terraform-plugin-framework-validators/setvalidator/size_at_most.go create mode 100644 vendor/github.com/hashicorp/terraform-plugin-framework-validators/setvalidator/size_between.go create mode 100644 vendor/github.com/hashicorp/terraform-plugin-framework-validators/setvalidator/value_float64s_are.go create mode 100644 vendor/github.com/hashicorp/terraform-plugin-framework-validators/setvalidator/value_int64s_are.go create mode 100644 vendor/github.com/hashicorp/terraform-plugin-framework-validators/setvalidator/value_lists_are.go create mode 100644 vendor/github.com/hashicorp/terraform-plugin-framework-validators/setvalidator/value_maps_are.go create mode 100644 vendor/github.com/hashicorp/terraform-plugin-framework-validators/setvalidator/value_numbers_are.go create mode 100644 vendor/github.com/hashicorp/terraform-plugin-framework-validators/setvalidator/value_sets_are.go create mode 100644 vendor/github.com/hashicorp/terraform-plugin-framework-validators/setvalidator/value_strings_are.go create mode 100644 website/docs/d/alert_grouping_setting.html.markdown create mode 100644 website/docs/r/alert_grouping_setting.html.markdown diff --git a/go.mod b/go.mod index 297000484..8809fb6c5 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/PagerDuty/terraform-provider-pagerduty go 1.20 require ( - github.com/PagerDuty/go-pagerduty v1.8.1-0.20240524180345-9b652f07c450 + github.com/PagerDuty/go-pagerduty v1.8.1-0.20241002154647-8ceedfd04d88 github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320 github.com/hashicorp/go-version v1.6.0 github.com/hashicorp/hc-install v0.6.2 @@ -66,7 +66,7 @@ require ( golang.org/x/exp v0.0.0-20230809150735-7b3493d9a819 // indirect golang.org/x/mod v0.14.0 // indirect golang.org/x/net v0.22.0 // indirect - golang.org/x/oauth2 v0.18.0 // indirect + golang.org/x/oauth2 v0.15.0 // indirect golang.org/x/sync v0.5.0 // indirect golang.org/x/sys v0.18.0 // indirect golang.org/x/text v0.14.0 // indirect diff --git a/go.sum b/go.sum index fcd0790d5..9fca179c9 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,7 @@ dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= -github.com/PagerDuty/go-pagerduty v1.8.1-0.20240524180345-9b652f07c450 h1:G8EOmgL7i+vO6hYAnjpVbPDqUpPY7ThF6NfasZntop0= -github.com/PagerDuty/go-pagerduty v1.8.1-0.20240524180345-9b652f07c450/go.mod h1:ilimTqwHSBjmvKeYA/yayDBZvzf/CX4Pwa9Qbhekzok= +github.com/PagerDuty/go-pagerduty v1.8.1-0.20241002154647-8ceedfd04d88 h1:y/icahuphX4xGMW4nLN+Bl4MbFUU4rEA9spwgcPIDJk= +github.com/PagerDuty/go-pagerduty v1.8.1-0.20241002154647-8ceedfd04d88/go.mod h1:ilimTqwHSBjmvKeYA/yayDBZvzf/CX4Pwa9Qbhekzok= github.com/ProtonMail/go-crypto v0.0.0-20230923063757-afb1ddc0824c h1:kMFnB0vCcX7IL/m9Y5LO+KQYv+t1CQOiFe6+SV2J7bE= github.com/ProtonMail/go-crypto v0.0.0-20230923063757-afb1ddc0824c/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0= github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo= @@ -189,8 +189,8 @@ golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc= golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= -golang.org/x/oauth2 v0.18.0 h1:09qnuIAgzdx1XplqJvW6CQqMCtGZykZWcXzPMPUusvI= -golang.org/x/oauth2 v0.18.0/go.mod h1:Wf7knwG0MPoWIMMBgFlEaSUDaKskp0dCfrlJRJXbBi8= +golang.org/x/oauth2 v0.15.0 h1:s8pnnxNVzjWyrvYdFUQq5llS1PX2zhPXmccZv99h7uQ= +golang.org/x/oauth2 v0.15.0/go.mod h1:q48ptWNTY5XWf+JNten23lcvHpLJ0ZSxF5ttTHKVCAM= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= diff --git a/pagerduty/resource_pagerduty_service.go b/pagerduty/resource_pagerduty_service.go index 6d34057c1..074454999 100644 --- a/pagerduty/resource_pagerduty_service.go +++ b/pagerduty/resource_pagerduty_service.go @@ -78,6 +78,7 @@ func resourcePagerDutyService() *schema.Resource { Optional: true, Computed: true, MaxItems: 1, + Deprecated: "Use a resource `pagerduty_alert_grouping_setting` instead", ConflictsWith: []string{"alert_grouping", "alert_grouping_timeout"}, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ diff --git a/pagerdutyplugin/data_source_pagerduty_alert_grouping_setting.go b/pagerdutyplugin/data_source_pagerduty_alert_grouping_setting.go new file mode 100644 index 000000000..34218ee94 --- /dev/null +++ b/pagerdutyplugin/data_source_pagerduty_alert_grouping_setting.go @@ -0,0 +1,110 @@ +package pagerduty + +import ( + "context" + "fmt" + "log" + + "github.com/PagerDuty/go-pagerduty" + "github.com/PagerDuty/terraform-provider-pagerduty/util/apiutil" + "github.com/hashicorp/terraform-plugin-framework/datasource" + "github.com/hashicorp/terraform-plugin-framework/datasource/schema" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +type dataSourceAlertGroupingSetting struct{ client *pagerduty.Client } + +var _ datasource.DataSourceWithConfigure = (*dataSourceAlertGroupingSetting)(nil) + +func (*dataSourceAlertGroupingSetting) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { + resp.TypeName = "pagerduty_alert_grouping_setting" +} + +func (*dataSourceAlertGroupingSetting) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{Computed: true}, + "name": schema.StringAttribute{Required: true}, + "description": schema.StringAttribute{Computed: true}, + "type": schema.StringAttribute{Computed: true}, + "services": schema.SetAttribute{ + Computed: true, + ElementType: types.StringType, + }, + }, + Blocks: map[string]schema.Block{ + "config": schema.SingleNestedBlock{ + Attributes: map[string]schema.Attribute{ + "timeout": schema.Int64Attribute{ + Computed: true, + }, + "time_window": schema.Int64Attribute{ + Computed: true, + }, + "aggregate": schema.StringAttribute{ + Optional: true, + }, + "fields": schema.SetAttribute{ + ElementType: types.StringType, + Optional: true, + }, + }, + }, + }, + } +} + +func (d *dataSourceAlertGroupingSetting) Configure(ctx context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) { + resp.Diagnostics.Append(ConfigurePagerdutyClient(&d.client, req.ProviderData)...) +} + +func (d *dataSourceAlertGroupingSetting) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { + log.Println("[INFO] Reading PagerDuty alert grouping setting") + + var searchName types.String + resp.Diagnostics.Append(req.Config.GetAttribute(ctx, path.Root("name"), &searchName)...) + if resp.Diagnostics.HasError() { + return + } + + var cursorAfter, cursorBefore string + var found *pagerduty.AlertGroupingSetting + err := apiutil.All(ctx, func(offset int) (bool, error) { + resp, err := d.client.ListAlertGroupingSettings(ctx, pagerduty.ListAlertGroupingSettingsOptions{ + After: cursorAfter, + Before: cursorBefore, + Limit: 100, + }) + if err != nil { + return false, err + } + + for _, alertGroupingSetting := range resp.AlertGroupingSettings { + if alertGroupingSetting.Name == searchName.ValueString() { + found = &alertGroupingSetting + break + } + } + + return resp.After != "", nil + }) + if err != nil { + resp.Diagnostics.AddError( + fmt.Sprintf("Error reading PagerDuty alert grouping setting %s", searchName), + err.Error(), + ) + return + } + + if found == nil { + resp.Diagnostics.AddError( + fmt.Sprintf("Unable to locate any alert grouping setting with the name: %s", searchName), + "", + ) + return + } + + model := flattenAlertGroupingSetting(found) + resp.Diagnostics.Append(resp.State.Set(ctx, &model)...) +} diff --git a/pagerdutyplugin/data_source_pagerduty_alert_grouping_setting_test.go b/pagerdutyplugin/data_source_pagerduty_alert_grouping_setting_test.go new file mode 100644 index 000000000..f26f9361f --- /dev/null +++ b/pagerdutyplugin/data_source_pagerduty_alert_grouping_setting_test.go @@ -0,0 +1,131 @@ +package pagerduty + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/terraform" +) + +func TestAccDataSourcePagerDutyAlertGroupingSetting_Time(t *testing.T) { + name := fmt.Sprintf("tf-%s", acctest.RandString(5)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV5ProviderFactories: testAccProtoV5ProviderFactories(), + Steps: []resource.TestStep{ + { + Config: testAccDataSourcePagerDutyAlertGroupingSettingTimeConfig(name), + Check: resource.ComposeTestCheckFunc( + testAccDataSourcePagerDutyAlertGroupingSetting("pagerduty_alert_grouping_setting.test", "data.pagerduty_alert_grouping_setting.by_name"), + resource.TestCheckResourceAttr("data.pagerduty_alert_grouping_setting.by_name", "name", name), + resource.TestCheckResourceAttr("data.pagerduty_alert_grouping_setting.by_name", "type", "time"), + resource.TestCheckResourceAttrSet("data.pagerduty_alert_grouping_setting.by_name", "description"), + resource.TestCheckResourceAttr("data.pagerduty_alert_grouping_setting.by_name", "services.#", "1"), + ), + }, + }, + }) +} + +func TestAccDataSourcePagerDutyAlertGroupingSetting_ContentBased(t *testing.T) { + name := fmt.Sprintf("tf-%s", acctest.RandString(5)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV5ProviderFactories: testAccProtoV5ProviderFactories(), + Steps: []resource.TestStep{ + { + Config: testAccDataSourcePagerDutyAlertGroupingSettingContentBasedConfig(name), + Check: resource.ComposeTestCheckFunc( + testAccDataSourcePagerDutyAlertGroupingSetting("pagerduty_alert_grouping_setting.test", "data.pagerduty_alert_grouping_setting.by_name"), + resource.TestCheckResourceAttr("data.pagerduty_alert_grouping_setting.by_name", "name", name), + resource.TestCheckResourceAttr("data.pagerduty_alert_grouping_setting.by_name", "type", "content_based"), + resource.TestCheckResourceAttrSet("data.pagerduty_alert_grouping_setting.by_name", "description"), + resource.TestCheckResourceAttr("data.pagerduty_alert_grouping_setting.by_name", "services.#", "1"), + resource.TestCheckResourceAttr("data.pagerduty_alert_grouping_setting.by_name", "config.time_window", "300"), + resource.TestCheckResourceAttr("data.pagerduty_alert_grouping_setting.by_name", "config.aggregate", "any"), + resource.TestCheckResourceAttr("data.pagerduty_alert_grouping_setting.by_name", "config.fields.#", "1"), + resource.TestCheckResourceAttr("data.pagerduty_alert_grouping_setting.by_name", "config.fields.0", "summary"), + ), + }, + }, + }) +} + +func testAccDataSourcePagerDutyAlertGroupingSetting(src, n string) resource.TestCheckFunc { + return func(s *terraform.State) error { + srcR := s.RootModule().Resources[src] + srcA := srcR.Primary.Attributes + + r := s.RootModule().Resources[n] + a := r.Primary.Attributes + + if a["id"] == "" { + return fmt.Errorf("Expected to get a alert grouping setting ID from PagerDuty") + } + + testAtts := []string{"id", "name"} + + for _, att := range testAtts { + if a[att] != srcA[att] { + return fmt.Errorf("Expected the alert grouping setting %s to be: %s, but got: %s", att, srcA[att], a[att]) + } + } + + return nil + } +} + +func testAccDataSourcePagerDutyAlertGroupingSettingTimeConfig(name string) string { + return fmt.Sprintf(` +data "pagerduty_escalation_policy" "test" { + name = "Default" +} + +resource "pagerduty_service" "test" { + name = "%s" + escalation_policy = data.pagerduty_escalation_policy.test.id +} + +resource "pagerduty_alert_grouping_setting" "test" { + name = "%[1]s" + type = "time" + services = [pagerduty_service.test.id] + config {} +} + +data "pagerduty_alert_grouping_setting" "by_name" { + name = pagerduty_alert_grouping_setting.test.name +} +`, name) +} + +func testAccDataSourcePagerDutyAlertGroupingSettingContentBasedConfig(name string) string { + return fmt.Sprintf(` +data "pagerduty_escalation_policy" "test" { + name = "Default" +} + +resource "pagerduty_service" "test" { + name = "%s" + escalation_policy = data.pagerduty_escalation_policy.test.id +} + +resource "pagerduty_alert_grouping_setting" "test" { + name = "%[1]s" + type = "content_based" + services = [pagerduty_service.test.id] + config { + aggregate = "any" + fields = ["summary"] + } +} + +data "pagerduty_alert_grouping_setting" "by_name" { + name = pagerduty_alert_grouping_setting.test.name +} +`, name) +} diff --git a/pagerdutyplugin/provider.go b/pagerdutyplugin/provider.go index 4a6f7e795..1e1050af9 100644 --- a/pagerdutyplugin/provider.go +++ b/pagerdutyplugin/provider.go @@ -52,6 +52,7 @@ func (p *Provider) Schema(_ context.Context, _ provider.SchemaRequest, resp *pro func (p *Provider) DataSources(_ context.Context) [](func() datasource.DataSource) { return [](func() datasource.DataSource){ + func() datasource.DataSource { return &dataSourceAlertGroupingSetting{} }, func() datasource.DataSource { return &dataSourceBusinessService{} }, func() datasource.DataSource { return &dataSourceExtensionSchema{} }, func() datasource.DataSource { return &dataSourceIntegration{} }, @@ -69,6 +70,7 @@ func (p *Provider) DataSources(_ context.Context) [](func() datasource.DataSourc func (p *Provider) Resources(_ context.Context) [](func() resource.Resource) { return [](func() resource.Resource){ func() resource.Resource { return &resourceAddon{} }, + func() resource.Resource { return &resourceAlertGroupingSetting{} }, func() resource.Resource { return &resourceBusinessService{} }, func() resource.Resource { return &resourceExtensionServiceNow{} }, func() resource.Resource { return &resourceExtension{} }, diff --git a/pagerdutyplugin/resource_pagerduty_alert_grouping_setting.go b/pagerdutyplugin/resource_pagerduty_alert_grouping_setting.go new file mode 100644 index 000000000..0e5f1f834 --- /dev/null +++ b/pagerdutyplugin/resource_pagerduty_alert_grouping_setting.go @@ -0,0 +1,511 @@ +package pagerduty + +import ( + "context" + "fmt" + "log" + "time" + + "github.com/PagerDuty/go-pagerduty" + "github.com/PagerDuty/terraform-provider-pagerduty/util" + "github.com/hashicorp/terraform-plugin-framework-validators/int64validator" + "github.com/hashicorp/terraform-plugin-framework-validators/setvalidator" + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/setplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringdefault" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-framework/types/basetypes" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry" +) + +type resourceAlertGroupingSetting struct{ client *pagerduty.Client } + +var ( + _ resource.ResourceWithConfigure = (*resourceAlertGroupingSetting)(nil) + _ resource.ResourceWithImportState = (*resourceAlertGroupingSetting)(nil) + _ resource.ResourceWithValidateConfig = (*resourceAlertGroupingSetting)(nil) +) + +func (r *resourceAlertGroupingSetting) Metadata(_ context.Context, _ resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = "pagerduty_alert_grouping_setting" +} + +func (r *resourceAlertGroupingSetting) ValidateConfig(ctx context.Context, req resource.ValidateConfigRequest, resp *resource.ValidateConfigResponse) { + var model resourceAlertGroupingSettingModel + + resp.Diagnostics.Append(req.Config.Get(ctx, &model)...) + if resp.Diagnostics.HasError() { + return + } + + t := pagerduty.AlertGroupingSettingType(model.Type.ValueString()) + + if t == pagerduty.AlertGroupingSettingContentBasedIntelligentType || t == pagerduty.AlertGroupingSettingIntelligentType || t == pagerduty.AlertGroupingSettingTimeType { + if len(model.Services.Elements()) > 1 { + resp.Diagnostics.AddAttributeError( + path.Root("services"), + "Invalid configuration", + fmt.Sprintf("Setting of type %q allows for only one service in the array", t), + ) + return + } + } +} + +func (r *resourceAlertGroupingSetting) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "name": schema.StringAttribute{ + Required: true, + }, + "description": schema.StringAttribute{ + Computed: true, + Optional: true, + Default: stringdefault.StaticString("Managed by Terraform"), + }, + "type": schema.StringAttribute{ + Required: true, + Validators: []validator.String{ + stringvalidator.OneOf( + "content_based", + "content_based_intelligent", + "intelligent", + "time", + ), + }, + }, + "services": schema.SetAttribute{ + ElementType: types.StringType, + Required: true, + Validators: []validator.Set{ + setvalidator.SizeAtLeast(1), + }, + PlanModifiers: []planmodifier.Set{ + setplanmodifier.RequiresReplaceIf( + checkAlertGroupingSettingServicesRequiresReplace, + "Requires replace when no service from previous configuration was reused.", + "Requires replace when no service from previous configuration was reused.", + ), + }, + }, + }, + Blocks: map[string]schema.Block{ + "config": schema.SingleNestedBlock{ + Attributes: map[string]schema.Attribute{ + "timeout": schema.Int64Attribute{ + Optional: true, + Computed: true, + Validators: []validator.Int64{int64validator.NoneOf(0)}, + }, + "time_window": schema.Int64Attribute{ + Optional: true, + Computed: true, + Validators: []validator.Int64{int64validator.NoneOf(0)}, + }, + "aggregate": schema.StringAttribute{ + Optional: true, + }, + "fields": schema.SetAttribute{ + ElementType: types.StringType, + Optional: true, + }, + }, + }, + }, + } +} + +func (r *resourceAlertGroupingSetting) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var model resourceAlertGroupingSettingModel + + resp.Diagnostics.Append(req.Plan.Get(ctx, &model)...) + if resp.Diagnostics.HasError() { + return + } + plan := buildPagerdutyAlertGroupingSetting(ctx, &model, &resp.Diagnostics) + log.Printf("[INFO] Creating PagerDuty alert grouping setting %s", plan.Name) + + r.validateServicesReuse(ctx, plan, &resp.Diagnostics) + if resp.Diagnostics.HasError() { + return + } + + err := retry.RetryContext(ctx, 2*time.Minute, func() *retry.RetryError { + response, err := r.client.CreateAlertGroupingSetting(ctx, plan) + if err != nil { + return retry.RetryableError(err) + } + plan.ID = response.ID + return nil + }) + if err != nil { + resp.Diagnostics.AddError( + fmt.Sprintf("Error creating PagerDuty alert grouping setting %s", plan.Name), + err.Error(), + ) + return + } + + model, err = requestGetAlertGroupingSetting(ctx, r.client, plan.ID, true, &resp.Diagnostics) + if err != nil { + resp.Diagnostics.AddError( + fmt.Sprintf("Error reading PagerDuty alert grouping setting %s", plan.ID), + err.Error(), + ) + return + } + + resp.Diagnostics.Append(resp.State.Set(ctx, &model)...) +} + +func (r *resourceAlertGroupingSetting) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + var id types.String + + resp.Diagnostics.Append(req.State.GetAttribute(ctx, path.Root("id"), &id)...) + if resp.Diagnostics.HasError() { + return + } + log.Printf("[INFO] Reading PagerDuty alert grouping setting %s", id) + + state, err := requestGetAlertGroupingSetting(ctx, r.client, id.ValueString(), false, &resp.Diagnostics) + if err != nil { + if util.IsNotFoundError(err) { + resp.State.RemoveResource(ctx) + return + } + resp.Diagnostics.AddError( + fmt.Sprintf("Error reading PagerDuty alert grouping setting %s", id), + err.Error(), + ) + return + } + resp.Diagnostics.Append(resp.State.Set(ctx, state)...) +} + +func (r *resourceAlertGroupingSetting) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var model resourceAlertGroupingSettingModel + + resp.Diagnostics.Append(req.Plan.Get(ctx, &model)...) + if resp.Diagnostics.HasError() { + return + } + + plan := buildPagerdutyAlertGroupingSetting(ctx, &model, &resp.Diagnostics) + log.Printf("[INFO] Updating PagerDuty alert grouping setting %s", plan.ID) + + r.validateServicesReuse(ctx, plan, &resp.Diagnostics) + if resp.Diagnostics.HasError() { + return + } + + alertGroupingSetting, err := r.client.UpdateAlertGroupingSetting(ctx, plan) + if err != nil { + if util.IsNotFoundError(err) { + resp.State.RemoveResource(ctx) + return + } + resp.Diagnostics.AddError( + fmt.Sprintf("Error creating PagerDuty alert grouping setting %s", plan.Name), + err.Error(), + ) + return + } + model = flattenAlertGroupingSetting(alertGroupingSetting) + + resp.Diagnostics.Append(resp.State.Set(ctx, &model)...) +} + +func (r *resourceAlertGroupingSetting) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + var id types.String + + resp.Diagnostics.Append(req.State.GetAttribute(ctx, path.Root("id"), &id)...) + if resp.Diagnostics.HasError() { + return + } + log.Printf("[INFO] Deleting PagerDuty alert grouping setting %s", id) + + err := r.client.DeleteAlertGroupingSetting(ctx, id.ValueString()) + if err != nil && !util.IsNotFoundError(err) { + resp.Diagnostics.AddError( + fmt.Sprintf("Error deleting PagerDuty alert grouping setting %s", id), + err.Error(), + ) + return + } + resp.State.RemoveResource(ctx) +} + +func (r *resourceAlertGroupingSetting) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { + resp.Diagnostics.Append(ConfigurePagerdutyClient(&r.client, req.ProviderData)...) +} + +func (r *resourceAlertGroupingSetting) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { + resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp) +} + +func (r *resourceAlertGroupingSetting) validateServicesReuse(ctx context.Context, plan pagerduty.AlertGroupingSetting, diags *diag.Diagnostics) { + serviceIDs := make([]string, len(plan.Services)) + for i, s := range plan.Services { + serviceIDs[i] = s.ID + } + + list, err := r.client.ListAlertGroupingSettings(ctx, pagerduty.ListAlertGroupingSettingsOptions{ + ServiceIDs: serviceIDs, + }) + if err != nil { + diags.AddError( + "Unable to obtain list of alert grouping settings", + err.Error(), + ) + return + } + + var reused []pagerduty.AlertGroupingSetting + if plan.ID == "" { + for _, a := range list.AlertGroupingSettings { + reused = append(reused, a) + } + } else { + for _, a := range list.AlertGroupingSettings { + if a.ID != plan.ID { + reused = append(reused, a) + } + } + } + + if len(reused) > 0 { + for _, a := range reused { + type usage struct { + At int + By, ByID string + } + bad := []usage{} + for _, s := range a.Services { + for i, sid := range serviceIDs { + if s.ID == sid { + bad = append(bad, usage{ + At: i, + By: a.Name, + ByID: a.ID, + }) + } + } + } + for _, b := range bad { + var agsString string + if b.By == "" { + agsString = fmt.Sprintf("id=%s", b.ByID) + } else { + agsString = fmt.Sprintf("%q [id=%s]", b.By, b.ByID) + } + diags.AddAttributeError( + path.Root("services").AtListIndex(b.At), + "This service is associated to another alert grouping setting", + fmt.Sprintf("Alert grouping setting %s", agsString), + ) + } + } + } +} + +type resourceAlertGroupingSettingModel struct { + ID types.String `tfsdk:"id"` + Name types.String `tfsdk:"name"` + Description types.String `tfsdk:"description"` + Type types.String `tfsdk:"type"` + Config types.Object `tfsdk:"config"` + Services types.Set `tfsdk:"services"` +} + +func requestGetAlertGroupingSetting(ctx context.Context, client *pagerduty.Client, id string, retryNotFound bool, diags *diag.Diagnostics) (resourceAlertGroupingSettingModel, error) { + var model resourceAlertGroupingSettingModel + + err := retry.RetryContext(ctx, 2*time.Minute, func() *retry.RetryError { + alertGroupingSetting, err := client.GetAlertGroupingSetting(ctx, id) + if err != nil { + if util.IsBadRequestError(err) { + return retry.NonRetryableError(err) + } + if !retryNotFound && util.IsNotFoundError(err) { + return retry.NonRetryableError(err) + } + return retry.RetryableError(err) + } + model = flattenAlertGroupingSetting(alertGroupingSetting) + return nil + }) + + return model, err +} + +func buildPagerdutyAlertGroupingSetting(ctx context.Context, model *resourceAlertGroupingSettingModel, diags *diag.Diagnostics) pagerduty.AlertGroupingSetting { + alertGroupingSetting := pagerduty.AlertGroupingSetting{ + ID: model.ID.ValueString(), + Name: model.Name.ValueString(), + Description: model.Description.ValueString(), + Type: pagerduty.AlertGroupingSettingType(model.Type.ValueString()), + Config: buildPagerdutyAlertGroupingSettingConfig(ctx, model, diags), + Services: buildPagerdutyAlertGroupingSettingServices(model), + } + return alertGroupingSetting +} + +func buildPagerdutyAlertGroupingSettingConfig(ctx context.Context, model *resourceAlertGroupingSettingModel, diags *diag.Diagnostics) interface{} { + var target struct { + Timeout types.Int64 `tfsdk:"timeout"` + TimeWindow types.Int64 `tfsdk:"time_window"` + Aggregate types.String `tfsdk:"aggregate"` + Fields types.Set `tfsdk:"fields"` + } + + switch model.Type.ValueString() { + case string(pagerduty.AlertGroupingSettingContentBasedType), string(pagerduty.AlertGroupingSettingContentBasedIntelligentType): + d := model.Config.As(ctx, &target, basetypes.ObjectAsOptions{UnhandledNullAsEmpty: true}) + if diags.Append(d...); d.HasError() { + return pagerduty.AlertGroupingSettingConfigContentBased{} + } + fields := []string{} + diags.Append(target.Fields.ElementsAs(ctx, &fields, false)...) + return pagerduty.AlertGroupingSettingConfigContentBased{ + TimeWindow: uint(target.TimeWindow.ValueInt64()), + Aggregate: target.Aggregate.ValueString(), + Fields: fields, + } + + case string(pagerduty.AlertGroupingSettingIntelligentType): + diags.Append(model.Config.As(ctx, &target, basetypes.ObjectAsOptions{})...) + return pagerduty.AlertGroupingSettingConfigIntelligent{ + TimeWindow: uint(target.TimeWindow.ValueInt64()), + } + + case string(pagerduty.AlertGroupingSettingTimeType): + diags.Append(model.Config.As(ctx, &target, basetypes.ObjectAsOptions{})...) + return pagerduty.AlertGroupingSettingConfigTime{ + Timeout: uint(target.Timeout.ValueInt64()), + } + } + + return nil +} + +func buildPagerdutyAlertGroupingSettingServices(model *resourceAlertGroupingSettingModel) []pagerduty.AlertGroupingSettingService { + elements := model.Services.Elements() + list := make([]pagerduty.AlertGroupingSettingService, 0, len(elements)) + for _, e := range elements { + v, _ := e.(types.String) + list = append(list, pagerduty.AlertGroupingSettingService{ID: v.ValueString()}) + } + return list +} + +func flattenAlertGroupingSetting(response *pagerduty.AlertGroupingSetting) resourceAlertGroupingSettingModel { + model := resourceAlertGroupingSettingModel{ + ID: types.StringValue(response.ID), + Name: types.StringValue(response.Name), + Description: types.StringValue(response.Description), + Type: types.StringValue(string(response.Type)), + Config: flattenAlertGroupingSettingConfig(response), + Services: flattenAlertGroupingSettingServices(response), + } + return model +} + +func flattenAlertGroupingSettingConfig(response *pagerduty.AlertGroupingSetting) types.Object { + var alertGroupingSettingConfigAttrTypes = map[string]attr.Type{ + "timeout": types.Int64Type, + "time_window": types.Int64Type, + "aggregate": types.StringType, + "fields": types.SetType{ElemType: types.StringType}, + } + + var obj map[string]attr.Value + + switch c := response.Config.(type) { + case pagerduty.AlertGroupingSettingConfigContentBased: + fields := make([]attr.Value, 0, len(c.Fields)) + for _, f := range c.Fields { + fields = append(fields, types.StringValue(f)) + } + tw := types.Int64Value(int64(c.TimeWindow)) + obj = map[string]attr.Value{ + "timeout": types.Int64Null(), + "time_window": tw, + "aggregate": types.StringValue(c.Aggregate), + "fields": types.SetValueMust(types.StringType, fields), + } + + case pagerduty.AlertGroupingSettingConfigIntelligent: + obj = map[string]attr.Value{ + "timeout": types.Int64Null(), + "time_window": types.Int64Value(int64(c.TimeWindow)), + "aggregate": types.StringNull(), + "fields": types.SetNull(types.StringType), + } + + case pagerduty.AlertGroupingSettingConfigTime: + obj = map[string]attr.Value{ + "timeout": types.Int64Value(int64(c.Timeout)), + "time_window": types.Int64Null(), + "aggregate": types.StringNull(), + "fields": types.SetNull(types.StringType), + } + } + + return types.ObjectValueMust(alertGroupingSettingConfigAttrTypes, obj) +} + +func flattenAlertGroupingSettingServices(response *pagerduty.AlertGroupingSetting) types.Set { + serviceIDs := make([]attr.Value, 0, len(response.Services)) + for _, s := range response.Services { + serviceIDs = append(serviceIDs, types.StringValue(s.ID)) + } + return types.SetValueMust(types.StringType, serviceIDs) +} + +// checkAlertGroupingSettingServicesRequiresReplace forces the resource to be +// recreated when no service from previous configuration was reused. +func checkAlertGroupingSettingServicesRequiresReplace(ctx context.Context, req planmodifier.SetRequest, resp *setplanmodifier.RequiresReplaceIfFuncResponse) { + // TODO: check other resources to see if they are also failing because + // the API is silently triggering a deletion of a resourced planned to + // be updated when an object referenced inside a list or set attribute + // is deleted earlier in a `terraform apply` execution. + noneReused := true + + var stateIDs []string + d := req.StateValue.ElementsAs(ctx, &stateIDs, false) + if resp.Diagnostics.Append(d...); d.HasError() { + return + } + + var planIDs []types.String + d = req.PlanValue.ElementsAs(ctx, &planIDs, false) + if resp.Diagnostics.Append(d...); d.HasError() { + return + } + +outerLoop: + for _, pID := range planIDs { + for _, sID := range stateIDs { + if pID.ValueString() == sID { + noneReused = false + break outerLoop + } + } + } + + resp.RequiresReplace = noneReused +} diff --git a/pagerdutyplugin/resource_pagerduty_alert_grouping_setting_test.go b/pagerdutyplugin/resource_pagerduty_alert_grouping_setting_test.go new file mode 100644 index 000000000..abee54526 --- /dev/null +++ b/pagerdutyplugin/resource_pagerduty_alert_grouping_setting_test.go @@ -0,0 +1,409 @@ +package pagerduty + +import ( + "context" + "fmt" + "log" + "strings" + "testing" + + "github.com/PagerDuty/go-pagerduty" + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/terraform" +) + +func init() { + resource.AddTestSweepers("pagerduty_alert_grouping_setting", &resource.Sweeper{ + Name: "pagerduty_alert_grouping_setting", + F: testSweepAlertGroupingSetting, + }) +} + +func testSweepAlertGroupingSetting(_ string) error { + ctx := context.Background() + + resp, err := testAccProvider.client.ListAlertGroupingSettings(ctx, pagerduty.ListAlertGroupingSettingsOptions{ + Limit: 100, + }) + if err != nil { + return err + } + + for _, setting := range resp.AlertGroupingSettings { + if strings.HasPrefix(setting.Name, "test") || strings.HasPrefix(setting.Name, "tf-") { + log.Printf("Destroying alert grouping setting %s (%s)", setting.Name, setting.ID) + if err := testAccProvider.client.DeleteAlertGroupingSetting(ctx, setting.ID); err != nil { + return err + } + } + } + + return nil +} + +func TestAccPagerDutyAlertGroupingSetting_Basic(t *testing.T) { + ref := fmt.Sprint("tf-", acctest.RandString(5)) + resourceRef := "pagerduty_alert_grouping_setting." + ref + + service := fmt.Sprint("tf-", acctest.RandString(5)) + serviceUpdated := fmt.Sprint("tf-", acctest.RandString(5)) + name := ref + "'s name" + nameUpdated := ref + "'s name updated" + + configType := string(pagerduty.AlertGroupingSettingContentBasedType) + config := pagerduty.AlertGroupingSettingConfigContentBased{ + TimeWindow: 0, + Aggregate: "all", + Fields: []string{"summary"}, + } + configTypeUpdated := string(pagerduty.AlertGroupingSettingTimeType) + configUpdated := pagerduty.AlertGroupingSettingConfigTime{Timeout: 60} + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV5ProviderFactories: testAccProtoV5ProviderFactories(), + CheckDestroy: testAccCheckPagerDutyAlertGroupingSettingDestroy, + Steps: []resource.TestStep{ + { + Config: testAccCheckPagerDutyAlertGroupingSettingConfig(ref, name, configType, service, config), + Check: resource.ComposeTestCheckFunc( + testAccCheckPagerDutyAlertGroupingSettingExists(resourceRef), + resource.TestCheckResourceAttr(resourceRef, "name", name), + resource.TestCheckResourceAttrSet(resourceRef, "description"), + resource.TestCheckResourceAttr(resourceRef, "type", configType), + resource.TestCheckResourceAttr(resourceRef, "config.time_window", fmt.Sprint(300)), + resource.TestCheckResourceAttr(resourceRef, "config.aggregate", config.Aggregate), + resource.TestCheckResourceAttr(resourceRef, "config.fields.0", config.Fields[0]), + resource.TestCheckResourceAttrSet(resourceRef, "services.0"), + ), + }, + { + Config: testAccCheckPagerDutyAlertGroupingSettingConfig(ref, nameUpdated, configTypeUpdated, serviceUpdated, configUpdated), + Check: resource.ComposeTestCheckFunc( + testAccCheckPagerDutyAlertGroupingSettingExists(resourceRef), + resource.TestCheckResourceAttr(resourceRef, "name", nameUpdated), + resource.TestCheckResourceAttrSet(resourceRef, "description"), + resource.TestCheckResourceAttr(resourceRef, "type", configTypeUpdated), + resource.TestCheckResourceAttr(resourceRef, "config.timeout", fmt.Sprint(configUpdated.Timeout)), + resource.TestCheckResourceAttrSet(resourceRef, "services.0"), + ), + }, + }, + }) +} + +func TestAccPagerDutyAlertGroupingSetting_AppendService(t *testing.T) { + name := fmt.Sprint("tf-", acctest.RandString(5)) + service1 := fmt.Sprint("tf-", acctest.RandString(5)) + service2 := fmt.Sprint("tf-", acctest.RandString(5)) + service3 := fmt.Sprint("tf-", acctest.RandString(5)) + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV5ProviderFactories: testAccProtoV5ProviderFactories(), + CheckDestroy: testAccCheckPagerDutyAlertGroupingSettingDestroy, + Steps: []resource.TestStep{ + { + Config: testAccCheckPagerDutyAlertGroupingSettingAppendServiceConfig(name, service1, service2), + Check: resource.ComposeTestCheckFunc( + testAccCheckPagerDutyAlertGroupingSettingExists("pagerduty_alert_grouping_setting.foo"), + resource.TestCheckResourceAttr("pagerduty_alert_grouping_setting.foo", "services.#", "2"), + ), + }, + { + Config: testAccCheckPagerDutyAlertGroupingSettingAppendServiceConfigUpdated(name, service1, service2, service3), + Check: resource.ComposeTestCheckFunc( + testAccCheckPagerDutyAlertGroupingSettingExists("pagerduty_alert_grouping_setting.foo"), + resource.TestCheckResourceAttr("pagerduty_alert_grouping_setting.foo", "services.#", "3"), + ), + }, + }, + }) +} + +func TestAccPagerDutyAlertGroupingSetting_PopService(t *testing.T) { + name := fmt.Sprint("tf-", acctest.RandString(5)) + service1 := fmt.Sprint("tf-", acctest.RandString(5)) + service2 := fmt.Sprint("tf-", acctest.RandString(5)) + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV5ProviderFactories: testAccProtoV5ProviderFactories(), + CheckDestroy: testAccCheckPagerDutyAlertGroupingSettingDestroy, + Steps: []resource.TestStep{ + { + Config: testAccCheckPagerDutyAlertGroupingSettingAppendServiceConfig(name, service1, service2), + Check: resource.ComposeTestCheckFunc( + testAccCheckPagerDutyAlertGroupingSettingExists("pagerduty_alert_grouping_setting.foo"), + resource.TestCheckResourceAttr("pagerduty_alert_grouping_setting.foo", "services.#", "2"), + ), + }, + { + Config: testAccCheckPagerDutyAlertGroupingSettingPopServiceConfigUpdated(name, service1), + Check: resource.ComposeTestCheckFunc( + testAccCheckPagerDutyAlertGroupingSettingExists("pagerduty_alert_grouping_setting.foo"), + resource.TestCheckResourceAttr("pagerduty_alert_grouping_setting.foo", "services.#", "1"), + ), + }, + }, + }) +} + +func TestAccPagerDutyAlertGroupingSetting_ContentBased_WithTimeWindow(t *testing.T) { + ref := fmt.Sprint("tf-", acctest.RandString(5)) + resourceRef := "pagerduty_alert_grouping_setting." + ref + name := ref + "'s name" + + configType := string(pagerduty.AlertGroupingSettingContentBasedType) + config := pagerduty.AlertGroupingSettingConfigContentBased{ + TimeWindow: 600, + Aggregate: "all", + Fields: []string{"summary"}, + } + + service := fmt.Sprint("tf-", acctest.RandString(5)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV5ProviderFactories: testAccProtoV5ProviderFactories(), + CheckDestroy: testAccCheckPagerDutyAlertGroupingSettingDestroy, + Steps: []resource.TestStep{ + { + Config: testAccCheckPagerDutyAlertGroupingSettingConfig(ref, name, configType, service, config), + Check: resource.ComposeTestCheckFunc( + testAccCheckPagerDutyAlertGroupingSettingExists(resourceRef), + resource.TestCheckResourceAttr(resourceRef, "name", name), + resource.TestCheckResourceAttrSet(resourceRef, "description"), + resource.TestCheckResourceAttr(resourceRef, "type", configType), + resource.TestCheckResourceAttr(resourceRef, "config.time_window", fmt.Sprint(config.TimeWindow)), + resource.TestCheckResourceAttr(resourceRef, "config.aggregate", config.Aggregate), + resource.TestCheckResourceAttr(resourceRef, "config.fields.0", config.Fields[0]), + resource.TestCheckResourceAttrSet(resourceRef, "services.0"), + ), + }, + }, + }) +} + +func TestAccPagerDutyAlertGroupingSetting_Time_WithTimeoutZero(t *testing.T) { + ref := fmt.Sprint("tf-", acctest.RandString(5)) + name := ref + + configType := string(pagerduty.AlertGroupingSettingTimeType) + config := pagerduty.AlertGroupingSettingConfigTime{Timeout: 0} + + service := fmt.Sprint("tf-", acctest.RandString(5)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV5ProviderFactories: testAccProtoV5ProviderFactories(), + CheckDestroy: testAccCheckPagerDutyAlertGroupingSettingDestroy, + Steps: []resource.TestStep{ + { + Config: testAccCheckPagerDutyAlertGroupingSettingConfig(ref, name, configType, service, config), + Check: resource.ComposeTestCheckFunc( + testAccCheckPagerDutyAlertGroupingSettingExists("pagerduty_alert_grouping_setting."+ref), + resource.TestCheckResourceAttr("pagerduty_alert_grouping_setting."+ref, "name", name), + resource.TestCheckResourceAttrSet("pagerduty_alert_grouping_setting."+ref, "description"), + resource.TestCheckResourceAttr("pagerduty_alert_grouping_setting."+ref, "type", configType), + resource.TestCheckResourceAttr("pagerduty_alert_grouping_setting."+ref, "config.timeout", fmt.Sprint(config.Timeout)), + resource.TestCheckResourceAttrSet("pagerduty_alert_grouping_setting."+ref, "services.0"), + ), + }, + }, + }) +} + +func testAccCheckPagerDutyAlertGroupingSettingDestroy(s *terraform.State) error { + for _, r := range s.RootModule().Resources { + if r.Type != "pagerduty_alert_grouping_setting" { + continue + } + + ctx := context.Background() + + if _, err := testAccProvider.client.GetAlertGroupingSetting(ctx, r.Primary.ID); err == nil { + return fmt.Errorf("Alert grouping setting still exists") + } + + } + return nil +} + +func testAccCheckPagerDutyAlertGroupingSettingExists(n string) resource.TestCheckFunc { + return func(s *terraform.State) error { + ctx := context.Background() + + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("Not found: %s", n) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("No alert grouping setting ID is set") + } + + found, err := testAccProvider.client.GetAlertGroupingSetting(ctx, rs.Primary.ID) + if err != nil { + return err + } + + if found.ID != rs.Primary.ID { + return fmt.Errorf("Alert grouping setting not found: %v - %v", rs.Primary.ID, found) + } + + return nil + } +} + +func helperConfigPagerDutyAlertGroupingSettingConfig(config interface{}) string { + switch c := config.(type) { + case pagerduty.AlertGroupingSettingConfigContentBased: + timeWindowStr := "" + if c.TimeWindow != 0 { + timeWindowStr = fmt.Sprintf("time_window = %d", c.TimeWindow) + } + return fmt.Sprintf(`{ + %s + aggregate = "%s" + fields = ["%s"] + }`, timeWindowStr, c.Aggregate, strings.Join(c.Fields, `","`)) + case pagerduty.AlertGroupingSettingConfigIntelligent: + timeWindowStr := "" + if c.TimeWindow != 0 { + timeWindowStr = fmt.Sprintf("time_window = %d", c.TimeWindow) + } + return fmt.Sprintf("{\n\t%s\n}", timeWindowStr) + case pagerduty.AlertGroupingSettingConfigTime: + timeoutStr := "" + if c.Timeout != 0 { + timeoutStr = fmt.Sprintf("timeout = %d", c.Timeout) + } + return fmt.Sprintf("{\n%s\n}", timeoutStr) + } + return "{}" +} + +func testAccCheckPagerDutyAlertGroupingSettingConfig(ref, name, cfgType, serviceName string, cfg interface{}) string { + config := helperConfigPagerDutyAlertGroupingSettingConfig(cfg) + return fmt.Sprintf(` +data "pagerduty_escalation_policy" "default" { + name = "Default" +} + +resource "pagerduty_service" "%[4]s" { + name = "%[4]s" + escalation_policy = data.pagerduty_escalation_policy.default.id +} + +resource "pagerduty_alert_grouping_setting" "%[1]s" { + name = "%[2]s" + type = "%[3]s" + services = [pagerduty_service.%[4]s.id] + config %[5]s +}`, ref, name, cfgType, serviceName, config) +} + +func testAccCheckPagerDutyAlertGroupingSettingConfigUpdated(ref, name, cfgType, serviceName string, cfg interface{}) string { + config := helperConfigPagerDutyAlertGroupingSettingConfig(cfg) + return fmt.Sprintf(` +data "pagerduty_escalation_policy" "default" { + name = "Default" +} + +resource "pagerduty_service" "%[4]s" { + name = "%[4]s" + escalation_policy = data.pagerduty_escalation_policy.default.id +} +resource "pagerduty_service" "%[4]s-copy" { + name = "Copy of %[4]s" + escalation_policy = data.pagerduty_escalation_policy.default.id +} + +resource "pagerduty_alert_grouping_setting" "%[1]s" { + name = "%[2]s" + type = "%[3]s" + services = [pagerduty_service.%[4]s.id, pagerduty_service.%[4]s-copy.id] + config %[5]s +}`, ref, name, cfgType, serviceName, config) +} + +func testAccCheckPagerDutyAlertGroupingSettingAppendServiceConfig(name, service1, service2 string) string { + return fmt.Sprintf(` +data "pagerduty_escalation_policy" "default" { + name = "Default" +} + +resource "pagerduty_service" "foo" { + name = "%s" + escalation_policy = data.pagerduty_escalation_policy.default.id +} +resource "pagerduty_service" "bar" { + name = "%s" + escalation_policy = data.pagerduty_escalation_policy.default.id +} + +resource "pagerduty_alert_grouping_setting" "foo" { + name = "%s" + type = "content_based" + config { + time_window = 1440 + aggregate = "all" + fields = ["summary"] + } + services = [pagerduty_service.foo.id, pagerduty_service.bar.id] +}`, service1, service2, name) +} + +func testAccCheckPagerDutyAlertGroupingSettingAppendServiceConfigUpdated(name, service1, service2, service3 string) string { + return fmt.Sprintf(` +data "pagerduty_escalation_policy" "default" { + name = "Default" +} + +resource "pagerduty_service" "foo" { + name = "%s" + escalation_policy = data.pagerduty_escalation_policy.default.id +} +resource "pagerduty_service" "bar" { + name = "%s" + escalation_policy = data.pagerduty_escalation_policy.default.id +} +resource "pagerduty_service" "qux" { + name = "%s" + escalation_policy = data.pagerduty_escalation_policy.default.id +} + +resource "pagerduty_alert_grouping_setting" "foo" { + name = "%s" + type = "content_based" + config { + time_window = 1440 + aggregate = "all" + fields = ["summary"] + } + services = [pagerduty_service.foo.id, pagerduty_service.qux.id, pagerduty_service.bar.id] +}`, service1, service2, service3, name) +} + +func testAccCheckPagerDutyAlertGroupingSettingPopServiceConfigUpdated(name, service1 string) string { + return fmt.Sprintf(` +data "pagerduty_escalation_policy" "default" { + name = "Default" +} + +resource "pagerduty_service" "foo" { + name = "%s" + escalation_policy = data.pagerduty_escalation_policy.default.id +} + +resource "pagerduty_alert_grouping_setting" "foo" { + name = "%s" + type = "content_based" + config { + time_window = 1440 + aggregate = "all" + fields = ["summary"] + } + services = [pagerduty_service.foo.id] +}`, service1, name) +} diff --git a/vendor/github.com/PagerDuty/go-pagerduty/alert_grouping_setting.go b/vendor/github.com/PagerDuty/go-pagerduty/alert_grouping_setting.go new file mode 100644 index 000000000..c7e0cb3c7 --- /dev/null +++ b/vendor/github.com/PagerDuty/go-pagerduty/alert_grouping_setting.go @@ -0,0 +1,215 @@ +package pagerduty + +import ( + "context" + "encoding/json" + "fmt" + + "github.com/google/go-querystring/query" +) + +// AlertGroupingSettingConfigTime is the configuration content for a +// AlertGroupingSetting of type "time" +type AlertGroupingSettingConfigTime struct { + Timeout uint `json:"timeout"` +} + +// AlertGroupingSettingConfigIntelligent is the configuration content for a +// AlertGroupingSetting of type "intelligent" +type AlertGroupingSettingConfigIntelligent struct { + TimeWindow uint `json:"time_window"` + RecommendedTimeWindow *uint `json:"recommended_time_window,omitempty"` +} + +// AlertGroupingSettingConfigContentBased is the configuration content for a +// AlertGroupingSetting of type "content_based" or "content_based_intelligent" +type AlertGroupingSettingConfigContentBased struct { + TimeWindow uint `json:"time_window"` + RecommendedTimeWindow *uint `json:"recommended_time_window,omitempty"` + Aggregate string `json:"aggregate"` + Fields []string `json:"fields"` +} + +type AlertGroupingSettingType string + +const ( + AlertGroupingSettingContentBasedType AlertGroupingSettingType = "content_based" + AlertGroupingSettingContentBasedIntelligentType AlertGroupingSettingType = "content_based_intelligent" + AlertGroupingSettingIntelligentType AlertGroupingSettingType = "intelligent" + AlertGroupingSettingTimeType AlertGroupingSettingType = "time" +) + +// AlertGroupingSetting is a configuration used during the grouping of the +// alerts easier to reuse and share between many services +type AlertGroupingSetting struct { + ID string `json:"id,omitempty"` + Name string `json:"name,omitempty"` + Description string `json:"description,omitempty"` + Type AlertGroupingSettingType `json:"type,omitempty"` + Config interface{} `json:"config,omitempty"` + Services []AlertGroupingSettingService `json:"services"` +} + +// AlertGroupingSettingService is a reference to services associated with an +// alert grouping setting. +type AlertGroupingSettingService struct { + ID string `json:"id,omitempty"` + Name string `json:"name,omitempty"` +} + +// CreateAlertGroupingSetting creates an instance of AlertGroupingSettings for +// either one service or many services that are in the alert group setting +func (c *Client) CreateAlertGroupingSetting(ctx context.Context, a AlertGroupingSetting) (*AlertGroupingSetting, error) { + d := map[string]AlertGroupingSetting{"alert_grouping_setting": a} + + resp, err := c.post(ctx, "/alert_grouping_settings", d, nil) + if err != nil { + return nil, err + } + + var resultRaw struct { + AlertGroupingSetting alertGroupingSettingRaw `json:"alert_grouping_setting"` + } + if err := c.decodeJSON(resp, &resultRaw); err != nil { + return nil, fmt.Errorf("Could not decode JSON response: %v", err) + } + + value := resultRaw.AlertGroupingSetting + return getAlertGroupingSettingFromRaw(value) +} + +// ListAlertGroupingSettingsOptions is the data structure used when calling the +// ListAlertGroupingSettings API endpoint. +type ListAlertGroupingSettingsOptions struct { + After string `url:"after,omitempty"` + Before string `url:"before,omitempty"` + Limit uint `url:"limit,omitempty"` + Total bool `url:"total,omitempty"` + ServiceIDs []string `url:"service_ids,omitempty,brackets"` +} + +// ListAlertGroupingSettingsResponse is the data structure returned from calling +// the ListAlertGroupingSettingsResponse API endpoint. +type ListAlertGroupingSettingsResponse struct { + After string `json:"after,omitempty"` + Before string `json:"before,omitempty"` + Limit uint `json:"limit,omitempty"` + Total bool `json:"total,omitempty"` + AlertGroupingSettings []AlertGroupingSetting `json:"alert_grouping_settings"` +} + +// ListAlertGroupingSettings lists all of your alert grouping settings including +// both single service settings and global content based settings. +func (c *Client) ListAlertGroupingSettings(ctx context.Context, o ListAlertGroupingSettingsOptions) (*ListAlertGroupingSettingsResponse, error) { + v, err := query.Values(o) + if err != nil { + return nil, err + } + + resp, err := c.get(ctx, "/alert_grouping_settings?"+v.Encode(), nil) + if err != nil { + return nil, err + } + + var resultRaw struct { + ListAlertGroupingSettingsResponse + AlertGroupingSettings []alertGroupingSettingRaw `json:"alert_grouping_settings"` + } + + if err = c.decodeJSON(resp, &resultRaw); err != nil { + return nil, err + } + + settings := make([]AlertGroupingSetting, 0, len(resultRaw.AlertGroupingSettings)) + for _, rawItem := range resultRaw.AlertGroupingSettings { + v, _ := getAlertGroupingSettingFromRaw(rawItem) + if v != nil { + settings = append(settings, *v) + } + } + + result := &resultRaw.ListAlertGroupingSettingsResponse + result.AlertGroupingSettings = settings + return result, nil +} + +// GetAlertGroupingSetting get an existing Alert Grouping Setting. +func (c *Client) GetAlertGroupingSetting(ctx context.Context, id string) (*AlertGroupingSetting, error) { + resp, err := c.get(ctx, "/alert_grouping_settings/"+id, nil) + if err != nil { + return nil, err + } + + var resultRaw struct { + AlertGroupingSetting alertGroupingSettingRaw `json:"alert_grouping_setting"` + } + if err := c.decodeJSON(resp, &resultRaw); err != nil { + return nil, fmt.Errorf("Could not decode JSON response: %v", err) + } + + value := resultRaw.AlertGroupingSetting + return getAlertGroupingSettingFromRaw(value) +} + +// DeleteAlertGroupingSetting deletes an existing Alert Grouping Setting. +func (c *Client) DeleteAlertGroupingSetting(ctx context.Context, id string) error { + _, err := c.delete(ctx, "/alert_grouping_settings/"+id) + return err +} + +// UpdateAlertGroupingSetting updates an Alert Grouping Setting. +func (c *Client) UpdateAlertGroupingSetting(ctx context.Context, a AlertGroupingSetting) (*AlertGroupingSetting, error) { + d := map[string]AlertGroupingSetting{"alert_grouping_setting": a} + + resp, err := c.put(ctx, "/alert_grouping_settings/"+a.ID, d, nil) + if err != nil { + return nil, err + } + + var resultRaw struct { + AlertGroupingSetting alertGroupingSettingRaw `json:"alert_grouping_setting"` + } + if err := c.decodeJSON(resp, &resultRaw); err != nil { + return nil, fmt.Errorf("Could not decode JSON response: %v", err) + } + + value := resultRaw.AlertGroupingSetting + return getAlertGroupingSettingFromRaw(value) +} + +// alertGroupingSettingRaw is an AlertGroupingSetting that overrides its Config with a json raw +// message in order to parse it later. +type alertGroupingSettingRaw struct { + AlertGroupingSetting + Config json.RawMessage `json:"config,omitempty"` +} + +// getAlertGroupingSettingFromRaw transform the content of a Alert Grouping +// Setting "config" field from a json raw message into the data structure +// corresponding to its "type". +func getAlertGroupingSettingFromRaw(raw alertGroupingSettingRaw) (*AlertGroupingSetting, error) { + result := &raw.AlertGroupingSetting + + switch raw.Type { + case AlertGroupingSettingContentBasedType, AlertGroupingSettingContentBasedIntelligentType: + var cfg AlertGroupingSettingConfigContentBased + if err := json.Unmarshal(raw.Config, &cfg); err != nil { + return nil, err + } + result.Config = cfg + case AlertGroupingSettingIntelligentType: + var cfg AlertGroupingSettingConfigIntelligent + if err := json.Unmarshal(raw.Config, &cfg); err != nil { + return nil, err + } + result.Config = cfg + case AlertGroupingSettingTimeType: + var cfg AlertGroupingSettingConfigTime + if err := json.Unmarshal(raw.Config, &cfg); err != nil { + return nil, err + } + result.Config = cfg + } + + return result, nil +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-framework-validators/setvalidator/all.go b/vendor/github.com/hashicorp/terraform-plugin-framework-validators/setvalidator/all.go new file mode 100644 index 000000000..a160764cb --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-framework-validators/setvalidator/all.go @@ -0,0 +1,57 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package setvalidator + +import ( + "context" + "fmt" + "strings" + + "github.com/hashicorp/terraform-plugin-framework/schema/validator" +) + +// All returns a validator which ensures that any configured attribute value +// attribute value validates against all the given validators. +// +// Use of All is only necessary when used in conjunction with Any or AnyWithAllWarnings +// as the Validators field automatically applies a logical AND. +func All(validators ...validator.Set) validator.Set { + return allValidator{ + validators: validators, + } +} + +var _ validator.Set = allValidator{} + +// allValidator implements the validator. +type allValidator struct { + validators []validator.Set +} + +// Description describes the validation in plain text formatting. +func (v allValidator) Description(ctx context.Context) string { + var descriptions []string + + for _, subValidator := range v.validators { + descriptions = append(descriptions, subValidator.Description(ctx)) + } + + return fmt.Sprintf("Value must satisfy all of the validations: %s", strings.Join(descriptions, " + ")) +} + +// MarkdownDescription describes the validation in Markdown formatting. +func (v allValidator) MarkdownDescription(ctx context.Context) string { + return v.Description(ctx) +} + +// ValidateSet performs the validation. +func (v allValidator) ValidateSet(ctx context.Context, req validator.SetRequest, resp *validator.SetResponse) { + for _, subValidator := range v.validators { + validateResp := &validator.SetResponse{} + + subValidator.ValidateSet(ctx, req, validateResp) + + resp.Diagnostics.Append(validateResp.Diagnostics...) + } +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-framework-validators/setvalidator/also_requires.go b/vendor/github.com/hashicorp/terraform-plugin-framework-validators/setvalidator/also_requires.go new file mode 100644 index 000000000..a505b2d11 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-framework-validators/setvalidator/also_requires.go @@ -0,0 +1,26 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package setvalidator + +import ( + "github.com/hashicorp/terraform-plugin-framework-validators/internal/schemavalidator" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" +) + +// AlsoRequires checks that a set of path.Expression has a non-null value, +// if the current attribute or block also has a non-null value. +// +// This implements the validation logic declaratively within the schema. +// Refer to [datasourcevalidator.RequiredTogether], +// [providervalidator.RequiredTogether], or [resourcevalidator.RequiredTogether] +// for declaring this type of validation outside the schema definition. +// +// Relative path.Expression will be resolved using the attribute or block +// being validated. +func AlsoRequires(expressions ...path.Expression) validator.Set { + return schemavalidator.AlsoRequiresValidator{ + PathExpressions: expressions, + } +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-framework-validators/setvalidator/any.go b/vendor/github.com/hashicorp/terraform-plugin-framework-validators/setvalidator/any.go new file mode 100644 index 000000000..387ca8651 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-framework-validators/setvalidator/any.go @@ -0,0 +1,65 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package setvalidator + +import ( + "context" + "fmt" + "strings" + + "github.com/hashicorp/terraform-plugin-framework/schema/validator" +) + +// Any returns a validator which ensures that any configured attribute value +// passes at least one of the given validators. +// +// To prevent practitioner confusion should non-passing validators have +// conflicting logic, only warnings from the passing validator are returned. +// Use AnyWithAllWarnings() to return warnings from non-passing validators +// as well. +func Any(validators ...validator.Set) validator.Set { + return anyValidator{ + validators: validators, + } +} + +var _ validator.Set = anyValidator{} + +// anyValidator implements the validator. +type anyValidator struct { + validators []validator.Set +} + +// Description describes the validation in plain text formatting. +func (v anyValidator) Description(ctx context.Context) string { + var descriptions []string + + for _, subValidator := range v.validators { + descriptions = append(descriptions, subValidator.Description(ctx)) + } + + return fmt.Sprintf("Value must satisfy at least one of the validations: %s", strings.Join(descriptions, " + ")) +} + +// MarkdownDescription describes the validation in Markdown formatting. +func (v anyValidator) MarkdownDescription(ctx context.Context) string { + return v.Description(ctx) +} + +// ValidateSet performs the validation. +func (v anyValidator) ValidateSet(ctx context.Context, req validator.SetRequest, resp *validator.SetResponse) { + for _, subValidator := range v.validators { + validateResp := &validator.SetResponse{} + + subValidator.ValidateSet(ctx, req, validateResp) + + if !validateResp.Diagnostics.HasError() { + resp.Diagnostics = validateResp.Diagnostics + + return + } + + resp.Diagnostics.Append(validateResp.Diagnostics...) + } +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-framework-validators/setvalidator/any_with_all_warnings.go b/vendor/github.com/hashicorp/terraform-plugin-framework-validators/setvalidator/any_with_all_warnings.go new file mode 100644 index 000000000..e0c534e62 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-framework-validators/setvalidator/any_with_all_warnings.go @@ -0,0 +1,67 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package setvalidator + +import ( + "context" + "fmt" + "strings" + + "github.com/hashicorp/terraform-plugin-framework/schema/validator" +) + +// AnyWithAllWarnings returns a validator which ensures that any configured +// attribute value passes at least one of the given validators. This validator +// returns all warnings, including failed validators. +// +// Use Any() to return warnings only from the passing validator. +func AnyWithAllWarnings(validators ...validator.Set) validator.Set { + return anyWithAllWarningsValidator{ + validators: validators, + } +} + +var _ validator.Set = anyWithAllWarningsValidator{} + +// anyWithAllWarningsValidator implements the validator. +type anyWithAllWarningsValidator struct { + validators []validator.Set +} + +// Description describes the validation in plain text formatting. +func (v anyWithAllWarningsValidator) Description(ctx context.Context) string { + var descriptions []string + + for _, subValidator := range v.validators { + descriptions = append(descriptions, subValidator.Description(ctx)) + } + + return fmt.Sprintf("Value must satisfy at least one of the validations: %s", strings.Join(descriptions, " + ")) +} + +// MarkdownDescription describes the validation in Markdown formatting. +func (v anyWithAllWarningsValidator) MarkdownDescription(ctx context.Context) string { + return v.Description(ctx) +} + +// ValidateSet performs the validation. +func (v anyWithAllWarningsValidator) ValidateSet(ctx context.Context, req validator.SetRequest, resp *validator.SetResponse) { + anyValid := false + + for _, subValidator := range v.validators { + validateResp := &validator.SetResponse{} + + subValidator.ValidateSet(ctx, req, validateResp) + + if !validateResp.Diagnostics.HasError() { + anyValid = true + } + + resp.Diagnostics.Append(validateResp.Diagnostics...) + } + + if anyValid { + resp.Diagnostics = resp.Diagnostics.Warnings() + } +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-framework-validators/setvalidator/at_least_one_of.go b/vendor/github.com/hashicorp/terraform-plugin-framework-validators/setvalidator/at_least_one_of.go new file mode 100644 index 000000000..7ff7edabc --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-framework-validators/setvalidator/at_least_one_of.go @@ -0,0 +1,27 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package setvalidator + +import ( + "github.com/hashicorp/terraform-plugin-framework-validators/internal/schemavalidator" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" +) + +// AtLeastOneOf checks that of a set of path.Expression, +// including the attribute or block this validator is applied to, +// at least one has a non-null value. +// +// This implements the validation logic declaratively within the tfsdk.Schema. +// Refer to [datasourcevalidator.AtLeastOneOf], +// [providervalidator.AtLeastOneOf], or [resourcevalidator.AtLeastOneOf] +// for declaring this type of validation outside the schema definition. +// +// Any relative path.Expression will be resolved using the attribute or block +// being validated. +func AtLeastOneOf(expressions ...path.Expression) validator.Set { + return schemavalidator.AtLeastOneOfValidator{ + PathExpressions: expressions, + } +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-framework-validators/setvalidator/conflicts_with.go b/vendor/github.com/hashicorp/terraform-plugin-framework-validators/setvalidator/conflicts_with.go new file mode 100644 index 000000000..54792664a --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-framework-validators/setvalidator/conflicts_with.go @@ -0,0 +1,27 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package setvalidator + +import ( + "github.com/hashicorp/terraform-plugin-framework-validators/internal/schemavalidator" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" +) + +// ConflictsWith checks that a set of path.Expression, +// including the attribute or block the validator is applied to, +// do not have a value simultaneously. +// +// This implements the validation logic declaratively within the schema. +// Refer to [datasourcevalidator.Conflicting], +// [providervalidator.Conflicting], or [resourcevalidator.Conflicting] +// for declaring this type of validation outside the schema definition. +// +// Relative path.Expression will be resolved using the attribute or block +// being validated. +func ConflictsWith(expressions ...path.Expression) validator.Set { + return schemavalidator.ConflictsWithValidator{ + PathExpressions: expressions, + } +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-framework-validators/setvalidator/doc.go b/vendor/github.com/hashicorp/terraform-plugin-framework-validators/setvalidator/doc.go new file mode 100644 index 000000000..258a0db5a --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-framework-validators/setvalidator/doc.go @@ -0,0 +1,5 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +// Package setvalidator provides validators for types.Set attributes. +package setvalidator diff --git a/vendor/github.com/hashicorp/terraform-plugin-framework-validators/setvalidator/exactly_one_of.go b/vendor/github.com/hashicorp/terraform-plugin-framework-validators/setvalidator/exactly_one_of.go new file mode 100644 index 000000000..b48408d7b --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-framework-validators/setvalidator/exactly_one_of.go @@ -0,0 +1,28 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package setvalidator + +import ( + "github.com/hashicorp/terraform-plugin-framework-validators/internal/schemavalidator" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" +) + +// ExactlyOneOf checks that of a set of path.Expression, +// including the attribute or block the validator is applied to, +// one and only one attribute has a value. +// It will also cause a validation error if none are specified. +// +// This implements the validation logic declaratively within the schema. +// Refer to [datasourcevalidator.ExactlyOneOf], +// [providervalidator.ExactlyOneOf], or [resourcevalidator.ExactlyOneOf] +// for declaring this type of validation outside the schema definition. +// +// Relative path.Expression will be resolved using the attribute or block +// being validated. +func ExactlyOneOf(expressions ...path.Expression) validator.Set { + return schemavalidator.ExactlyOneOfValidator{ + PathExpressions: expressions, + } +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-framework-validators/setvalidator/is_required.go b/vendor/github.com/hashicorp/terraform-plugin-framework-validators/setvalidator/is_required.go new file mode 100644 index 000000000..63564c5c2 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-framework-validators/setvalidator/is_required.go @@ -0,0 +1,44 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package setvalidator + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework-validators/helpers/validatordiag" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" +) + +var _ validator.Set = isRequiredValidator{} + +// isRequiredValidator validates that a set has a configuration value. +type isRequiredValidator struct{} + +// Description describes the validation in plain text formatting. +func (v isRequiredValidator) Description(_ context.Context) string { + return "must have a configuration value as the provider has marked it as required" +} + +// MarkdownDescription describes the validation in Markdown formatting. +func (v isRequiredValidator) MarkdownDescription(ctx context.Context) string { + return v.Description(ctx) +} + +// Validate performs the validation. +func (v isRequiredValidator) ValidateSet(ctx context.Context, req validator.SetRequest, resp *validator.SetResponse) { + if req.ConfigValue.IsNull() { + resp.Diagnostics.Append(validatordiag.InvalidBlockDiagnostic( + req.Path, + v.Description(ctx), + )) + } +} + +// IsRequired returns a validator which ensures that any configured set has a value (not null). +// +// This validator is equivalent to the `Required` field on attributes and is only +// practical for use with `schema.SetNestedBlock` +func IsRequired() validator.Set { + return isRequiredValidator{} +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-framework-validators/setvalidator/size_at_least.go b/vendor/github.com/hashicorp/terraform-plugin-framework-validators/setvalidator/size_at_least.go new file mode 100644 index 000000000..d010b8585 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-framework-validators/setvalidator/size_at_least.go @@ -0,0 +1,59 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package setvalidator + +import ( + "context" + "fmt" + + "github.com/hashicorp/terraform-plugin-framework-validators/helpers/validatordiag" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" +) + +var _ validator.Set = sizeAtLeastValidator{} + +// sizeAtLeastValidator validates that set contains at least min elements. +type sizeAtLeastValidator struct { + min int +} + +// Description describes the validation in plain text formatting. +func (v sizeAtLeastValidator) Description(_ context.Context) string { + return fmt.Sprintf("set must contain at least %d elements", v.min) +} + +// MarkdownDescription describes the validation in Markdown formatting. +func (v sizeAtLeastValidator) MarkdownDescription(ctx context.Context) string { + return v.Description(ctx) +} + +// Validate performs the validation. +func (v sizeAtLeastValidator) ValidateSet(ctx context.Context, req validator.SetRequest, resp *validator.SetResponse) { + if req.ConfigValue.IsNull() || req.ConfigValue.IsUnknown() { + return + } + + elems := req.ConfigValue.Elements() + + if len(elems) < v.min { + resp.Diagnostics.Append(validatordiag.InvalidAttributeValueDiagnostic( + req.Path, + v.Description(ctx), + fmt.Sprintf("%d", len(elems)), + )) + } +} + +// SizeAtLeast returns an AttributeValidator which ensures that any configured +// attribute value: +// +// - Is a Set. +// - Contains at least min elements. +// +// Null (unconfigured) and unknown (known after apply) values are skipped. +func SizeAtLeast(min int) validator.Set { + return sizeAtLeastValidator{ + min: min, + } +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-framework-validators/setvalidator/size_at_most.go b/vendor/github.com/hashicorp/terraform-plugin-framework-validators/setvalidator/size_at_most.go new file mode 100644 index 000000000..4479f5970 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-framework-validators/setvalidator/size_at_most.go @@ -0,0 +1,59 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package setvalidator + +import ( + "context" + "fmt" + + "github.com/hashicorp/terraform-plugin-framework-validators/helpers/validatordiag" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" +) + +var _ validator.Set = sizeAtMostValidator{} + +// sizeAtMostValidator validates that set contains at most max elements. +type sizeAtMostValidator struct { + max int +} + +// Description describes the validation in plain text formatting. +func (v sizeAtMostValidator) Description(_ context.Context) string { + return fmt.Sprintf("set must contain at most %d elements", v.max) +} + +// MarkdownDescription describes the validation in Markdown formatting. +func (v sizeAtMostValidator) MarkdownDescription(ctx context.Context) string { + return v.Description(ctx) +} + +// Validate performs the validation. +func (v sizeAtMostValidator) ValidateSet(ctx context.Context, req validator.SetRequest, resp *validator.SetResponse) { + if req.ConfigValue.IsNull() || req.ConfigValue.IsUnknown() { + return + } + + elems := req.ConfigValue.Elements() + + if len(elems) > v.max { + resp.Diagnostics.Append(validatordiag.InvalidAttributeValueDiagnostic( + req.Path, + v.Description(ctx), + fmt.Sprintf("%d", len(elems)), + )) + } +} + +// SizeAtMost returns an AttributeValidator which ensures that any configured +// attribute value: +// +// - Is a Set. +// - Contains at most max elements. +// +// Null (unconfigured) and unknown (known after apply) values are skipped. +func SizeAtMost(max int) validator.Set { + return sizeAtMostValidator{ + max: max, + } +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-framework-validators/setvalidator/size_between.go b/vendor/github.com/hashicorp/terraform-plugin-framework-validators/setvalidator/size_between.go new file mode 100644 index 000000000..15945a7bc --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-framework-validators/setvalidator/size_between.go @@ -0,0 +1,62 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package setvalidator + +import ( + "context" + "fmt" + + "github.com/hashicorp/terraform-plugin-framework-validators/helpers/validatordiag" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" +) + +var _ validator.Set = sizeBetweenValidator{} + +// sizeBetweenValidator validates that set contains at least min elements +// and at most max elements. +type sizeBetweenValidator struct { + min int + max int +} + +// Description describes the validation in plain text formatting. +func (v sizeBetweenValidator) Description(_ context.Context) string { + return fmt.Sprintf("set must contain at least %d elements and at most %d elements", v.min, v.max) +} + +// MarkdownDescription describes the validation in Markdown formatting. +func (v sizeBetweenValidator) MarkdownDescription(ctx context.Context) string { + return v.Description(ctx) +} + +// ValidateSet performs the validation. +func (v sizeBetweenValidator) ValidateSet(ctx context.Context, req validator.SetRequest, resp *validator.SetResponse) { + if req.ConfigValue.IsNull() || req.ConfigValue.IsUnknown() { + return + } + + elems := req.ConfigValue.Elements() + + if len(elems) < v.min || len(elems) > v.max { + resp.Diagnostics.Append(validatordiag.InvalidAttributeValueDiagnostic( + req.Path, + v.Description(ctx), + fmt.Sprintf("%d", len(elems)), + )) + } +} + +// SizeBetween returns an AttributeValidator which ensures that any configured +// attribute value: +// +// - Is a Set. +// - Contains at least min elements and at most max elements. +// +// Null (unconfigured) and unknown (known after apply) values are skipped. +func SizeBetween(min, max int) validator.Set { + return sizeBetweenValidator{ + min: min, + max: max, + } +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-framework-validators/setvalidator/value_float64s_are.go b/vendor/github.com/hashicorp/terraform-plugin-framework-validators/setvalidator/value_float64s_are.go new file mode 100644 index 000000000..04140b8c6 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-framework-validators/setvalidator/value_float64s_are.go @@ -0,0 +1,119 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package setvalidator + +import ( + "context" + "fmt" + "strings" + + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/types/basetypes" +) + +// ValueFloat64sAre returns an validator which ensures that any configured +// Float64 values passes each Float64 validator. +// +// Null (unconfigured) and unknown (known after apply) values are skipped. +func ValueFloat64sAre(elementValidators ...validator.Float64) validator.Set { + return valueFloat64sAreValidator{ + elementValidators: elementValidators, + } +} + +var _ validator.Set = valueFloat64sAreValidator{} + +// valueFloat64sAreValidator validates that each Float64 member validates against each of the value validators. +type valueFloat64sAreValidator struct { + elementValidators []validator.Float64 +} + +// Description describes the validation in plain text formatting. +func (v valueFloat64sAreValidator) Description(ctx context.Context) string { + var descriptions []string + + for _, elementValidator := range v.elementValidators { + descriptions = append(descriptions, elementValidator.Description(ctx)) + } + + return fmt.Sprintf("element value must satisfy all validations: %s", strings.Join(descriptions, " + ")) +} + +// MarkdownDescription describes the validation in Markdown formatting. +func (v valueFloat64sAreValidator) MarkdownDescription(ctx context.Context) string { + return v.Description(ctx) +} + +// ValidateFloat64 performs the validation. +func (v valueFloat64sAreValidator) ValidateSet(ctx context.Context, req validator.SetRequest, resp *validator.SetResponse) { + if req.ConfigValue.IsNull() || req.ConfigValue.IsUnknown() { + return + } + + _, ok := req.ConfigValue.ElementType(ctx).(basetypes.Float64Typable) + + if !ok { + resp.Diagnostics.AddAttributeError( + req.Path, + "Invalid Validator for Element Type", + "While performing schema-based validation, an unexpected error occurred. "+ + "The attribute declares a Float64 values validator, however its values do not implement types.Float64Type or the types.Float64Typable interface for custom Float64 types. "+ + "Use the appropriate values validator that matches the element type. "+ + "This is always an issue with the provider and should be reported to the provider developers.\n\n"+ + fmt.Sprintf("Path: %s\n", req.Path.String())+ + fmt.Sprintf("Element Type: %T\n", req.ConfigValue.ElementType(ctx)), + ) + + return + } + + for _, element := range req.ConfigValue.Elements() { + elementPath := req.Path.AtSetValue(element) + + elementValuable, ok := element.(basetypes.Float64Valuable) + + // The check above should have prevented this, but raise an error + // instead of a type assertion panic or skipping the element. Any issue + // here likely indicates something wrong in the framework itself. + if !ok { + resp.Diagnostics.AddAttributeError( + req.Path, + "Invalid Validator for Element Value", + "While performing schema-based validation, an unexpected error occurred. "+ + "The attribute declares a Float64 values validator, however its values do not implement types.Float64Type or the types.Float64Typable interface for custom Float64 types. "+ + "This is likely an issue with terraform-plugin-framework and should be reported to the provider developers.\n\n"+ + fmt.Sprintf("Path: %s\n", req.Path.String())+ + fmt.Sprintf("Element Type: %T\n", req.ConfigValue.ElementType(ctx))+ + fmt.Sprintf("Element Value Type: %T\n", element), + ) + + return + } + + elementValue, diags := elementValuable.ToFloat64Value(ctx) + + resp.Diagnostics.Append(diags...) + + // Only return early if the new diagnostics indicate an issue since + // it likely will be the same for all elements. + if diags.HasError() { + return + } + + elementReq := validator.Float64Request{ + Path: elementPath, + PathExpression: elementPath.Expression(), + ConfigValue: elementValue, + Config: req.Config, + } + + for _, elementValidator := range v.elementValidators { + elementResp := &validator.Float64Response{} + + elementValidator.ValidateFloat64(ctx, elementReq, elementResp) + + resp.Diagnostics.Append(elementResp.Diagnostics...) + } + } +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-framework-validators/setvalidator/value_int64s_are.go b/vendor/github.com/hashicorp/terraform-plugin-framework-validators/setvalidator/value_int64s_are.go new file mode 100644 index 000000000..37cf6c271 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-framework-validators/setvalidator/value_int64s_are.go @@ -0,0 +1,119 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package setvalidator + +import ( + "context" + "fmt" + "strings" + + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/types/basetypes" +) + +// ValueInt64sAre returns an validator which ensures that any configured +// Int64 values passes each Int64 validator. +// +// Null (unconfigured) and unknown (known after apply) values are skipped. +func ValueInt64sAre(elementValidators ...validator.Int64) validator.Set { + return valueInt64sAreValidator{ + elementValidators: elementValidators, + } +} + +var _ validator.Set = valueInt64sAreValidator{} + +// valueInt64sAreValidator validates that each Int64 member validates against each of the value validators. +type valueInt64sAreValidator struct { + elementValidators []validator.Int64 +} + +// Description describes the validation in plain text formatting. +func (v valueInt64sAreValidator) Description(ctx context.Context) string { + var descriptions []string + + for _, elementValidator := range v.elementValidators { + descriptions = append(descriptions, elementValidator.Description(ctx)) + } + + return fmt.Sprintf("element value must satisfy all validations: %s", strings.Join(descriptions, " + ")) +} + +// MarkdownDescription describes the validation in Markdown formatting. +func (v valueInt64sAreValidator) MarkdownDescription(ctx context.Context) string { + return v.Description(ctx) +} + +// ValidateInt64 performs the validation. +func (v valueInt64sAreValidator) ValidateSet(ctx context.Context, req validator.SetRequest, resp *validator.SetResponse) { + if req.ConfigValue.IsNull() || req.ConfigValue.IsUnknown() { + return + } + + _, ok := req.ConfigValue.ElementType(ctx).(basetypes.Int64Typable) + + if !ok { + resp.Diagnostics.AddAttributeError( + req.Path, + "Invalid Validator for Element Type", + "While performing schema-based validation, an unexpected error occurred. "+ + "The attribute declares a Int64 values validator, however its values do not implement types.Int64Type or the types.Int64Typable interface for custom Int64 types. "+ + "Use the appropriate values validator that matches the element type. "+ + "This is always an issue with the provider and should be reported to the provider developers.\n\n"+ + fmt.Sprintf("Path: %s\n", req.Path.String())+ + fmt.Sprintf("Element Type: %T\n", req.ConfigValue.ElementType(ctx)), + ) + + return + } + + for _, element := range req.ConfigValue.Elements() { + elementPath := req.Path.AtSetValue(element) + + elementValuable, ok := element.(basetypes.Int64Valuable) + + // The check above should have prevented this, but raise an error + // instead of a type assertion panic or skipping the element. Any issue + // here likely indicates something wrong in the framework itself. + if !ok { + resp.Diagnostics.AddAttributeError( + req.Path, + "Invalid Validator for Element Value", + "While performing schema-based validation, an unexpected error occurred. "+ + "The attribute declares a Int64 values validator, however its values do not implement types.Int64Type or the types.Int64Typable interface for custom Int64 types. "+ + "This is likely an issue with terraform-plugin-framework and should be reported to the provider developers.\n\n"+ + fmt.Sprintf("Path: %s\n", req.Path.String())+ + fmt.Sprintf("Element Type: %T\n", req.ConfigValue.ElementType(ctx))+ + fmt.Sprintf("Element Value Type: %T\n", element), + ) + + return + } + + elementValue, diags := elementValuable.ToInt64Value(ctx) + + resp.Diagnostics.Append(diags...) + + // Only return early if the new diagnostics indicate an issue since + // it likely will be the same for all elements. + if diags.HasError() { + return + } + + elementReq := validator.Int64Request{ + Path: elementPath, + PathExpression: elementPath.Expression(), + ConfigValue: elementValue, + Config: req.Config, + } + + for _, elementValidator := range v.elementValidators { + elementResp := &validator.Int64Response{} + + elementValidator.ValidateInt64(ctx, elementReq, elementResp) + + resp.Diagnostics.Append(elementResp.Diagnostics...) + } + } +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-framework-validators/setvalidator/value_lists_are.go b/vendor/github.com/hashicorp/terraform-plugin-framework-validators/setvalidator/value_lists_are.go new file mode 100644 index 000000000..3f54b37e3 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-framework-validators/setvalidator/value_lists_are.go @@ -0,0 +1,119 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package setvalidator + +import ( + "context" + "fmt" + "strings" + + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/types/basetypes" +) + +// ValueListsAre returns an validator which ensures that any configured +// List values passes each List validator. +// +// Null (unconfigured) and unknown (known after apply) values are skipped. +func ValueListsAre(elementValidators ...validator.List) validator.Set { + return valueListsAreValidator{ + elementValidators: elementValidators, + } +} + +var _ validator.Set = valueListsAreValidator{} + +// valueListsAreValidator validates that each set member validates against each of the value validators. +type valueListsAreValidator struct { + elementValidators []validator.List +} + +// Description describes the validation in plain text formatting. +func (v valueListsAreValidator) Description(ctx context.Context) string { + var descriptions []string + + for _, elementValidator := range v.elementValidators { + descriptions = append(descriptions, elementValidator.Description(ctx)) + } + + return fmt.Sprintf("element value must satisfy all validations: %s", strings.Join(descriptions, " + ")) +} + +// MarkdownDescription describes the validation in Markdown formatting. +func (v valueListsAreValidator) MarkdownDescription(ctx context.Context) string { + return v.Description(ctx) +} + +// ValidateSet performs the validation. +func (v valueListsAreValidator) ValidateSet(ctx context.Context, req validator.SetRequest, resp *validator.SetResponse) { + if req.ConfigValue.IsNull() || req.ConfigValue.IsUnknown() { + return + } + + _, ok := req.ConfigValue.ElementType(ctx).(basetypes.ListTypable) + + if !ok { + resp.Diagnostics.AddAttributeError( + req.Path, + "Invalid Validator for Element Type", + "While performing schema-based validation, an unexpected error occurred. "+ + "The attribute declares a List values validator, however its values do not implement types.ListType or the types.ListTypable interface for custom List types. "+ + "Use the appropriate values validator that matches the element type. "+ + "This is always an issue with the provider and should be reported to the provider developers.\n\n"+ + fmt.Sprintf("Path: %s\n", req.Path.String())+ + fmt.Sprintf("Element Type: %T\n", req.ConfigValue.ElementType(ctx)), + ) + + return + } + + for _, element := range req.ConfigValue.Elements() { + elementPath := req.Path.AtSetValue(element) + + elementValuable, ok := element.(basetypes.ListValuable) + + // The check above should have prevented this, but raise an error + // instead of a type assertion panic or skipping the element. Any issue + // here likely indicates something wrong in the framework itself. + if !ok { + resp.Diagnostics.AddAttributeError( + req.Path, + "Invalid Validator for Element Value", + "While performing schema-based validation, an unexpected error occurred. "+ + "The attribute declares a List values validator, however its values do not implement types.ListType or the types.ListTypable interface for custom List types. "+ + "This is likely an issue with terraform-plugin-framework and should be reported to the provider developers.\n\n"+ + fmt.Sprintf("Path: %s\n", req.Path.String())+ + fmt.Sprintf("Element Type: %T\n", req.ConfigValue.ElementType(ctx))+ + fmt.Sprintf("Element Value Type: %T\n", element), + ) + + return + } + + elementValue, diags := elementValuable.ToListValue(ctx) + + resp.Diagnostics.Append(diags...) + + // Only return early if the new diagnostics indicate an issue since + // it likely will be the same for all elements. + if diags.HasError() { + return + } + + elementReq := validator.ListRequest{ + Path: elementPath, + PathExpression: elementPath.Expression(), + ConfigValue: elementValue, + Config: req.Config, + } + + for _, elementValidator := range v.elementValidators { + elementResp := &validator.ListResponse{} + + elementValidator.ValidateList(ctx, elementReq, elementResp) + + resp.Diagnostics.Append(elementResp.Diagnostics...) + } + } +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-framework-validators/setvalidator/value_maps_are.go b/vendor/github.com/hashicorp/terraform-plugin-framework-validators/setvalidator/value_maps_are.go new file mode 100644 index 000000000..19328be47 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-framework-validators/setvalidator/value_maps_are.go @@ -0,0 +1,119 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package setvalidator + +import ( + "context" + "fmt" + "strings" + + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/types/basetypes" +) + +// ValueMapsAre returns an validator which ensures that any configured +// Map values passes each Map validator. +// +// Null (unconfigured) and unknown (known after apply) values are skipped. +func ValueMapsAre(elementValidators ...validator.Map) validator.Set { + return valueMapsAreValidator{ + elementValidators: elementValidators, + } +} + +var _ validator.Set = valueMapsAreValidator{} + +// valueMapsAreValidator validates that each set member validates against each of the value validators. +type valueMapsAreValidator struct { + elementValidators []validator.Map +} + +// Description describes the validation in plain text formatting. +func (v valueMapsAreValidator) Description(ctx context.Context) string { + var descriptions []string + + for _, elementValidator := range v.elementValidators { + descriptions = append(descriptions, elementValidator.Description(ctx)) + } + + return fmt.Sprintf("element value must satisfy all validations: %s", strings.Join(descriptions, " + ")) +} + +// MarkdownDescription describes the validation in Markdown formatting. +func (v valueMapsAreValidator) MarkdownDescription(ctx context.Context) string { + return v.Description(ctx) +} + +// ValidateSet performs the validation. +func (v valueMapsAreValidator) ValidateSet(ctx context.Context, req validator.SetRequest, resp *validator.SetResponse) { + if req.ConfigValue.IsNull() || req.ConfigValue.IsUnknown() { + return + } + + _, ok := req.ConfigValue.ElementType(ctx).(basetypes.MapTypable) + + if !ok { + resp.Diagnostics.AddAttributeError( + req.Path, + "Invalid Validator for Element Type", + "While performing schema-based validation, an unexpected error occurred. "+ + "The attribute declares a Map values validator, however its values do not implement types.MapType or the types.MapTypable interface for custom Map types. "+ + "Use the appropriate values validator that matches the element type. "+ + "This is always an issue with the provider and should be reported to the provider developers.\n\n"+ + fmt.Sprintf("Path: %s\n", req.Path.String())+ + fmt.Sprintf("Element Type: %T\n", req.ConfigValue.ElementType(ctx)), + ) + + return + } + + for _, element := range req.ConfigValue.Elements() { + elementPath := req.Path.AtSetValue(element) + + elementValuable, ok := element.(basetypes.MapValuable) + + // The check above should have prevented this, but raise an error + // instead of a type assertion panic or skipping the element. Any issue + // here likely indicates something wrong in the framework itself. + if !ok { + resp.Diagnostics.AddAttributeError( + req.Path, + "Invalid Validator for Element Value", + "While performing schema-based validation, an unexpected error occurred. "+ + "The attribute declares a Map values validator, however its values do not implement types.MapType or the types.MapTypable interface for custom Map types. "+ + "This is likely an issue with terraform-plugin-framework and should be reported to the provider developers.\n\n"+ + fmt.Sprintf("Path: %s\n", req.Path.String())+ + fmt.Sprintf("Element Type: %T\n", req.ConfigValue.ElementType(ctx))+ + fmt.Sprintf("Element Value Type: %T\n", element), + ) + + return + } + + elementValue, diags := elementValuable.ToMapValue(ctx) + + resp.Diagnostics.Append(diags...) + + // Only return early if the new diagnostics indicate an issue since + // it likely will be the same for all elements. + if diags.HasError() { + return + } + + elementReq := validator.MapRequest{ + Path: elementPath, + PathExpression: elementPath.Expression(), + ConfigValue: elementValue, + Config: req.Config, + } + + for _, elementValidator := range v.elementValidators { + elementResp := &validator.MapResponse{} + + elementValidator.ValidateMap(ctx, elementReq, elementResp) + + resp.Diagnostics.Append(elementResp.Diagnostics...) + } + } +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-framework-validators/setvalidator/value_numbers_are.go b/vendor/github.com/hashicorp/terraform-plugin-framework-validators/setvalidator/value_numbers_are.go new file mode 100644 index 000000000..abcc496ae --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-framework-validators/setvalidator/value_numbers_are.go @@ -0,0 +1,119 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package setvalidator + +import ( + "context" + "fmt" + "strings" + + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/types/basetypes" +) + +// ValueNumbersAre returns an validator which ensures that any configured +// Number values passes each Number validator. +// +// Null (unconfigured) and unknown (known after apply) values are skipped. +func ValueNumbersAre(elementValidators ...validator.Number) validator.Set { + return valueNumbersAreValidator{ + elementValidators: elementValidators, + } +} + +var _ validator.Set = valueNumbersAreValidator{} + +// valueNumbersAreValidator validates that each Number member validates against each of the value validators. +type valueNumbersAreValidator struct { + elementValidators []validator.Number +} + +// Description describes the validation in plain text formatting. +func (v valueNumbersAreValidator) Description(ctx context.Context) string { + var descriptions []string + + for _, elementValidator := range v.elementValidators { + descriptions = append(descriptions, elementValidator.Description(ctx)) + } + + return fmt.Sprintf("element value must satisfy all validations: %s", strings.Join(descriptions, " + ")) +} + +// MarkdownDescription describes the validation in Markdown formatting. +func (v valueNumbersAreValidator) MarkdownDescription(ctx context.Context) string { + return v.Description(ctx) +} + +// ValidateNumber performs the validation. +func (v valueNumbersAreValidator) ValidateSet(ctx context.Context, req validator.SetRequest, resp *validator.SetResponse) { + if req.ConfigValue.IsNull() || req.ConfigValue.IsUnknown() { + return + } + + _, ok := req.ConfigValue.ElementType(ctx).(basetypes.NumberTypable) + + if !ok { + resp.Diagnostics.AddAttributeError( + req.Path, + "Invalid Validator for Element Type", + "While performing schema-based validation, an unexpected error occurred. "+ + "The attribute declares a Number values validator, however its values do not implement types.NumberType or the types.NumberTypable interface for custom Number types. "+ + "Use the appropriate values validator that matches the element type. "+ + "This is always an issue with the provider and should be reported to the provider developers.\n\n"+ + fmt.Sprintf("Path: %s\n", req.Path.String())+ + fmt.Sprintf("Element Type: %T\n", req.ConfigValue.ElementType(ctx)), + ) + + return + } + + for _, element := range req.ConfigValue.Elements() { + elementPath := req.Path.AtSetValue(element) + + elementValuable, ok := element.(basetypes.NumberValuable) + + // The check above should have prevented this, but raise an error + // instead of a type assertion panic or skipping the element. Any issue + // here likely indicates something wrong in the framework itself. + if !ok { + resp.Diagnostics.AddAttributeError( + req.Path, + "Invalid Validator for Element Value", + "While performing schema-based validation, an unexpected error occurred. "+ + "The attribute declares a Number values validator, however its values do not implement types.NumberType or the types.NumberTypable interface for custom Number types. "+ + "This is likely an issue with terraform-plugin-framework and should be reported to the provider developers.\n\n"+ + fmt.Sprintf("Path: %s\n", req.Path.String())+ + fmt.Sprintf("Element Type: %T\n", req.ConfigValue.ElementType(ctx))+ + fmt.Sprintf("Element Value Type: %T\n", element), + ) + + return + } + + elementValue, diags := elementValuable.ToNumberValue(ctx) + + resp.Diagnostics.Append(diags...) + + // Only return early if the new diagnostics indicate an issue since + // it likely will be the same for all elements. + if diags.HasError() { + return + } + + elementReq := validator.NumberRequest{ + Path: elementPath, + PathExpression: elementPath.Expression(), + ConfigValue: elementValue, + Config: req.Config, + } + + for _, elementValidator := range v.elementValidators { + elementResp := &validator.NumberResponse{} + + elementValidator.ValidateNumber(ctx, elementReq, elementResp) + + resp.Diagnostics.Append(elementResp.Diagnostics...) + } + } +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-framework-validators/setvalidator/value_sets_are.go b/vendor/github.com/hashicorp/terraform-plugin-framework-validators/setvalidator/value_sets_are.go new file mode 100644 index 000000000..023582a04 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-framework-validators/setvalidator/value_sets_are.go @@ -0,0 +1,119 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package setvalidator + +import ( + "context" + "fmt" + "strings" + + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/types/basetypes" +) + +// ValueSetsAre returns an validator which ensures that any configured +// Set values passes each Set validator. +// +// Null (unconfigured) and unknown (known after apply) values are skipped. +func ValueSetsAre(elementValidators ...validator.Set) validator.Set { + return valueSetsAreValidator{ + elementValidators: elementValidators, + } +} + +var _ validator.Set = valueSetsAreValidator{} + +// valueSetsAreValidator validates that each set member validates against each of the value validators. +type valueSetsAreValidator struct { + elementValidators []validator.Set +} + +// Description describes the validation in plain text formatting. +func (v valueSetsAreValidator) Description(ctx context.Context) string { + var descriptions []string + + for _, elementValidator := range v.elementValidators { + descriptions = append(descriptions, elementValidator.Description(ctx)) + } + + return fmt.Sprintf("element value must satisfy all validations: %s", strings.Join(descriptions, " + ")) +} + +// MarkdownDescription describes the validation in Markdown formatting. +func (v valueSetsAreValidator) MarkdownDescription(ctx context.Context) string { + return v.Description(ctx) +} + +// ValidateSet performs the validation. +func (v valueSetsAreValidator) ValidateSet(ctx context.Context, req validator.SetRequest, resp *validator.SetResponse) { + if req.ConfigValue.IsNull() || req.ConfigValue.IsUnknown() { + return + } + + _, ok := req.ConfigValue.ElementType(ctx).(basetypes.SetTypable) + + if !ok { + resp.Diagnostics.AddAttributeError( + req.Path, + "Invalid Validator for Element Type", + "While performing schema-based validation, an unexpected error occurred. "+ + "The attribute declares a Set values validator, however its values do not implement types.SetType or the types.SetTypable interface for custom Set types. "+ + "Use the appropriate values validator that matches the element type. "+ + "This is always an issue with the provider and should be reported to the provider developers.\n\n"+ + fmt.Sprintf("Path: %s\n", req.Path.String())+ + fmt.Sprintf("Element Type: %T\n", req.ConfigValue.ElementType(ctx)), + ) + + return + } + + for _, element := range req.ConfigValue.Elements() { + elementPath := req.Path.AtSetValue(element) + + elementValuable, ok := element.(basetypes.SetValuable) + + // The check above should have prevented this, but raise an error + // instead of a type assertion panic or skipping the element. Any issue + // here likely indicates something wrong in the framework itself. + if !ok { + resp.Diagnostics.AddAttributeError( + req.Path, + "Invalid Validator for Element Value", + "While performing schema-based validation, an unexpected error occurred. "+ + "The attribute declares a Set values validator, however its values do not implement types.SetType or the types.SetTypable interface for custom Set types. "+ + "This is likely an issue with terraform-plugin-framework and should be reported to the provider developers.\n\n"+ + fmt.Sprintf("Path: %s\n", req.Path.String())+ + fmt.Sprintf("Element Type: %T\n", req.ConfigValue.ElementType(ctx))+ + fmt.Sprintf("Element Value Type: %T\n", element), + ) + + return + } + + elementValue, diags := elementValuable.ToSetValue(ctx) + + resp.Diagnostics.Append(diags...) + + // Only return early if the new diagnostics indicate an issue since + // it likely will be the same for all elements. + if diags.HasError() { + return + } + + elementReq := validator.SetRequest{ + Path: elementPath, + PathExpression: elementPath.Expression(), + ConfigValue: elementValue, + Config: req.Config, + } + + for _, elementValidator := range v.elementValidators { + elementResp := &validator.SetResponse{} + + elementValidator.ValidateSet(ctx, elementReq, elementResp) + + resp.Diagnostics.Append(elementResp.Diagnostics...) + } + } +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-framework-validators/setvalidator/value_strings_are.go b/vendor/github.com/hashicorp/terraform-plugin-framework-validators/setvalidator/value_strings_are.go new file mode 100644 index 000000000..38b4b7815 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-framework-validators/setvalidator/value_strings_are.go @@ -0,0 +1,119 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package setvalidator + +import ( + "context" + "fmt" + "strings" + + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/types/basetypes" +) + +// ValueStringsAre returns an validator which ensures that any configured +// String values passes each String validator. +// +// Null (unconfigured) and unknown (known after apply) values are skipped. +func ValueStringsAre(elementValidators ...validator.String) validator.Set { + return valueStringsAreValidator{ + elementValidators: elementValidators, + } +} + +var _ validator.Set = valueStringsAreValidator{} + +// valueStringsAreValidator validates that each set member validates against each of the value validators. +type valueStringsAreValidator struct { + elementValidators []validator.String +} + +// Description describes the validation in plain text formatting. +func (v valueStringsAreValidator) Description(ctx context.Context) string { + var descriptions []string + + for _, elementValidator := range v.elementValidators { + descriptions = append(descriptions, elementValidator.Description(ctx)) + } + + return fmt.Sprintf("element value must satisfy all validations: %s", strings.Join(descriptions, " + ")) +} + +// MarkdownDescription describes the validation in Markdown formatting. +func (v valueStringsAreValidator) MarkdownDescription(ctx context.Context) string { + return v.Description(ctx) +} + +// ValidateSet performs the validation. +func (v valueStringsAreValidator) ValidateSet(ctx context.Context, req validator.SetRequest, resp *validator.SetResponse) { + if req.ConfigValue.IsNull() || req.ConfigValue.IsUnknown() { + return + } + + _, ok := req.ConfigValue.ElementType(ctx).(basetypes.StringTypable) + + if !ok { + resp.Diagnostics.AddAttributeError( + req.Path, + "Invalid Validator for Element Type", + "While performing schema-based validation, an unexpected error occurred. "+ + "The attribute declares a String values validator, however its values do not implement types.StringType or the types.StringTypable interface for custom String types. "+ + "Use the appropriate values validator that matches the element type. "+ + "This is always an issue with the provider and should be reported to the provider developers.\n\n"+ + fmt.Sprintf("Path: %s\n", req.Path.String())+ + fmt.Sprintf("Element Type: %T\n", req.ConfigValue.ElementType(ctx)), + ) + + return + } + + for _, element := range req.ConfigValue.Elements() { + elementPath := req.Path.AtSetValue(element) + + elementValuable, ok := element.(basetypes.StringValuable) + + // The check above should have prevented this, but raise an error + // instead of a type assertion panic or skipping the element. Any issue + // here likely indicates something wrong in the framework itself. + if !ok { + resp.Diagnostics.AddAttributeError( + req.Path, + "Invalid Validator for Element Value", + "While performing schema-based validation, an unexpected error occurred. "+ + "The attribute declares a String values validator, however its values do not implement types.StringType or the types.StringTypable interface for custom String types. "+ + "This is likely an issue with terraform-plugin-framework and should be reported to the provider developers.\n\n"+ + fmt.Sprintf("Path: %s\n", req.Path.String())+ + fmt.Sprintf("Element Type: %T\n", req.ConfigValue.ElementType(ctx))+ + fmt.Sprintf("Element Value Type: %T\n", element), + ) + + return + } + + elementValue, diags := elementValuable.ToStringValue(ctx) + + resp.Diagnostics.Append(diags...) + + // Only return early if the new diagnostics indicate an issue since + // it likely will be the same for all elements. + if diags.HasError() { + return + } + + elementReq := validator.StringRequest{ + Path: elementPath, + PathExpression: elementPath.Expression(), + ConfigValue: elementValue, + Config: req.Config, + } + + for _, elementValidator := range v.elementValidators { + elementResp := &validator.StringResponse{} + + elementValidator.ValidateString(ctx, elementReq, elementResp) + + resp.Diagnostics.Append(elementResp.Diagnostics...) + } + } +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 378228d51..81aff8d93 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -1,4 +1,4 @@ -# github.com/PagerDuty/go-pagerduty v1.8.1-0.20240524180345-9b652f07c450 +# github.com/PagerDuty/go-pagerduty v1.8.1-0.20241002154647-8ceedfd04d88 ## explicit; go 1.19 github.com/PagerDuty/go-pagerduty # github.com/ProtonMail/go-crypto v0.0.0-20230923063757-afb1ddc0824c @@ -183,6 +183,7 @@ github.com/hashicorp/terraform-plugin-framework-validators/helpers/validatordiag github.com/hashicorp/terraform-plugin-framework-validators/int64validator github.com/hashicorp/terraform-plugin-framework-validators/internal/schemavalidator github.com/hashicorp/terraform-plugin-framework-validators/listvalidator +github.com/hashicorp/terraform-plugin-framework-validators/setvalidator github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator # github.com/hashicorp/terraform-plugin-go v0.20.0 ## explicit; go 1.20 @@ -422,7 +423,7 @@ golang.org/x/net/http2/hpack golang.org/x/net/idna golang.org/x/net/internal/timeseries golang.org/x/net/trace -# golang.org/x/oauth2 v0.18.0 +# golang.org/x/oauth2 v0.15.0 ## explicit; go 1.18 golang.org/x/oauth2 golang.org/x/oauth2/clientcredentials diff --git a/website/docs/d/alert_grouping_setting.html.markdown b/website/docs/d/alert_grouping_setting.html.markdown new file mode 100644 index 000000000..0a0f5e40e --- /dev/null +++ b/website/docs/d/alert_grouping_setting.html.markdown @@ -0,0 +1,43 @@ +--- +layout: "pagerduty" +page_title: "PagerDuty: pagerduty_alert_grouping_setting" +sidebar_current: "docs-pagerduty-datasource-alert-grouping-setting" +description: |- + Get information about an alert grouping setting that you have created. +--- + +# pagerduty\_alert\_grouping\_setting + +Use this data source to get information about a specific [alert grouping setting][1]. + +## Example Usage + +```hcl +data "pagerduty_alert_grouping_setting" "example" { + name = "My example setting" +} +``` + +## Argument Reference + +The following arguments are supported: + +* `name` - (Required) The name to use to find an alert grouping setting in the PagerDuty API. + +## Attributes Reference + +* `id` - The ID of the found alert grouping setting. +* `name` - The short name of the found alert grouping setting. +* `description` - A description of this alert grouping setting. +* `type` - The type of object. The value returned will be one of `content_based`, `content_based_intelligent`, `intelligent` or `time`. +* `config` - The values for the configuration setup for this setting. +* `services` - A list of string containing the IDs of services associated with this setting. + +The `config` block contains the following arguments: + +* `timeout` - The duration in minutes within which to automatically group incoming alerts. This setting is only required and applies when `type` is set to `time`. To continue grouping alerts until the incident is resolved leave this value unset or set it to `null`. +* `aggregate` - One of `any` or `all`. This setting is only required and applies when `type` is set to `content_based` or `content_based_intelligent`. Group alerts based on one or all of `fields` value(s). +* `fields` - Alerts will be grouped together if the content of these fields match. This setting is only required and applies when `type` is set to `content_based` or `content_based_intelligent`. +* `time_window` - The maximum amount of time allowed between Alerts. This setting applies only when `type` is set to `intelligent`, `content_based`, `content_based_intelligent`. Value must be between `300` and `3600` or exactly `86400` (86400 is supported only for `content_based` alert grouping). Any Alerts arriving greater than `time_window` seconds apart will not be grouped together. This is a rolling time window and is counted from the most recently grouped alert. The window is extended every time a new alert is added to the group, up to 24 hours. To use the recommended time window leave this value unset or set it to `null`. + +[1]: https://developer.pagerduty.com/api-reference/9b5a6c8d7379b-get-an-alert-grouping-setting diff --git a/website/docs/r/alert_grouping_setting.html.markdown b/website/docs/r/alert_grouping_setting.html.markdown new file mode 100644 index 000000000..c9cf1e283 --- /dev/null +++ b/website/docs/r/alert_grouping_setting.html.markdown @@ -0,0 +1,226 @@ +--- +layout: "pagerduty" +page_title: "PagerDuty: pagerduty_alert_grouping_setting" +sidebar_current: "docs-pagerduty-resource-alert-grouping-setting" +description: |- + Creates and manages an alert grouping setting in PagerDuty. +--- + +# pagerduty\_alert\_grouping\_setting + +An [alert grouping setting](https://developer.pagerduty.com/api-reference/create-an-alert-grouping-setting) +stores and centralize the configuration used during grouping of the alerts. + +## Example Usage + +```hcl +data "pagerduty_escalation_policy" "default" { + name = "Default" +} + +resource "pagerduty_service" "basic" { + name = "Example" + escalation_policy = data.pagerduty_escalation_policy.default.id +} + +resource "pagerduty_alert_grouping_setting" "%[1]s" { + name = "Configuration for type-1 devices" + type = "content_based" + services = [pagerduty_service.basic.id] + config { + time_window = 300 + aggregate = "all" + fields = ["fields"] + } +} +``` + +## Argument Reference + +The following arguments are supported: + +* `name` - (Required) The name for the alert groupig settings. +* `type` - (Required) The type of alert grouping; one of `intelligent`, `time`, `content_based` or `content_based_intelligent`. +* `services` - (Required) [Updating can cause a resource replacement] The list IDs of services associated to this setting. +* `config` - (Required) The set of values used for configuration. + +The `config` block contains the following arguments: + +* `timeout` - (Optional) The duration in minutes within which to automatically group incoming alerts. This setting is only required and applies when `type` is set to `time`. To continue grouping alerts until the incident is resolved leave this value unset or set it to `null`. +* `aggregate` - (Optional) One of `any` or `all`. This setting is only required and applies when `type` is set to `content_based` or `content_based_intelligent`. Group alerts based on one or all of `fields` value(s). +* `fields` - (Optional) Alerts will be grouped together if the content of these fields match. This setting is only required and applies when `type` is set to `content_based` or `content_based_intelligent`. +* `time_window` - (Optional) The maximum amount of time allowed between Alerts. This setting applies only when `type` is set to `intelligent`, `content_based`, `content_based_intelligent`. Value must be between `300` and `3600` or exactly `86400` (86400 is supported only for `content_based` alert grouping). Any Alerts arriving greater than `time_window` seconds apart will not be grouped together. This is a rolling time window and is counted from the most recently grouped alert. The window is extended every time a new alert is added to the group, up to 24 hours. To use the recommended time window leave this value unset or set it to `null`. + +## Attributes Reference + +The following attributes are exported: + + * `id` - The ID of the alert grouping setting. + +## Migration from `alert_grouping_parameters` + +To migrate from using the field `alert_grouping_parameters` of a +[service](https://registry.terraform.io/providers/PagerDuty/pagerduty/latest/docs/resources/service) +to a `pagerduty_alert_grouping_setting` resource, you can cut-and-paste the +contents of an `alert_grouping_parameters` field from a `pagerduty_service` +resource into the new resource, but you also need to add at least one value in +the field `services` to create the alert grouping setting with a service +associated to it. + +If you are using `timeout = 0` or `time_window = 0` in order to use the values +recommended by PagerDuty you also need to set its value to null or delete it, +since a value of `0` is no longer accepted. + +Since the `alert_grouping_parameters` field creates an Alert Grouping Setting +behind the scenes, it is necessary to import them if you want to keep your +configuration the same as it is right now. + +**Example**: + +Before: +``` +data "pagerduty_escalation_policy" "default" { + name = "Default" +} + +resource "pagerduty_service" "foo" { + name = "Foo" + escalation_policy = data.pagerduty_escalation_policy.default.id + alert_grouping_parameters { + type = "time" + config { + timeout = 0 + } + } +} +``` + +After: +``` +data "pagerduty_escalation_policy" "default" { + name = "Default" +} + +resource "pagerduty_service" "foo" { + name = "Foo" + escalation_policy = data.pagerduty_escalation_policy.default.id +} + +data "pagerduty_alert_grouping_setting" "foo_alert" { + name = "Foo" +} + +import { + id = data.pagerduty_alert_grouping_setting.foo_alert.id + to = pagerduty_alert_grouping_setting.foo_alert +} + +resource "pagerduty_alert_grouping_setting" "foo_alert" { + name = "" + type = "content_based" + config { + aggregate = "all" + fields = ["summary"] + } + services = [pagerduty_service.foo.id] +} +``` + +But if you prefer to have a clean restart, you can do it in two steps: delete +the current `alert_grouping_parameters` and later create a new +`alert_grouping_setting` associated to your resources now free to be associated +with this Alert Grouping Setting. + +**Example**: + +Before: +``` +data "pagerduty_escalation_policy" "default" { + name = "Default" +} + +resource "pagerduty_service" "foo" { + name = "Foo" + escalation_policy = data.pagerduty_escalation_policy.default.id + alert_grouping_parameters { + type = "content_based" + config { + time_window = 300 + aggregate = "all" + fields = ["summary"] + } + } +} + +resource "pagerduty_service" "bar" { + name = "Bar" + escalation_policy = data.pagerduty_escalation_policy.default.id + alert_grouping_parameters { + type = "content_based" + config { + time_window = 300 + aggregate = "all" + fields = ["summary"] + } + } +} +``` + +Step 1: +``` +data "pagerduty_escalation_policy" "default" { + name = "Default" +} + +resource "pagerduty_service" "foo" { + name = "Foo" + escalation_policy = data.pagerduty_escalation_policy.default.id + alert_grouping_parameters {} +} + +resource "pagerduty_service" "bar" { + name = "Bar" + escalation_policy = data.pagerduty_escalation_policy.default.id + alert_grouping_parameters {} +} +``` + +Step 2: +``` +data "pagerduty_escalation_policy" "default" { + name = "Default" +} + +resource "pagerduty_service" "foo" { + name = "Foo" + escalation_policy = data.pagerduty_escalation_policy.default.id +} + +resource "pagerduty_service" "bar" { + name = "Bar" + escalation_policy = data.pagerduty_escalation_policy.default.id +} + +resource "pagerduty_alert_grouping_setting" "type_a" { + name = "Type A" + description = "Configuration used for all services of type A" + type = "content_based" + config { + time_window = 300 + aggregate = "all" + fields = ["summary"] + } + services = [ + pagerduty_service.foo.id, + pagerduty_service.bar.id, + ] +} +``` + +## Import + +Alert grouping settings can be imported using its `id`, e.g. + +``` +$ terraform import pagerduty_alert_grouping_setting.example P3DH5M6 +``` diff --git a/website/docs/r/service.html.markdown b/website/docs/r/service.html.markdown index dd96ea600..24d005a41 100644 --- a/website/docs/r/service.html.markdown +++ b/website/docs/r/service.html.markdown @@ -57,11 +57,11 @@ The following arguments are supported: * `auto_resolve_timeout` - (Optional) Time in seconds that an incident is automatically resolved if left open for that long. Disabled if set to the `"null"` string. * `acknowledgement_timeout` - (Optional) Time in seconds that an incident changes to the Triggered State after being Acknowledged. Disabled if set to the `"null"` string. If not passed in, will default to '"1800"'. * `escalation_policy` - (Required) The escalation policy used by this service. - * `response_play` - (Optional) The response play used by this service. + * `response_play` - (Optional) (Deprecated) The response play used by this service. * `alert_creation` - (Optional) (Deprecated) This attribute has been deprecated as all services will be migrated to use alerts and incidents. The incident only service setting will be no longer available and this attribute will be removed in an upcoming version. See knowledge base for details https://support.pagerduty.com/docs/alerts#enable-and-disable-alerts-on-a-service. * `alert_grouping` - (Optional) (Deprecated) Defines how alerts on this service will be automatically grouped into incidents. Note that the alert grouping features are available only on certain plans. If not set, each alert will create a separate incident; If value is set to `time`: All alerts within a specified duration will be grouped into the same incident. This duration is set in the `alert_grouping_timeout` setting (described below). Available on Standard, Enterprise, and Event Intelligence plans; If value is set to `intelligent` - Alerts will be intelligently grouped based on a machine learning model that looks at the alert summary, timing, and the history of grouped alerts. Available on Enterprise and Event Intelligence plan. This field is deprecated, use `alert_grouping_parameters.type` instead, * `alert_grouping_timeout` - (Optional) (Deprecated) The duration in minutes within which to automatically group incoming alerts. This setting applies only when `alert_grouping` is set to `time`. To continue grouping alerts until the incident is resolved, set this value to `0`. This field is deprecated, use `alert_grouping_parameters.config.timeout` instead, - * `alert_grouping_parameters` - (Optional) Defines how alerts on this service will be automatically grouped into incidents. Note that the alert grouping features are available only on certain plans. If not set, each alert will create a separate incident. + * `alert_grouping_parameters` - (Optional) (Deprecated) Defines how alerts on this service will be automatically grouped into incidents. Note that the alert grouping features are available only on certain plans. If not set, each alert will create a separate incident. Instructions on how to migrate this configuration to `pagerduty_alert_grouping_setting` resource can be found [here](https://registry.terraform.io/providers/PagerDuty/pagerduty/latest/docs/resources/alert_grouping_setting#migration-from-alert_grouping_parameters). * `auto_pause_notifications_parameters` - (Optional) Defines how alerts on this service are automatically suspended for a period of time before triggering, when identified as likely being transient. Note that automatically pausing notifications is only available on certain plans as mentioned [here](https://support.pagerduty.com/docs/auto-pause-incident-notifications). The `alert_grouping_parameters` block contains the following arguments: