diff --git a/pagerduty/resource_pagerduty_service_test.go b/pagerduty/resource_pagerduty_service_test.go index e9fc56dcd..96031fc6b 100644 --- a/pagerduty/resource_pagerduty_service_test.go +++ b/pagerduty/resource_pagerduty_service_test.go @@ -120,79 +120,6 @@ resource "pagerduty_service" "foo" { `, username, email, escalationPolicy, service) } -func testAccCheckPagerDutyServiceAlertGroupingInputValidationConfig(username, email, escalationPolicy, service, alertGroupingParams string) string { - return fmt.Sprintf(` -resource "pagerduty_user" "foo" { - name = "%s" - email = "%s" - color = "green" - role = "user" - job_title = "foo" - description = "foo" -} - -resource "pagerduty_escalation_policy" "foo" { - name = "%s" - description = "bar" - num_loops = 2 - rule { - escalation_delay_in_minutes = 10 - target { - type = "user_reference" - id = pagerduty_user.foo.id - } - } -} - -resource "pagerduty_service" "foo" { - name = "%s" - description = "foo" - auto_resolve_timeout = 1800 - acknowledgement_timeout = 1800 - escalation_policy = pagerduty_escalation_policy.foo.id - alert_creation = "create_alerts_and_incidents" - %s -} -`, username, email, escalationPolicy, service, alertGroupingParams) -} - -func testAccCheckPagerDutyServiceConfigWithAlertGrouping(username, email, escalationPolicy, service string) string { - return fmt.Sprintf(` -resource "pagerduty_user" "foo" { - name = "%s" - email = "%s" - color = "green" - role = "user" - job_title = "foo" - description = "foo" -} - -resource "pagerduty_escalation_policy" "foo" { - name = "%s" - description = "bar" - num_loops = 2 - rule { - escalation_delay_in_minutes = 10 - target { - type = "user_reference" - id = pagerduty_user.foo.id - } - } -} - -resource "pagerduty_service" "foo" { - name = "%s" - description = "foo" - auto_resolve_timeout = 1800 - acknowledgement_timeout = 1800 - escalation_policy = pagerduty_escalation_policy.foo.id - alert_creation = "create_alerts_and_incidents" - alert_grouping = "time" - alert_grouping_timeout = 1800 -} -`, username, email, escalationPolicy, service) -} - func testAccCheckPagerDutyServiceConfigWithAlertContentGrouping(username, email, escalationPolicy, service string) string { return fmt.Sprintf(` resource "pagerduty_user" "foo" { @@ -235,592 +162,6 @@ resource "pagerduty_service" "foo" { `, username, email, escalationPolicy, service) } -func testAccCheckPagerDutyServiceConfigWithAlertContentGroupingIntelligentTimeWindow(username, email, escalationPolicy, service string) string { - return fmt.Sprintf(` -resource "pagerduty_user" "foo" { - name = "%s" - email = "%s" - color = "green" - role = "user" - job_title = "foo" - description = "foo" -} - -resource "pagerduty_escalation_policy" "foo" { - name = "%s" - description = "bar" - num_loops = 2 - rule { - escalation_delay_in_minutes = 10 - target { - type = "user_reference" - id = pagerduty_user.foo.id - } - } -} - -resource "pagerduty_service" "foo" { - name = "%s" - description = "foo" - auto_resolve_timeout = 1800 - acknowledgement_timeout = 1800 - escalation_policy = pagerduty_escalation_policy.foo.id - alert_creation = "create_alerts_and_incidents" - alert_grouping_parameters { - type = "intelligent" - } -} -`, username, email, escalationPolicy, service) -} -func testAccCheckPagerDutyServiceConfigWithAlertContentGroupingIntelligentTimeWindowUpdated(username, email, escalationPolicy, service string) string { - return fmt.Sprintf(` -resource "pagerduty_user" "foo" { - name = "%s" - email = "%s" - color = "green" - role = "user" - job_title = "foo" - description = "foo" -} - -resource "pagerduty_escalation_policy" "foo" { - name = "%s" - description = "bar" - num_loops = 2 - rule { - escalation_delay_in_minutes = 10 - target { - type = "user_reference" - id = pagerduty_user.foo.id - } - } -} - -resource "pagerduty_service" "foo" { - name = "%s" - description = "foo" - auto_resolve_timeout = 1800 - acknowledgement_timeout = 1800 - escalation_policy = pagerduty_escalation_policy.foo.id - alert_creation = "create_alerts_and_incidents" - alert_grouping_parameters { - type = "intelligent" - config { - time_window = 900 - } - } -} -`, username, email, escalationPolicy, service) -} - -func testAccCheckPagerDutyServiceConfigWithAlertContentGroupingUpdated(username, email, escalationPolicy, service string) string { - return fmt.Sprintf(` -resource "pagerduty_user" "foo" { - name = "%s" - email = "%s" - color = "green" - role = "user" - job_title = "foo" - description = "foo" -} - -resource "pagerduty_escalation_policy" "foo" { - name = "%s" - description = "bar" - num_loops = 2 - rule { - escalation_delay_in_minutes = 10 - target { - type = "user_reference" - id = pagerduty_user.foo.id - } - } -} - -resource "pagerduty_service" "foo" { - name = "%s" - description = "foo" - auto_resolve_timeout = 1800 - acknowledgement_timeout = 1800 - escalation_policy = pagerduty_escalation_policy.foo.id - alert_creation = "create_alerts_and_incidents" - alert_grouping_parameters { - type = null - } -} -`, username, email, escalationPolicy, service) -} - -func testAccCheckPagerDutyServiceConfigWithAlertTimeGroupingUpdated(username, email, escalationPolicy, service string) string { - return fmt.Sprintf(` -resource "pagerduty_user" "foo" { - name = "%s" - email = "%s" - color = "green" - role = "user" - job_title = "foo" - description = "foo" -} - -resource "pagerduty_escalation_policy" "foo" { - name = "%s" - description = "bar" - num_loops = 2 - rule { - escalation_delay_in_minutes = 10 - target { - type = "user_reference" - id = pagerduty_user.foo.id - } - } -} - -resource "pagerduty_service" "foo" { - name = "%s" - description = "foo" - auto_resolve_timeout = 1800 - acknowledgement_timeout = 1800 - escalation_policy = pagerduty_escalation_policy.foo.id - alert_creation = "create_alerts_and_incidents" - alert_grouping_parameters { - type = "time" - config { - timeout = 5 - } - } -} -`, username, email, escalationPolicy, service) -} - -func testAccCheckPagerDutyServiceConfigWithAlertTimeGroupingTimeoutZeroUpdated(username, email, escalationPolicy, service string) string { - return fmt.Sprintf(` -resource "pagerduty_user" "foo" { - name = "%s" - email = "%s" - color = "green" - role = "user" - job_title = "foo" - description = "foo" -} - -resource "pagerduty_escalation_policy" "foo" { - name = "%s" - description = "bar" - num_loops = 2 - rule { - escalation_delay_in_minutes = 10 - target { - type = "user_reference" - id = pagerduty_user.foo.id - } - } -} - -resource "pagerduty_service" "foo" { - name = "%s" - description = "foo" - auto_resolve_timeout = 1800 - acknowledgement_timeout = 1800 - escalation_policy = pagerduty_escalation_policy.foo.id - alert_creation = "create_alerts_and_incidents" - alert_grouping_parameters { - type = "time" - config { - timeout = 0 - } - } -} -`, username, email, escalationPolicy, service) -} - -func testAccCheckPagerDutyServiceConfigWithAlertGroupingUpdated(username, email, escalationPolicy, service string) string { - return fmt.Sprintf(` -resource "pagerduty_user" "foo" { - name = "%s" - email = "%s" - color = "green" - role = "user" - job_title = "foo" - description = "foo" -} - -resource "pagerduty_escalation_policy" "foo" { - name = "%s" - description = "bar" - num_loops = 2 - rule { - escalation_delay_in_minutes = 10 - target { - type = "user_reference" - id = pagerduty_user.foo.id - } - } -} - -resource "pagerduty_service" "foo" { - name = "%s" - description = "foo" - auto_resolve_timeout = 1800 - acknowledgement_timeout = 1800 - escalation_policy = pagerduty_escalation_policy.foo.id - alert_creation = "create_alerts_and_incidents" - alert_grouping = "intelligent" - alert_grouping_timeout = 1900 -} -`, username, email, escalationPolicy, service) -} - -func testAccCheckPagerDutyServiceConfigWithAlertIntelligentGroupingUpdated(username, email, escalationPolicy, service string) string { - return fmt.Sprintf(` -resource "pagerduty_user" "foo" { - name = "%s" - email = "%s" - color = "green" - role = "user" - job_title = "foo" - description = "foo" -} - -resource "pagerduty_escalation_policy" "foo" { - name = "%s" - description = "bar" - num_loops = 2 - rule { - escalation_delay_in_minutes = 10 - target { - type = "user_reference" - id = pagerduty_user.foo.id - } - } -} - -resource "pagerduty_service" "foo" { - name = "%s" - description = "foo" - auto_resolve_timeout = 1800 - acknowledgement_timeout = 1800 - escalation_policy = pagerduty_escalation_policy.foo.id - alert_creation = "create_alerts_and_incidents" - alert_grouping_parameters { - type = "intelligent" - config { - fields = null - timeout = 0 - } - } -} -`, username, email, escalationPolicy, service) -} - -func testAccCheckPagerDutyServiceConfigWithAlertIntelligentGroupingDescriptionUpdated(username, email, escalationPolicy, service string) string { - return fmt.Sprintf(` -resource "pagerduty_user" "foo" { - name = "%s" - email = "%s" - color = "green" - role = "user" - job_title = "foo" - description = "foo" -} - -resource "pagerduty_escalation_policy" "foo" { - name = "%s" - description = "bar" - num_loops = 2 - rule { - escalation_delay_in_minutes = 10 - target { - type = "user_reference" - id = pagerduty_user.foo.id - } - } -} - -resource "pagerduty_service" "foo" { - name = "%s" - description = "bar" - auto_resolve_timeout = 1800 - acknowledgement_timeout = 1800 - escalation_policy = pagerduty_escalation_policy.foo.id - alert_creation = "create_alerts_and_incidents" - alert_grouping_parameters { - type = "intelligent" - config {} - } -} -`, username, email, escalationPolicy, service) -} - -func testAccCheckPagerDutyServiceConfigWithAlertIntelligentGroupingOmittingConfig(username, email, escalationPolicy, service string) string { - return fmt.Sprintf(` -resource "pagerduty_user" "foo" { - name = "%s" - email = "%s" - color = "green" - role = "user" - job_title = "foo" - description = "foo" -} - -resource "pagerduty_escalation_policy" "foo" { - name = "%s" - description = "bar" - num_loops = 2 - rule { - escalation_delay_in_minutes = 10 - target { - type = "user_reference" - id = pagerduty_user.foo.id - } - } -} - -resource "pagerduty_service" "foo" { - name = "%s" - description = "bar" - auto_resolve_timeout = 1800 - acknowledgement_timeout = 1800 - escalation_policy = pagerduty_escalation_policy.foo.id - alert_creation = "create_alerts_and_incidents" - alert_grouping_parameters { - type = "intelligent" - } -} -`, username, email, escalationPolicy, service) -} - -func testAccCheckPagerDutyServiceConfigWithAlertIntelligentGroupingTypeNullEmptyConfigConfig(username, email, escalationPolicy, service string) string { - return fmt.Sprintf(` -resource "pagerduty_user" "foo" { - name = "%s" - email = "%s" - color = "green" - role = "user" - job_title = "foo" - description = "foo" -} - -resource "pagerduty_escalation_policy" "foo" { - name = "%s" - description = "bar" - num_loops = 2 - rule { - escalation_delay_in_minutes = 10 - target { - type = "user_reference" - id = pagerduty_user.foo.id - } - } -} - -resource "pagerduty_service" "foo" { - name = "%s" - description = "bar" - auto_resolve_timeout = 1800 - acknowledgement_timeout = 1800 - escalation_policy = pagerduty_escalation_policy.foo.id - alert_creation = "create_alerts_and_incidents" - alert_grouping_parameters { - type = null - config {} - } -} -`, username, email, escalationPolicy, service) -} - -func testAccCheckPagerDutyServiceConfigWithAutoPauseNotificationsParameters(username, email, escalationPolicy, service string) string { - return fmt.Sprintf(` -resource "pagerduty_user" "foo" { - name = "%s" - email = "%s" - color = "green" - role = "user" - job_title = "foo" - description = "foo" -} - -resource "pagerduty_escalation_policy" "foo" { - name = "%s" - description = "bar" - num_loops = 2 - rule { - escalation_delay_in_minutes = 10 - target { - type = "user_reference" - id = pagerduty_user.foo.id - } - } -} - -resource "pagerduty_service" "foo" { - name = "%s" - description = "foo" - auto_resolve_timeout = 1800 - acknowledgement_timeout = 1800 - escalation_policy = pagerduty_escalation_policy.foo.id - alert_creation = "create_alerts_and_incidents" - auto_pause_notifications_parameters { - enabled = true - timeout = 300 - } -} -`, username, email, escalationPolicy, service) -} - -func testAccCheckPagerDutyServiceConfigWithAutoPauseNotificationsParametersUpdated(username, email, escalationPolicy, service string) string { - return fmt.Sprintf(` -resource "pagerduty_user" "foo" { - name = "%s" - email = "%s" - color = "green" - role = "user" - job_title = "foo" - description = "foo" -} - -resource "pagerduty_escalation_policy" "foo" { - name = "%s" - description = "bar" - num_loops = 2 - rule { - escalation_delay_in_minutes = 10 - target { - type = "user_reference" - id = pagerduty_user.foo.id - } - } -} - -resource "pagerduty_service" "foo" { - name = "%s" - description = "foo" - auto_resolve_timeout = 1800 - acknowledgement_timeout = 1800 - escalation_policy = pagerduty_escalation_policy.foo.id - alert_creation = "create_alerts_and_incidents" - auto_pause_notifications_parameters { - enabled = false - timeout = null - } -} -`, username, email, escalationPolicy, service) -} - -func testAccCheckPagerDutyServiceConfigWithAutoPauseNotificationsParametersRemoved(username, email, escalationPolicy, service string) string { - return fmt.Sprintf(` -resource "pagerduty_user" "foo" { - name = "%s" - email = "%s" - color = "green" - role = "user" - job_title = "foo" - description = "foo" -} - -resource "pagerduty_escalation_policy" "foo" { - name = "%s" - description = "bar" - num_loops = 2 - rule { - escalation_delay_in_minutes = 10 - target { - type = "user_reference" - id = pagerduty_user.foo.id - } - } -} - -resource "pagerduty_service" "foo" { - name = "%s" - description = "foo" - auto_resolve_timeout = 1800 - acknowledgement_timeout = 1800 - escalation_policy = pagerduty_escalation_policy.foo.id - alert_creation = "create_alerts_and_incidents" -} -`, username, email, escalationPolicy, service) -} - -func testAccCheckPagerDutyServiceConfigUpdated(username, email, escalationPolicy, service string) string { - return fmt.Sprintf(` -resource "pagerduty_user" "foo" { - name = "%s" - email = "%s" - color = "green" - role = "user" - job_title = "foo" - description = "foo" -} - -resource "pagerduty_escalation_policy" "foo" { - name = "%s" - description = "bar" - num_loops = 2 - - rule { - escalation_delay_in_minutes = 10 - target { - type = "user_reference" - id = pagerduty_user.foo.id - } - } -} - -resource "pagerduty_service" "foo" { - name = "%s" - description = "bar" - auto_resolve_timeout = 3600 - acknowledgement_timeout = 3600 - - escalation_policy = pagerduty_escalation_policy.foo.id - incident_urgency_rule { - type = "constant" - urgency = "high" - } -} -`, username, email, escalationPolicy, service) -} - -func testAccCheckPagerDutyServiceConfigUpdatedWithDisabledTimeouts(username, email, escalationPolicy, service string) string { - return fmt.Sprintf(` -resource "pagerduty_user" "foo" { - name = "%s" - email = "%s" - color = "green" - role = "user" - job_title = "foo" - description = "foo" -} - -resource "pagerduty_escalation_policy" "foo" { - name = "%s" - description = "bar" - num_loops = 2 - - rule { - escalation_delay_in_minutes = 10 - target { - type = "user_reference" - id = pagerduty_user.foo.id - } - } -} - -resource "pagerduty_service" "foo" { - name = "%s" - description = "bar" - auto_resolve_timeout = "null" - acknowledgement_timeout = "null" - - escalation_policy = pagerduty_escalation_policy.foo.id - incident_urgency_rule { - type = "constant" - urgency = "high" - } -} -`, username, email, escalationPolicy, service) -} - func testAccCheckPagerDutyServiceWithIncidentUrgencyRulesConfig(username, email, escalationPolicy, service string) string { return fmt.Sprintf(` resource "pagerduty_user" "foo" { diff --git a/pagerdutyplugin/provider.go b/pagerdutyplugin/provider.go index 14163ae85..d082e9f7e 100644 --- a/pagerdutyplugin/provider.go +++ b/pagerdutyplugin/provider.go @@ -60,6 +60,7 @@ func (p *Provider) Resources(ctx context.Context) [](func() resource.Resource) { return [](func() resource.Resource){ func() resource.Resource { return &resourceBusinessService{} }, func() resource.Resource { return &resourceServiceDependency{} }, + func() resource.Resource { return &resourceService{} }, } } diff --git a/pagerdutyplugin/resource_pagerduty_service.go b/pagerdutyplugin/resource_pagerduty_service.go new file mode 100644 index 000000000..869d50959 --- /dev/null +++ b/pagerdutyplugin/resource_pagerduty_service.go @@ -0,0 +1,941 @@ +package pagerduty + +import ( + "context" + "fmt" + "log" + "strconv" + "time" + + "github.com/PagerDuty/go-pagerduty" + "github.com/PagerDuty/terraform-provider-pagerduty/util" + "github.com/PagerDuty/terraform-provider-pagerduty/util/enumtypes" + "github.com/PagerDuty/terraform-provider-pagerduty/util/rangetypes" + "github.com/PagerDuty/terraform-provider-pagerduty/util/tztypes" + "github.com/PagerDuty/terraform-provider-pagerduty/util/validate" + "github.com/hashicorp/terraform-plugin-framework-validators/listvalidator" + "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/listplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "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-sdk/v2/helper/retry" +) + +type resourceService struct { + client *pagerduty.Client +} + +var ( + _ resource.ResourceWithConfigure = (*resourceService)(nil) + _ resource.ResourceWithConfigValidators = (*resourceService)(nil) + _ resource.ResourceWithImportState = (*resourceService)(nil) +) + +func (r *resourceService) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { + resp.Diagnostics.Append(ConfigurePagerdutyClient(&r.client, req.ProviderData)...) +} + +func (r *resourceService) ConfigValidators(ctx context.Context) []resource.ConfigValidator { + return []resource.ConfigValidator{ + validate.Require( + path.Root("incident_urgency_rule").AtListIndex(0).AtName("type"), + ), + validate.RequireAIfBEqual( + path.Root("support_hours"), + path.Root("incident_urgency_rule").AtListIndex(0).AtName("type"), + types.StringValue("use_support_hours"), + ), + validate.RequireList(path.Root("alert_grouping_parameters").AtListIndex(0).AtName("config")), + validate.RequireList(path.Root("incident_urgency_rule").AtListIndex(0).AtName("during_support_hours")), + validate.RequireList(path.Root("incident_urgency_rule").AtListIndex(0).AtName("outside_support_hours")), + validate.RequireList(path.Root("support_hours").AtListIndex(0).AtName("days_of_week")), // TODO at most 7 + } +} + +func (r *resourceService) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = "pagerduty_service" +} + +func (r *resourceService) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "name": schema.StringAttribute{ + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + Validators: []validator.String{ + validate.IsAllowedString(util.NoNonPrintableChars), + }, + }, + + "acknowledgement_timeout": schema.StringAttribute{ + Computed: true, + Optional: true, + Default: stringdefault.StaticString("1800"), + }, + + "alert_creation": schema.StringAttribute{ + Optional: true, + Computed: true, + Default: stringdefault.StaticString("create_alerts_and_incidents"), + Validators: []validator.String{ + stringvalidator.OneOf("create_alerts_and_incidents", "create_incidents"), + }, + }, + + "alert_grouping": schema.StringAttribute{ + Computed: true, + Optional: true, + Validators: []validator.String{ + stringvalidator.OneOf("time", "intelligent", "rules"), + stringvalidator.ConflictsWith(path.MatchRoot("alert_grouping_parameters")), + }, + DeprecationMessage: "Use `alert_grouping_parameters.type`", + }, + + "alert_grouping_timeout": schema.StringAttribute{ + Computed: true, + Optional: true, + DeprecationMessage: "Use `alert_grouping_parameters.config.timeout`", + Validators: []validator.String{ + stringvalidator.ConflictsWith(path.MatchRoot("alert_grouping_parameters")), + }, + }, + + "auto_resolve_timeout": schema.StringAttribute{ + Computed: true, + Optional: true, + Default: stringdefault.StaticString("14400"), + }, + + "description": schema.StringAttribute{ + Optional: true, + Computed: true, + Default: stringdefault.StaticString("Managed by Terraform"), + }, + + "id": schema.StringAttribute{Computed: true}, + "created_at": schema.StringAttribute{Computed: true}, + "escalation_policy": schema.StringAttribute{Required: true}, + "html_url": schema.StringAttribute{Computed: true}, + "last_incident_timestamp": schema.StringAttribute{Computed: true}, + "response_play": schema.StringAttribute{Computed: true, Optional: true}, + "status": schema.StringAttribute{Computed: true}, + "type": schema.StringAttribute{Computed: true}, + + "alert_grouping_parameters": schema.ListAttribute{ + Optional: true, + Computed: true, + Validators: []validator.List{ + listvalidator.SizeBetween(1, 1), + listvalidator.ConflictsWith(path.MatchRoot("alert_grouping")), + listvalidator.ConflictsWith(path.MatchRoot("alert_grouping_timeout")), + }, + ElementType: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "type": alertGroupingParametersTypeType, + "config": types.ListType{ + ElemType: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "timeout": types.Int64Type, + "fields": types.ListType{ElemType: types.StringType}, + "aggregate": alertGroupingParametersConfigAggregateType, + "time_window": alertGroupingParametersConfigTimeWindowType, + }, + }, + }, + }, + }, + }, + + "auto_pause_notifications_parameters": schema.ListAttribute{ + Optional: true, + Computed: true, + Validators: []validator.List{ + listvalidator.SizeAtLeast(1), + listvalidator.SizeAtMost(1), + }, + ElementType: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "enabled": types.BoolType, + "timeout": autoPauseNotificationsParametersTimeoutType, + }, + }, + }, + + "incident_urgency_rule": schema.ListAttribute{ + Optional: true, + Computed: true, + Validators: []validator.List{ + listvalidator.SizeAtLeast(1), + listvalidator.SizeAtMost(1), + }, + ElementType: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "type": types.StringType, + "urgency": types.StringType, + "during_support_hours": types.ListType{ + ElemType: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "type": types.StringType, // require + "urgency": types.StringType, + }, + }, + }, + "outside_support_hours": types.ListType{ + ElemType: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "type": types.StringType, // require + "urgency": types.StringType, + }, + }, + }, + }, + }, + }, + + "scheduled_actions": schema.ListAttribute{ + Optional: true, + Computed: true, + ElementType: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "type": types.StringType, + "to_urgency": types.StringType, + "at": types.ListType{ + ElemType: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "type": types.StringType, + "name": types.StringType, + }, + }, + }, + }, + }, + PlanModifiers: []planmodifier.List{ + listplanmodifier.RequiresReplace(), + }, + Validators: []validator.List{ + listvalidator.SizeBetween(1, 1), + }, + }, + + "support_hours": schema.ListAttribute{ + Optional: true, + Computed: true, + Validators: []validator.List{ + listvalidator.SizeBetween(1, 1), + }, + PlanModifiers: []planmodifier.List{ + listplanmodifier.RequiresReplace(), + }, + ElementType: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "type": types.StringType, + "start_time": types.StringType, + "end_time": types.StringType, + "time_zone": tztypes.StringType{}, + "days_of_week": types.ListType{ + ElemType: types.StringType, + }, + }, + }, + }, + }, + } +} + +func (r *resourceService) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { + resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp) +} + +func (r *resourceService) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var model resourceServiceModel + if d := req.Plan.Get(ctx, &model); d.HasError() { + resp.Diagnostics.Append(d...) + return + } + + serviceBody := buildService(ctx, &model, &resp.Diagnostics) + if resp.Diagnostics.HasError() { + return + } + log.Printf("[INFO] Creating PagerDuty service %s", serviceBody.Name) + + service, err := r.client.CreateServiceWithContext(ctx, serviceBody) + if err != nil { + resp.Diagnostics.AddError("Error calling CreateServiceWithContext", err.Error()) + return + } + + err = retry.RetryContext(ctx, 2*time.Minute, func() *retry.RetryError { + serviceResponse, err := r.client.GetServiceWithContext(ctx, service.ID, &pagerduty.GetServiceOptions{ + Includes: []string{"auto_pause_notifications_parameters"}, + }) + if err != nil { + if util.IsBadRequestError(err) { + return retry.NonRetryableError(err) + } + return retry.RetryableError(err) + } + model = flattenService(ctx, serviceResponse, &resp.Diagnostics) + if resp.Diagnostics.HasError() { + return retry.NonRetryableError(fmt.Errorf("%#v", resp.Diagnostics)) + } + return nil + }) + if err != nil { + resp.Diagnostics.AddError("Error calling GetServiceWithContext", err.Error()) + return + } + + resp.Diagnostics.Append(resp.State.Set(ctx, &model)...) +} + +func (r *resourceService) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + var id types.String + if d := req.State.GetAttribute(ctx, path.Root("id"), &id); d.HasError() { + resp.Diagnostics.Append(d...) + } + log.Printf("[INFO] Reading PagerDuty service %s", id) + + if id.IsNull() { + resp.State.RemoveResource(ctx) + return + } + + var model resourceServiceModel + err := retry.RetryContext(ctx, 2*time.Minute, func() *retry.RetryError { + serviceResponse, err := r.client.GetServiceWithContext(ctx, id.ValueString(), &pagerduty.GetServiceOptions{ + Includes: []string{"auto_pause_notifications_parameters"}, + }) + if err != nil { + if util.IsBadRequestError(err) { + return retry.NonRetryableError(err) + } + if util.IsNotFoundError(err) { + resp.State.RemoveResource(ctx) + return nil + } + return retry.RetryableError(err) + } + model = flattenService(ctx, serviceResponse, &resp.Diagnostics) + return nil + }) + if err != nil { + resp.Diagnostics.AddError("Error calling GetServiceWithContext", err.Error()) + return + } + resp.State.Set(ctx, &model) +} + +func (r *resourceService) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { +} + +func (r *resourceService) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + var id types.String + if d := req.State.GetAttribute(ctx, path.Root("id"), &id); d.HasError() { + resp.Diagnostics.Append(d...) + } + log.Printf("[INFO] Deleting PagerDuty service %s", id) + + if id.IsNull() { + resp.State.RemoveResource(ctx) + return + } + + if err := r.client.DeleteServiceWithContext(ctx, id.ValueString()); err != nil { + resp.Diagnostics.AddError("Error calling DeleteServiceWithContext", err.Error()) + return + } + + resp.State.RemoveResource(ctx) +} + +type resourceServiceModel struct { + ID types.String `tfsdk:"id"` + AcknowledgementTimeout types.String `tfsdk:"acknowledgement_timeout"` + AlertCreation types.String `tfsdk:"alert_creation"` + AlertGrouping types.String `tfsdk:"alert_grouping"` + AlertGroupingTimeout types.String `tfsdk:"alert_grouping_timeout"` + AutoResolveTimeout types.String `tfsdk:"auto_resolve_timeout"` + CreatedAt types.String `tfsdk:"created_at"` + Description types.String `tfsdk:"description"` + EscalationPolicy types.String `tfsdk:"escalation_policy"` + HtmlUrl types.String `tfsdk:"html_url"` + LastIncidentTimestamp types.String `tfsdk:"last_incident_timestamp"` + Name types.String `tfsdk:"name"` + ResponsePlay types.String `tfsdk:"response_play"` + Status types.String `tfsdk:"status"` + Type types.String `tfsdk:"type"` + AlertGroupingParameters types.List `tfsdk:"alert_grouping_parameters"` + AutoPauseNotificationsParameters types.List `tfsdk:"auto_pause_notifications_parameters"` + IncidentUrgencyRule types.List `tfsdk:"incident_urgency_rule"` + ScheduledActions types.List `tfsdk:"scheduled_actions"` + SupportHours types.List `tfsdk:"support_hours"` +} + +func buildService(ctx context.Context, model *resourceServiceModel, diags *diag.Diagnostics) pagerduty.Service { + service := pagerduty.Service{ + Name: model.Name.ValueString(), + Description: model.Description.ValueString(), + AlertCreation: model.AlertCreation.ValueString(), + AlertGrouping: model.AlertGrouping.ValueString(), + } + + u := util.StringToUintPointer(path.Root("auto_resolve_timeout"), model.AutoResolveTimeout, diags) + service.AutoResolveTimeout = u + + u = util.StringToUintPointer(path.Root("acknowledgement_timeout"), model.AcknowledgementTimeout, diags) + service.AcknowledgementTimeout = u + + u = util.StringToUintPointer(path.Root("alert_grouping_timeout"), model.AlertGroupingTimeout, diags) + service.AlertGroupingTimeout = u + + service.EscalationPolicy.ID = model.EscalationPolicy.ValueString() + service.EscalationPolicy.Type = "escalation_policy_reference" + + service.AlertGroupingParameters = buildAlertGroupingParameters(ctx, model.AlertGroupingParameters, diags) + service.AutoPauseNotificationsParameters = buildAutoPauseNotificationsParameters(ctx, model.AutoPauseNotificationsParameters, diags) + service.IncidentUrgencyRule = buildIncidentUrgencyRule(ctx, model.IncidentUrgencyRule, diags) + service.ScheduledActions = buildScheduledActions(ctx, model.ScheduledActions, diags) + service.SupportHours = buildSupportHours(ctx, model.SupportHours, diags) + + if !model.ResponsePlay.IsNull() && !model.ResponsePlay.IsUnknown() { + service.ResponsePlay = &pagerduty.APIObject{ + ID: model.ResponsePlay.ValueString(), + Type: "response_play_reference", + } + } + + return service +} + +func buildAlertGroupingParameters(ctx context.Context, list types.List, diags *diag.Diagnostics) *pagerduty.AlertGroupingParameters { + if list.IsNull() || list.IsUnknown() { + return nil + } + var target []struct { + Type types.String `tfsdk:"type"` + Config types.List `tfsdk:"config"` + } + if d := list.ElementsAs(ctx, &target, false); d.HasError() { + diags.Append(d...) + return nil + } + obj := target[0] + return &pagerduty.AlertGroupingParameters{ + Type: obj.Type.ValueString(), + Config: buildAlertGroupingConfig(ctx, obj.Config, diags), + } +} + +func buildAlertGroupingConfig(ctx context.Context, list types.List, diags *diag.Diagnostics) *pagerduty.AlertGroupParamsConfig { + var target []struct { + Timeout types.Int64 `tfsdk:"timeout"` + Aggregate types.String `tfsdk:"aggregate"` + Fields types.List `tfsdk:"fields"` + TimeWindow types.Int64 `tfsdk:"time_window"` + } + if d := list.ElementsAs(ctx, &target, false); d.HasError() { + diags.Append(d...) + return nil + } + obj := target[0] + + ut := uint(obj.Timeout.ValueInt64()) + + var fields []string + if d := obj.Fields.ElementsAs(ctx, &fields, false); d.HasError() { + diags.Append(d...) + return nil + } + + return &pagerduty.AlertGroupParamsConfig{ + Timeout: &ut, + Aggregate: obj.Aggregate.ValueString(), + Fields: fields, + } +} + +func buildAutoPauseNotificationsParameters(ctx context.Context, list types.List, diags *diag.Diagnostics) *pagerduty.AutoPauseNotificationsParameters { + if list.IsNull() || list.IsUnknown() { + return nil + } + var target []struct { + Timeout types.Int64 `tfsdk:"timeout"` + Enabled types.Bool `tfsdk:"enabled"` + } + if d := list.ElementsAs(ctx, &target, false); d.HasError() { + diags.Append(d...) + return nil + } + obj := target[0] + + return &pagerduty.AutoPauseNotificationsParameters{ + Enabled: obj.Enabled.ValueBool(), + Timeout: uint(obj.Timeout.ValueInt64()), + } +} + +func buildIncidentUrgencyRule(ctx context.Context, list types.List, diags *diag.Diagnostics) *pagerduty.IncidentUrgencyRule { + if list.IsNull() || list.IsUnknown() { + return nil + } + var target []struct { + Type types.String `tfsdk:"type"` + Urgency types.String `tfsdk:"urgency"` + DuringSupportHours types.List `tfsdk:"during_support_hours"` + OutsideSupportHours types.List `tfsdk:"outside_support_hours"` + } + if d := list.ElementsAs(ctx, &target, false); d.HasError() { + diags.Append(d...) + return nil + } + obj := target[0] + incidentUrgencyRule := &pagerduty.IncidentUrgencyRule{ + Type: obj.Type.ValueString(), + Urgency: obj.Urgency.ValueString(), + } + incidentUrgencyRule.DuringSupportHours = buildIncidentUrgencyType(ctx, obj.DuringSupportHours, diags) + incidentUrgencyRule.OutsideSupportHours = buildIncidentUrgencyType(ctx, obj.OutsideSupportHours, diags) + return incidentUrgencyRule +} + +func buildIncidentUrgencyType(ctx context.Context, list types.List, diags *diag.Diagnostics) *pagerduty.IncidentUrgencyType { + var target []struct { + Type types.String `tfsdk:"type"` + Urgency types.String `tfsdk:"urgency"` + } + if d := list.ElementsAs(ctx, &target, false); d.HasError() { + diags.Append(d...) + } + if len(target) < 1 { + return nil + } + obj := target[0] + return &pagerduty.IncidentUrgencyType{ + Type: obj.Type.ValueString(), + Urgency: obj.Urgency.ValueString(), + } +} + +func buildScheduledActions(ctx context.Context, list types.List, diags *diag.Diagnostics) []pagerduty.ScheduledAction { + if list.IsNull() || list.IsUnknown() { + return nil + } + var target []struct { + Type types.String `tfsdk:"type"` + ToUrgency types.String `tfsdk:"to_urgency"` + At types.List `tfsdk:"at"` + } + if d := list.ElementsAs(ctx, &target, false); d.HasError() { + diags.Append(d...) + } + scheduledActions := []pagerduty.ScheduledAction{} + for _, src := range target { + dst := pagerduty.ScheduledAction{ + Type: src.Type.ValueString(), + ToUrgency: src.ToUrgency.ValueString(), + At: buildScheduledActionAt(ctx, src.At, diags), + } + scheduledActions = append(scheduledActions, dst) + } + return scheduledActions +} + +func buildScheduledActionAt(ctx context.Context, list types.List, diags *diag.Diagnostics) pagerduty.InlineModel { + var target []struct { + Type types.String `tfsdk:"type"` + Name types.String `tfsdk:"name"` + } + if d := list.ElementsAs(ctx, &target, false); d.HasError() { + diags.Append(d...) + return pagerduty.InlineModel{} + } + obj := target[0] + return pagerduty.InlineModel{ + Type: obj.Type.ValueString(), + Name: obj.Name.ValueString(), + } +} + +func buildSupportHours(ctx context.Context, list types.List, diags *diag.Diagnostics) *pagerduty.SupportHours { + if list.IsNull() || list.IsUnknown() { + return nil + } + var target []struct { + Type types.String `tfsdk:"type"` + Timezone types.String `tfsdk:"time_zone"` + StartTime types.String `tfsdk:"start_time"` + EndTime types.String `tfsdk:"end_time"` + DaysOfWeek types.List `tfsdk:"days_of_week"` + } + if d := list.ElementsAs(ctx, &target, false); d.HasError() { + diags.Append(d...) + return nil + } + obj := target[0] + supportHours := &pagerduty.SupportHours{ + Type: obj.Type.ValueString(), + Timezone: obj.Timezone.ValueString(), + StartTime: obj.StartTime.ValueString(), + EndTime: obj.EndTime.ValueString(), + } + + if !obj.DaysOfWeek.IsNull() { + daysOfWeekStr := []string{} + if d := obj.DaysOfWeek.ElementsAs(ctx, &daysOfWeekStr, false); d.HasError() { + diags.Append(d...) + return nil + } + daysOfWeek := make([]uint, 0, len(daysOfWeekStr)) + for _, s := range daysOfWeekStr { + v, err := strconv.Atoi(s) + if err != nil { + continue + } + daysOfWeek = append(daysOfWeek, uint(v)) + } + supportHours.DaysOfWeek = daysOfWeek + } + return supportHours +} + +var ( + alertGroupingParametersTypeType = enumtypes.StringType{ + OneOf: []string{"time", "intelligent", "rules"}} + alertGroupingParametersConfigAggregateType = enumtypes.StringType{ + OneOf: []string{"all", "any"}} + alertGroupingParametersConfigTimeWindowType = rangetypes.Int64Type{ + Start: 300, End: 3600} + autoPauseNotificationsParametersTimeoutType = enumtypes.Int64Type{ + OneOf: []int64{120, 180, 300, 600, 900}} +) + +func flattenService(ctx context.Context, service *pagerduty.Service, diags *diag.Diagnostics) resourceServiceModel { + model := resourceServiceModel{ + ID: types.StringValue(service.ID), + AlertCreation: types.StringValue(service.AlertCreation), + CreatedAt: types.StringValue(service.CreateAt), + Description: types.StringValue(service.Description), + EscalationPolicy: types.StringValue(service.EscalationPolicy.ID), + HtmlUrl: types.StringValue(service.HTMLURL), + LastIncidentTimestamp: types.StringValue(service.LastIncidentTimestamp), + Name: types.StringValue(service.Name), + Status: types.StringValue(service.Status), + Type: types.StringValue(service.Type), + } + + if service.AcknowledgementTimeout != nil { + s := strconv.Itoa(int(*service.AcknowledgementTimeout)) + model.AcknowledgementTimeout = types.StringValue(s) + } + + if service.AutoResolveTimeout != nil { + s := strconv.Itoa(int(*service.AutoResolveTimeout)) + model.AutoResolveTimeout = types.StringValue(s) + } + + if service.AlertGrouping != "" { + model.AlertGrouping = types.StringValue(service.AlertGrouping) + } + + if service.AlertGroupingTimeout != nil { + s := strconv.Itoa(int(*service.AlertGroupingTimeout)) + model.AlertGroupingTimeout = types.StringValue(s) + } + + model.AlertGroupingParameters = flattenAlertGroupingParameters(ctx, service.AlertGroupingParameters, diags) + model.AutoPauseNotificationsParameters = flattenAutoPauseNotificationsParameters(service.AutoPauseNotificationsParameters, diags) + model.IncidentUrgencyRule = flattenIncidentUrgencyRule(service.IncidentUrgencyRule, diags) + + if service.ResponsePlay != nil { + model.ResponsePlay = types.StringValue(service.ResponsePlay.ID) + } + + model.ScheduledActions = flattenScheduledActions(service.ScheduledActions, diags) + model.SupportHours = flattenSupportHours(service.SupportHours, diags) + + return model +} + +func flattenAlertGroupingParameters(ctx context.Context, params *pagerduty.AlertGroupingParameters, diags *diag.Diagnostics) types.List { + alertGroupParamsConfigObjectType := types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "aggregate": alertGroupingParametersConfigAggregateType, + "fields": types.ListType{ElemType: types.StringType}, + "timeout": types.Int64Type, + "time_window": alertGroupingParametersConfigTimeWindowType, + }, + } + alertGroupingParametersObjectType := types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "type": alertGroupingParametersTypeType, + "config": types.ListType{ElemType: alertGroupParamsConfigObjectType}, + }, + } + + nullList := types.ListNull(alertGroupingParametersObjectType) + if params == nil { + return nullList + } + + configList := types.ListNull(alertGroupParamsConfigObjectType) + if params.Config != nil { + fieldsList, d := types.ListValueFrom(ctx, types.StringType, params.Config.Fields) + if d.HasError() { + diags.Append(d...) + return nullList + } + + var timeout types.Int64 + if params.Config.Timeout != nil { + timeout = types.Int64Value(int64(*params.Config.Timeout)) + } + + aggregate := enumtypes.NewStringNull(alertGroupingParametersConfigAggregateType) + if params.Config.Aggregate != "" { + aggregate = enumtypes.NewStringValue(params.Config.Aggregate, alertGroupingParametersConfigAggregateType) + } + + configObj, d := types.ObjectValue(alertGroupParamsConfigObjectType.AttrTypes, map[string]attr.Value{ + "aggregate": aggregate, + "fields": fieldsList, + "timeout": timeout, + "time_window": types.Int64Null(), // TODO + }) + if d.HasError() { + diags.Append(d...) + return nullList + } + configList, d = types.ListValue(alertGroupParamsConfigObjectType, []attr.Value{configObj}) + if d.HasError() { + diags.Append(d...) + return nullList + } + } + + obj, d := types.ObjectValue(alertGroupingParametersObjectType.AttrTypes, map[string]attr.Value{ + "type": enumtypes.NewStringValue(params.Type, alertGroupingParametersTypeType), + "config": configList, + }) + diags.Append(d...) + if d.HasError() { + return nullList + } + + list, d := types.ListValue(alertGroupingParametersObjectType, []attr.Value{obj}) + diags.Append(d...) + if d.HasError() { + return nullList + } + + return list +} + +func flattenAutoPauseNotificationsParameters(params *pagerduty.AutoPauseNotificationsParameters, diags *diag.Diagnostics) types.List { + autoPauseNotificationsParametersObjectType := types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "enabled": types.BoolType, + "timeout": autoPauseNotificationsParametersTimeoutType, + }, + } + + nullList := types.ListNull(autoPauseNotificationsParametersObjectType) + if params == nil { + return nullList + } + + timeout := enumtypes.NewInt64Null(autoPauseNotificationsParametersTimeoutType) + if params.Enabled { + timeout = enumtypes.NewInt64Value( + int64(params.Timeout), + autoPauseNotificationsParametersTimeoutType, + ) + } + + obj, d := types.ObjectValue(autoPauseNotificationsParametersObjectType.AttrTypes, map[string]attr.Value{ + "enabled": types.BoolValue(params.Enabled), + "timeout": timeout, + }) + if d.HasError() { + diags.Append(d...) + return nullList + } + + list, d := types.ListValue(autoPauseNotificationsParametersObjectType, []attr.Value{obj}) + if d.HasError() { + diags.Append(d...) + return nullList + } + + return list +} + +var incidentUrgencyTypeObjectType = types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "type": types.StringType, + "urgency": types.StringType, + }, +} + +func flattenIncidentUrgencyRule(rule *pagerduty.IncidentUrgencyRule, diags *diag.Diagnostics) types.List { + incidentUrgencyRuleObjectType := types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "type": types.StringType, + "urgency": types.StringType, + "during_support_hours": types.ListType{ElemType: incidentUrgencyTypeObjectType}, + "outside_support_hours": types.ListType{ElemType: incidentUrgencyTypeObjectType}, + }, + } + nullList := types.ListNull(incidentUrgencyTypeObjectType) + if rule == nil { + return nullList + } + + objValues := map[string]attr.Value{ + "type": types.StringValue(rule.Type), + "urgency": types.StringNull(), + "during_support_hours": types.ListNull(incidentUrgencyTypeObjectType), + "outside_support_hours": types.ListNull(incidentUrgencyTypeObjectType), + } + if rule.Urgency != "" { + objValues["urgency"] = types.StringValue(rule.Urgency) + } + if rule.DuringSupportHours != nil { + objValues["during_support_hours"] = flattenIncidentUrgencyType(rule.DuringSupportHours, diags) + } + if rule.OutsideSupportHours != nil { + objValues["outside_support_hours"] = flattenIncidentUrgencyType(rule.OutsideSupportHours, diags) + } + if diags.HasError() { + return nullList + } + + obj, d := types.ObjectValue(incidentUrgencyRuleObjectType.AttrTypes, objValues) + if d.HasError() { + diags.Append(d...) + return nullList + } + + list, d := types.ListValue(incidentUrgencyRuleObjectType, []attr.Value{obj}) + diags.Append(d...) + return list +} + +func flattenIncidentUrgencyType(urgency *pagerduty.IncidentUrgencyType, diags *diag.Diagnostics) types.List { + obj, d := types.ObjectValue(incidentUrgencyTypeObjectType.AttrTypes, map[string]attr.Value{ + "type": types.StringValue(urgency.Type), + "urgency": types.StringValue(urgency.Urgency), + }) + diags.Append(d...) + if d.HasError() { + return types.List{} + } + list, d := types.ListValue(incidentUrgencyTypeObjectType, []attr.Value{obj}) + diags.Append(d...) + return list +} + +var scheduledActionAtObjectType = types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "type": types.StringType, + "name": types.StringType, + }, +} + +func flattenScheduledActions(actions []pagerduty.ScheduledAction, diags *diag.Diagnostics) types.List { + scheduledActionObjectType := types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "type": types.StringType, + "to_urgency": types.StringType, + "at": types.ListType{ElemType: scheduledActionAtObjectType}, + }, + } + nullList := types.ListNull(scheduledActionObjectType) + if len(actions) == 0 { + return nullList + } + + elements := []attr.Value{} + for _, action := range actions { + obj, d := types.ObjectValue(scheduledActionObjectType.AttrTypes, map[string]attr.Value{ + "type": types.StringValue(action.Type), + "to_urgency": types.StringValue(action.ToUrgency), + "at": flattenScheduledActionAt(action.At, diags), + }) + diags.Append(d...) + if diags.HasError() { + return nullList + } + elements = append(elements, obj) + } + + list, d := types.ListValue(scheduledActionObjectType, elements) + diags.Append(d...) + return list +} + +func flattenScheduledActionAt(at pagerduty.InlineModel, diags *diag.Diagnostics) types.List { + obj, d := types.ObjectValue(scheduledActionAtObjectType.AttrTypes, map[string]attr.Value{ + "type": types.StringValue(at.Type), + "name": types.StringValue(at.Name), + }) + if d.HasError() { + diags.Append(d...) + return types.List{} + } + list, d := types.ListValue(scheduledActionAtObjectType, []attr.Value{obj}) + diags.Append(d...) + return list +} + +func flattenSupportHours(hours *pagerduty.SupportHours, diags *diag.Diagnostics) types.List { + supportHoursObjectType := types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "type": types.StringType, + "start_time": types.StringType, + "end_time": types.StringType, + "time_zone": tztypes.StringType{}, + "days_of_week": types.ListType{ElemType: types.StringType}, + }, + } + nullList := types.ListNull(supportHoursObjectType) + if hours == nil { + return nullList + } + + daysOfWeek := []attr.Value{} + for _, dow := range hours.DaysOfWeek { + v := strconv.FormatInt(int64(dow), 10) + daysOfWeek = append(daysOfWeek, types.StringValue(v)) + } + + dowList, d := types.ListValue(types.StringType, daysOfWeek) + diags.Append(d...) + + obj, d := types.ObjectValue(supportHoursObjectType.AttrTypes, map[string]attr.Value{ + "type": types.StringValue(hours.Type), + "start_time": types.StringValue(hours.StartTime), + "end_time": types.StringValue(hours.EndTime), + "time_zone": tztypes.NewStringValue(hours.Timezone), + "days_of_week": dowList, + }) + if d.HasError() { + diags.Append(d...) + return nullList + } + + list, d := types.ListValue(supportHoursObjectType, []attr.Value{obj}) + diags.Append(d...) + return list +} diff --git a/pagerdutyplugin/resource_pagerduty_service_test.go b/pagerdutyplugin/resource_pagerduty_service_test.go new file mode 100644 index 000000000..92a2e7ce5 --- /dev/null +++ b/pagerdutyplugin/resource_pagerduty_service_test.go @@ -0,0 +1,2418 @@ +package pagerduty + +import ( + "context" + "fmt" + "log" + "regexp" + "strings" + "testing" + + "github.com/PagerDuty/go-pagerduty" + "github.com/hashicorp/terraform-plugin-framework/diag" + "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_service", &resource.Sweeper{ + Name: "pagerduty_service", + F: testSweepService, + }) +} + +func testSweepService(region string) error { + ctx := context.Background() + resp, err := testAccProvider.client.ListServicesWithContext(ctx, pagerduty.ListServiceOptions{}) + if err != nil { + return err + } + + for _, service := range resp.Services { + if strings.HasPrefix(service.Name, "test") || strings.HasPrefix(service.Name, "tf-") { + log.Printf("Destroying service %s (%s)", service.Name, service.ID) + if err := testAccProvider.client.DeleteServiceWithContext(ctx, service.ID); err != nil { + return err + } + } + } + + return nil +} + +func TestAccPagerDutyService_Basic(t *testing.T) { + username := fmt.Sprintf("tf-%s", acctest.RandString(5)) + email := fmt.Sprintf("%s@foo.test", username) + escalationPolicy := fmt.Sprintf("tf-%s", acctest.RandString(5)) + service := fmt.Sprintf("tf-%s", acctest.RandString(5)) + serviceUpdated := fmt.Sprintf("tf-%s", acctest.RandString(5)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV5ProviderFactories: testAccProtoV5ProviderFactories(), + CheckDestroy: testAccCheckPagerDutyServiceDestroy, + Steps: []resource.TestStep{ + { + Config: testAccCheckPagerDutyServiceConfig(username, email, escalationPolicy, service), + Check: resource.ComposeTestCheckFunc( + testAccCheckPagerDutyServiceExists("pagerduty_service.foo"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "name", service), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "description", "foo"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "auto_resolve_timeout", "1800"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "acknowledgement_timeout", "1800"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "alert_creation", "create_alerts_and_incidents"), + resource.TestCheckNoResourceAttr( + "pagerduty_service.foo", "alert_grouping"), + resource.TestCheckNoResourceAttr( + "pagerduty_service.foo", "alert_grouping_timeout"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "incident_urgency_rule.#", "1"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "incident_urgency_rule.0.urgency", "high"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "incident_urgency_rule.0.type", "constant"), + resource.TestCheckResourceAttrSet( + "pagerduty_service.foo", "html_url"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "type", "service"), + ), + }, + { + Config: testAccCheckPagerDutyServiceConfigUpdated(username, email, escalationPolicy, serviceUpdated), + Check: resource.ComposeTestCheckFunc( + testAccCheckPagerDutyServiceExists("pagerduty_service.foo"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "name", serviceUpdated), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "description", "bar"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "auto_resolve_timeout", "3600"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "acknowledgement_timeout", "3600"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "alert_creation", "create_alerts_and_incidents"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "incident_urgency_rule.#", "1"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "incident_urgency_rule.0.urgency", "high"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "incident_urgency_rule.0.type", "constant"), + ), + }, + { + Config: testAccCheckPagerDutyServiceConfigUpdatedWithDisabledTimeouts(username, email, escalationPolicy, serviceUpdated), + Check: resource.ComposeTestCheckFunc( + testAccCheckPagerDutyServiceExists("pagerduty_service.foo"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "name", serviceUpdated), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "description", "bar"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "auto_resolve_timeout", "null"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "acknowledgement_timeout", "null"), + ), + }, + }, + }) +} + +func TestAccPagerDutyService_FormatValidation(t *testing.T) { + service := fmt.Sprintf("ts-%s", acctest.RandString(5)) + username := fmt.Sprintf("tf-%s", acctest.RandString(5)) + email := fmt.Sprintf("%s@foo.test", username) + escalationPolicy := fmt.Sprintf("tf-%s", acctest.RandString(5)) + errMessageMatcher := "Name can not be blank, nor contain non-printable characters. Trailing white spaces are not allowed either." + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV5ProviderFactories: testAccProtoV5ProviderFactories(), + CheckDestroy: testAccCheckPagerDutyServiceDestroy, + Steps: []resource.TestStep{ + // Just a valid name + { + Config: testAccCheckPagerDutyServiceConfig(username, email, escalationPolicy, "DB Technical Service"), + PlanOnly: true, + ExpectNonEmptyPlan: true, + }, + // Blank Name + { + Config: testAccCheckPagerDutyServiceConfig(username, email, escalationPolicy, ""), + PlanOnly: true, + ExpectError: regexp.MustCompile(errMessageMatcher), + }, + // Name with one white space at the end + { + Config: testAccCheckPagerDutyServiceConfig(username, email, escalationPolicy, "this name has a white space at the end "), + PlanOnly: true, + ExpectError: regexp.MustCompile(errMessageMatcher), + }, + // Name with multiple white space at the end + { + Config: testAccCheckPagerDutyServiceConfig(username, email, escalationPolicy, "this name has white spaces at the end "), + PlanOnly: true, + ExpectError: regexp.MustCompile(errMessageMatcher), + }, + // Name with non printable characters + { + Config: testAccCheckPagerDutyServiceConfig(username, email, escalationPolicy, "this name has a non printable\\n character"), + PlanOnly: true, + ExpectError: regexp.MustCompile(errMessageMatcher), + }, + // Alert grouping parameters "Content Based" type input validation + { + Config: testAccCheckPagerDutyServiceAlertGroupingInputValidationConfig(username, email, escalationPolicy, service, + ` + alert_grouping_parameters { + type = "content_based" + config {} + } + `, + ), + PlanOnly: true, + ExpectError: regexp.MustCompile("When using Alert grouping parameters configuration of type \"content_based\" is in use, attributes \"aggregate\" and \"fields\" are required"), + }, + { + Config: testAccCheckPagerDutyServiceAlertGroupingInputValidationConfig(username, email, escalationPolicy, service, + ` + alert_grouping_parameters { + type = "content_based" + config { + aggregate = "all" + fields = ["custom_details.source_id"] + } + } + `, + ), + }, + { + Config: testAccCheckPagerDutyServiceAlertGroupingInputValidationConfig(username, email, escalationPolicy, service, + ` + alert_grouping_parameters { + type = "time" + config { + aggregate = "all" + fields = ["custom_details.source_id"] + } + } + `, + ), + PlanOnly: true, + ExpectError: regexp.MustCompile("Alert grouping parameters configuration attributes \"aggregate\" and \"fields\" are only supported by \"content_based\" type Alert Grouping"), + }, + // Alert grouping parameters "time" type input validation + { + Config: testAccCheckPagerDutyServiceAlertGroupingInputValidationConfig(username, email, escalationPolicy, service, + ` + alert_grouping_parameters { + type = "time" + config { + timeout = 5 + } + } + `, + ), + }, + { + Config: testAccCheckPagerDutyServiceAlertGroupingInputValidationConfig(username, email, escalationPolicy, service, + ` + alert_grouping_parameters { + type = "intelligent" + config { + timeout = 5 + } + } + `, + ), + PlanOnly: true, + ExpectError: regexp.MustCompile("Alert grouping parameters configuration attribute \"timeout\" is only supported by \"time\" type Alert Grouping"), + }, + // Alert grouping parameters "intelligent" type input validation + { + Config: testAccCheckPagerDutyServiceAlertGroupingInputValidationConfig(username, email, escalationPolicy, service, + ` + alert_grouping_parameters { + type = "time" + config { + time_window = 600 + } + } + `, + ), + PlanOnly: true, + ExpectError: regexp.MustCompile("Alert grouping parameters configuration attribute \"time_window\" is only supported by \"intelligent\" and \"content-based\" type Alert Grouping"), + }, + { + Config: testAccCheckPagerDutyServiceAlertGroupingInputValidationConfig(username, email, escalationPolicy, service, + ` + alert_grouping_parameters { + type = "intelligent" + config {} + } + `, + ), + }, + { + Config: testAccCheckPagerDutyServiceAlertGroupingInputValidationConfig(username, email, escalationPolicy, service, + ` + alert_grouping_parameters { + type = "intelligent" + config { + time_window = 5 + } + } + `, + ), + PlanOnly: true, + ExpectError: regexp.MustCompile("Alert grouping time window value must be between 300 and 3600"), + }, + { + Config: testAccCheckPagerDutyServiceAlertGroupingInputValidationConfig(username, email, escalationPolicy, service, + ` + alert_grouping_parameters { + type = "intelligent" + config { + time_window = 300 + } + } + `, + ), + PlanOnly: true, + }, + { + Config: testAccCheckPagerDutyServiceAlertGroupingInputValidationConfig(username, email, escalationPolicy, service, + ` + alert_grouping_parameters { + type = "content_based" + config { + time_window = 5 + } + } + `, + ), + PlanOnly: true, + ExpectError: regexp.MustCompile("Alert grouping time window value must be between 300 and 3600"), + }, + { + Config: testAccCheckPagerDutyServiceAlertGroupingInputValidationConfig(username, email, escalationPolicy, service, + ` + alert_grouping_parameters { + type = "content_based" + config { + aggregate = "all" + fields = ["custom_details.source_id"] + time_window = 300 + } + } + `, + ), + }, + }, + }) +} + +func TestAccPagerDutyService_AlertGrouping(t *testing.T) { + username := fmt.Sprintf("tf-%s", acctest.RandString(5)) + email := fmt.Sprintf("%s@foo.test", username) + escalationPolicy := fmt.Sprintf("tf-%s", acctest.RandString(5)) + service := fmt.Sprintf("tf-%s", acctest.RandString(5)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t); testAccPreCheckPagerDutyAbility(t, "preview_intelligent_alert_grouping") }, + ProtoV5ProviderFactories: testAccProtoV5ProviderFactories(), + CheckDestroy: testAccCheckPagerDutyServiceDestroy, + Steps: []resource.TestStep{ + { + Config: testAccCheckPagerDutyServiceConfigWithAlertGrouping(username, email, escalationPolicy, service), + Check: resource.ComposeTestCheckFunc( + testAccCheckPagerDutyServiceExists("pagerduty_service.foo"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "name", service), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "description", "foo"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "auto_resolve_timeout", "1800"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "acknowledgement_timeout", "1800"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "alert_creation", "create_alerts_and_incidents"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "alert_grouping", "time"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "alert_grouping_timeout", "1800"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "incident_urgency_rule.#", "1"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "incident_urgency_rule.0.urgency", "high"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "incident_urgency_rule.0.type", "constant"), + ), + }, + { + Config: testAccCheckPagerDutyServiceConfigWithAlertGroupingUpdated(username, email, escalationPolicy, service), + Check: resource.ComposeTestCheckFunc( + testAccCheckPagerDutyServiceExists("pagerduty_service.foo"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "name", service), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "description", "foo"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "auto_resolve_timeout", "1800"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "acknowledgement_timeout", "1800"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "alert_creation", "create_alerts_and_incidents"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "alert_grouping", "intelligent"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "alert_grouping_timeout", "1900"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "incident_urgency_rule.#", "1"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "incident_urgency_rule.0.urgency", "high"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "incident_urgency_rule.0.type", "constant"), + ), + }, + }, + }) +} + +func TestAccPagerDutyService_AlertContentGrouping(t *testing.T) { + username := fmt.Sprintf("tf-%s", acctest.RandString(5)) + email := fmt.Sprintf("%s@foo.test", username) + escalationPolicy := fmt.Sprintf("tf-%s", acctest.RandString(5)) + service := fmt.Sprintf("tf-%s", acctest.RandString(5)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV5ProviderFactories: testAccProtoV5ProviderFactories(), + CheckDestroy: testAccCheckPagerDutyServiceDestroy, + Steps: []resource.TestStep{ + { + Config: testAccCheckPagerDutyServiceConfigWithAlertContentGrouping(username, email, escalationPolicy, service), + Check: resource.ComposeTestCheckFunc( + testAccCheckPagerDutyServiceExists("pagerduty_service.foo"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "name", service), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "description", "foo"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "auto_resolve_timeout", "1800"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "acknowledgement_timeout", "1800"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "alert_creation", "create_alerts_and_incidents"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "alert_grouping", "rules"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "alert_grouping_parameters.0.config.0.aggregate", "all"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "alert_grouping_parameters.0.type", "content_based"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "alert_grouping_parameters.0.config.0.fields.0", "custom_details.field1"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "incident_urgency_rule.#", "1"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "incident_urgency_rule.0.urgency", "high"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "incident_urgency_rule.0.type", "constant"), + ), + }, + { + Config: testAccCheckPagerDutyServiceConfigWithAlertContentGrouping(username, email, escalationPolicy, service), + PlanOnly: true, + }, + { + Config: testAccCheckPagerDutyServiceConfigWithAlertIntelligentGroupingUpdated(username, email, escalationPolicy, service), + Check: resource.ComposeTestCheckFunc( + testAccCheckPagerDutyServiceExists("pagerduty_service.foo"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "name", service), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "description", "foo"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "auto_resolve_timeout", "1800"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "acknowledgement_timeout", "1800"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "alert_creation", "create_alerts_and_incidents"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "alert_grouping_parameters.0.type", "intelligent"), + resource.TestCheckNoResourceAttr( + "pagerduty_service.foo", "alert_grouping_parameters.0.config.0"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "incident_urgency_rule.#", "1"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "incident_urgency_rule.0.urgency", "high"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "incident_urgency_rule.0.type", "constant"), + ), + }, + { + Config: testAccCheckPagerDutyServiceConfigWithAlertContentGroupingUpdated(username, email, escalationPolicy, service), + Check: resource.ComposeTestCheckFunc( + testAccCheckPagerDutyServiceExists("pagerduty_service.foo"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "name", service), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "description", "foo"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "auto_resolve_timeout", "1800"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "acknowledgement_timeout", "1800"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "alert_creation", "create_alerts_and_incidents"), + resource.TestCheckNoResourceAttr( + "pagerduty_service.foo", "alert_grouping_parameters.0.config"), + resource.TestCheckNoResourceAttr( + "pagerduty_service.foo", "alert_grouping_parameters.0.type"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "incident_urgency_rule.#", "1"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "incident_urgency_rule.0.urgency", "high"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "incident_urgency_rule.0.type", "constant"), + ), + }, + { + Config: testAccCheckPagerDutyServiceConfigWithAlertTimeGroupingUpdated(username, email, escalationPolicy, service), + Check: resource.ComposeTestCheckFunc( + testAccCheckPagerDutyServiceExists("pagerduty_service.foo"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "name", service), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "description", "foo"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "auto_resolve_timeout", "1800"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "acknowledgement_timeout", "1800"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "alert_creation", "create_alerts_and_incidents"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "alert_grouping_parameters.0.type", "time"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "alert_grouping_parameters.0.config.0.timeout", "5"), + resource.TestCheckNoResourceAttr( + "pagerduty_service.foo", "alert_grouping_parameters.0.config.0"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "incident_urgency_rule.#", "1"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "incident_urgency_rule.0.urgency", "high"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "incident_urgency_rule.0.type", "constant"), + ), + }, + { + Config: testAccCheckPagerDutyServiceConfigWithAlertTimeGroupingTimeoutZeroUpdated(username, email, escalationPolicy, service), + Check: resource.ComposeTestCheckFunc( + testAccCheckPagerDutyServiceExists("pagerduty_service.foo"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "name", service), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "description", "foo"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "auto_resolve_timeout", "1800"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "acknowledgement_timeout", "1800"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "alert_creation", "create_alerts_and_incidents"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "alert_grouping_parameters.0.type", "time"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "alert_grouping_parameters.0.config.0.timeout", "0"), + resource.TestCheckNoResourceAttr( + "pagerduty_service.foo", "alert_grouping_parameters.0.config.0"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "incident_urgency_rule.#", "1"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "incident_urgency_rule.0.urgency", "high"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "incident_urgency_rule.0.type", "constant"), + ), + }, + { + Config: testAccCheckPagerDutyServiceConfigWithAlertIntelligentGroupingUpdated(username, email, escalationPolicy, service), + Check: resource.ComposeTestCheckFunc( + testAccCheckPagerDutyServiceExists("pagerduty_service.foo"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "name", service), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "description", "foo"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "auto_resolve_timeout", "1800"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "acknowledgement_timeout", "1800"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "alert_creation", "create_alerts_and_incidents"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "alert_grouping_parameters.0.type", "intelligent"), + resource.TestCheckNoResourceAttr( + "pagerduty_service.foo", "alert_grouping_parameters.0.config.0"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "incident_urgency_rule.#", "1"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "incident_urgency_rule.0.urgency", "high"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "incident_urgency_rule.0.type", "constant"), + ), + }, + { + Config: testAccCheckPagerDutyServiceConfigWithAlertIntelligentGroupingDescriptionUpdated(username, email, escalationPolicy, service), + Check: resource.ComposeTestCheckFunc( + testAccCheckPagerDutyServiceExists("pagerduty_service.foo"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "name", service), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "description", "bar"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "auto_resolve_timeout", "1800"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "acknowledgement_timeout", "1800"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "alert_creation", "create_alerts_and_incidents"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "alert_grouping_parameters.0.type", "intelligent"), + resource.TestCheckNoResourceAttr( + "pagerduty_service.foo", "alert_grouping_parameters.0.config.0"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "incident_urgency_rule.#", "1"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "incident_urgency_rule.0.urgency", "high"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "incident_urgency_rule.0.type", "constant"), + ), + }, + { + Config: testAccCheckPagerDutyServiceConfigWithAlertIntelligentGroupingOmittingConfig(username, email, escalationPolicy, service), + Check: resource.ComposeTestCheckFunc( + testAccCheckPagerDutyServiceExists("pagerduty_service.foo"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "name", service), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "alert_grouping_parameters.0.type", "intelligent"), + resource.TestCheckNoResourceAttr( + "pagerduty_service.foo", "alert_grouping_parameters.0.config.0"), + ), + }, + { + Config: testAccCheckPagerDutyServiceConfigWithAlertIntelligentGroupingTypeNullEmptyConfigConfig(username, email, escalationPolicy, service), + Check: resource.ComposeTestCheckFunc( + testAccCheckPagerDutyServiceExists("pagerduty_service.foo"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "name", service), + resource.TestCheckNoResourceAttr( + "pagerduty_service.foo", "alert_grouping_parameters.0.type"), + resource.TestCheckNoResourceAttr( + "pagerduty_service.foo", "alert_grouping_parameters.0.config.0"), + ), + }, + }, + }) +} + +func TestAccPagerDutyService_AlertContentGroupingIntelligentTimeWindow(t *testing.T) { + username := fmt.Sprintf("tf-%s", acctest.RandString(5)) + email := fmt.Sprintf("%s@foo.test", username) + escalationPolicy := fmt.Sprintf("tf-%s", acctest.RandString(5)) + service := fmt.Sprintf("tf-%s", acctest.RandString(5)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV5ProviderFactories: testAccProtoV5ProviderFactories(), + CheckDestroy: testAccCheckPagerDutyServiceDestroy, + Steps: []resource.TestStep{ + { + Config: testAccCheckPagerDutyServiceConfigWithAlertContentGroupingIntelligentTimeWindow(username, email, escalationPolicy, service), + Check: resource.ComposeTestCheckFunc( + testAccCheckPagerDutyServiceExists("pagerduty_service.foo"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "name", service), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "description", "foo"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "alert_creation", "create_alerts_and_incidents"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "alert_grouping_parameters.0.type", "intelligent"), + ), + }, + { + Config: testAccCheckPagerDutyServiceConfigWithAlertContentGroupingIntelligentTimeWindowUpdated(username, email, escalationPolicy, service), + Check: resource.ComposeTestCheckFunc( + testAccCheckPagerDutyServiceExists("pagerduty_service.foo"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "name", service), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "description", "foo"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "alert_creation", "create_alerts_and_incidents"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "alert_grouping_parameters.0.type", "intelligent"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "alert_grouping_parameters.0.config.0.time_window", "900"), + ), + }, + }, + }) +} + +func TestAccPagerDutyService_AutoPauseNotificationsParameters(t *testing.T) { + username := fmt.Sprintf("tf-%s", acctest.RandString(5)) + email := fmt.Sprintf("%s@foo.test", username) + escalationPolicy := fmt.Sprintf("tf-%s", acctest.RandString(5)) + service := fmt.Sprintf("tf-%s", acctest.RandString(5)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV5ProviderFactories: testAccProtoV5ProviderFactories(), + CheckDestroy: testAccCheckPagerDutyServiceDestroy, + Steps: []resource.TestStep{ + { + Config: testAccCheckPagerDutyServiceConfigWithAutoPauseNotificationsParameters(username, email, escalationPolicy, service), + Check: resource.ComposeTestCheckFunc( + testAccCheckPagerDutyServiceExists("pagerduty_service.foo"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "name", service), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "description", "foo"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "auto_resolve_timeout", "1800"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "acknowledgement_timeout", "1800"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "alert_creation", "create_alerts_and_incidents"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "auto_pause_notifications_parameters.#", "1"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "auto_pause_notifications_parameters.0.enabled", "true"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "auto_pause_notifications_parameters.0.timeout", "300"), + ), + }, + { + Config: testAccCheckPagerDutyServiceConfigWithAutoPauseNotificationsParametersUpdated(username, email, escalationPolicy, service), + Check: resource.ComposeTestCheckFunc( + testAccCheckPagerDutyServiceExists("pagerduty_service.foo"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "name", service), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "description", "foo"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "auto_resolve_timeout", "1800"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "acknowledgement_timeout", "1800"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "alert_creation", "create_alerts_and_incidents"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "auto_pause_notifications_parameters.#", "1"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "auto_pause_notifications_parameters.0.enabled", "false"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "auto_pause_notifications_parameters.0.timeout", "0"), + ), + }, + { + Config: testAccCheckPagerDutyServiceConfigWithAutoPauseNotificationsParametersRemoved(username, email, escalationPolicy, service), + Check: resource.ComposeTestCheckFunc( + testAccCheckPagerDutyServiceExists("pagerduty_service.foo"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "name", service), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "description", "foo"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "auto_resolve_timeout", "1800"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "acknowledgement_timeout", "1800"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "alert_creation", "create_alerts_and_incidents"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "auto_pause_notifications_parameters.#", "1"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "auto_pause_notifications_parameters.0.enabled", "false"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "auto_pause_notifications_parameters.0.timeout", "0"), + ), + }, + }, + }) +} + +func TestAccPagerDutyService_BasicWithIncidentUrgencyRules(t *testing.T) { + username := fmt.Sprintf("tf-%s", acctest.RandString(5)) + email := fmt.Sprintf("%s@foo.test", username) + escalationPolicy := fmt.Sprintf("tf-%s", acctest.RandString(5)) + service := fmt.Sprintf("tf-%s", acctest.RandString(5)) + serviceUpdated := fmt.Sprintf("tf-%s", acctest.RandString(5)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV5ProviderFactories: testAccProtoV5ProviderFactories(), + CheckDestroy: testAccCheckPagerDutyServiceDestroy, + Steps: []resource.TestStep{ + { + Config: testAccCheckPagerDutyServiceWithIncidentUrgencyRulesConfig(username, email, escalationPolicy, service), + Check: resource.ComposeTestCheckFunc( + testAccCheckPagerDutyServiceExists("pagerduty_service.foo"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "name", service), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "description", "foo"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "auto_resolve_timeout", "1800"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "acknowledgement_timeout", "1800"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "alert_creation", "create_alerts_and_incidents"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "incident_urgency_rule.#", "1"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "incident_urgency_rule.0.during_support_hours.#", "1"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "incident_urgency_rule.0.during_support_hours.0.type", "constant"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "incident_urgency_rule.0.during_support_hours.0.urgency", "high"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "incident_urgency_rule.0.outside_support_hours.#", "1"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "incident_urgency_rule.0.outside_support_hours.0.type", "constant"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "incident_urgency_rule.0.outside_support_hours.0.urgency", "low"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "incident_urgency_rule.0.type", "use_support_hours"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "scheduled_actions.#", "1"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "scheduled_actions.0.at.#", "1"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "scheduled_actions.0.at.0.name", "support_hours_start"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "scheduled_actions.0.to_urgency", "high"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "scheduled_actions.0.type", "urgency_change"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "support_hours.#", "1"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "support_hours.0.days_of_week.#", "5"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "support_hours.0.days_of_week.0", "1"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "support_hours.0.days_of_week.1", "2"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "support_hours.0.days_of_week.2", "3"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "support_hours.0.days_of_week.3", "4"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "support_hours.0.days_of_week.4", "5"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "support_hours.0.end_time", "17:00:00"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "support_hours.0.start_time", "09:00:00"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "support_hours.0.time_zone", "America/Lima"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "support_hours.0.type", "fixed_time_per_day"), + ), + }, + { + Config: testAccCheckPagerDutyServiceWithIncidentUrgencyRulesConfigError(username, email, escalationPolicy, serviceUpdated), + ExpectError: regexp.MustCompile("general urgency cannot be set for a use_support_hours incident urgency rule type"), + }, + { + Config: testAccCheckPagerDutyServiceWithIncidentUrgencyRulesWithoutScheduledActionsConfig(username, email, escalationPolicy, service), + Check: resource.ComposeTestCheckFunc( + testAccCheckPagerDutyServiceExists("pagerduty_service.foo"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "name", service), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "description", "foo"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "auto_resolve_timeout", "1800"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "acknowledgement_timeout", "1800"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "alert_creation", "create_alerts_and_incidents"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "incident_urgency_rule.#", "1"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "incident_urgency_rule.0.during_support_hours.#", "1"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "incident_urgency_rule.0.during_support_hours.0.type", "constant"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "incident_urgency_rule.0.during_support_hours.0.urgency", "high"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "incident_urgency_rule.0.outside_support_hours.#", "1"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "incident_urgency_rule.0.outside_support_hours.0.type", "constant"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "incident_urgency_rule.0.outside_support_hours.0.urgency", "severity_based"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "incident_urgency_rule.0.type", "use_support_hours"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "support_hours.#", "1"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "support_hours.0.days_of_week.#", "5"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "support_hours.0.days_of_week.0", "1"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "support_hours.0.days_of_week.1", "2"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "support_hours.0.days_of_week.2", "3"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "support_hours.0.days_of_week.3", "4"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "support_hours.0.days_of_week.4", "5"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "support_hours.0.end_time", "17:00:00"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "support_hours.0.start_time", "09:00:00"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "support_hours.0.time_zone", "America/Lima"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "support_hours.0.type", "fixed_time_per_day"), + ), + }, + { + Config: testAccCheckPagerDutyServiceWithIncidentUrgencyRulesConfigUpdated(username, email, escalationPolicy, serviceUpdated), + Check: resource.ComposeTestCheckFunc( + testAccCheckPagerDutyServiceExists("pagerduty_service.foo"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "name", serviceUpdated), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "description", "bar bar bar"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "auto_resolve_timeout", "3600"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "acknowledgement_timeout", "3600"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "alert_creation", "create_alerts_and_incidents"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "incident_urgency_rule.#", "1"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "incident_urgency_rule.0.during_support_hours.#", "1"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "incident_urgency_rule.0.during_support_hours.0.type", "constant"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "incident_urgency_rule.0.during_support_hours.0.urgency", "high"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "incident_urgency_rule.0.outside_support_hours.#", "1"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "incident_urgency_rule.0.outside_support_hours.0.type", "constant"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "incident_urgency_rule.0.outside_support_hours.0.urgency", "low"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "incident_urgency_rule.0.type", "use_support_hours"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "scheduled_actions.#", "1"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "scheduled_actions.0.at.#", "1"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "scheduled_actions.0.at.0.name", "support_hours_start"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "scheduled_actions.0.to_urgency", "high"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "scheduled_actions.0.type", "urgency_change"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "support_hours.#", "1"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "support_hours.0.days_of_week.#", "5"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "support_hours.0.days_of_week.0", "1"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "support_hours.0.days_of_week.1", "2"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "support_hours.0.days_of_week.2", "3"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "support_hours.0.days_of_week.3", "4"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "support_hours.0.days_of_week.4", "5"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "support_hours.0.end_time", "17:00:00"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "support_hours.0.start_time", "09:00:00"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "support_hours.0.time_zone", "America/Lima"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "support_hours.0.type", "fixed_time_per_day"), + ), + }, + }, + }) +} + +func TestAccPagerDutyService_FromBasicToCustomIncidentUrgencyRules(t *testing.T) { + username := fmt.Sprintf("tf-%s", acctest.RandString(5)) + email := fmt.Sprintf("%s@foo.test", username) + escalationPolicy := fmt.Sprintf("tf-%s", acctest.RandString(5)) + service := fmt.Sprintf("tf-%s", acctest.RandString(5)) + serviceUpdated := fmt.Sprintf("tf-%s", acctest.RandString(5)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV5ProviderFactories: testAccProtoV5ProviderFactories(), + CheckDestroy: testAccCheckPagerDutyServiceDestroy, + Steps: []resource.TestStep{ + { + Config: testAccCheckPagerDutyServiceConfig(username, email, escalationPolicy, service), + Check: resource.ComposeTestCheckFunc( + testAccCheckPagerDutyServiceExists("pagerduty_service.foo"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "name", service), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "description", "foo"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "auto_resolve_timeout", "1800"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "acknowledgement_timeout", "1800"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "alert_creation", "create_alerts_and_incidents"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "incident_urgency_rule.#", "1"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "incident_urgency_rule.0.urgency", "high"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "incident_urgency_rule.0.type", "constant"), + ), + }, + { + Config: testAccCheckPagerDutyServiceWithIncidentUrgencyRulesConfigUpdated(username, email, escalationPolicy, serviceUpdated), + Check: resource.ComposeTestCheckFunc( + testAccCheckPagerDutyServiceExists("pagerduty_service.foo"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "name", serviceUpdated), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "description", "bar bar bar"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "auto_resolve_timeout", "3600"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "acknowledgement_timeout", "3600"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "alert_creation", "create_alerts_and_incidents"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "incident_urgency_rule.#", "1"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "incident_urgency_rule.0.during_support_hours.#", "1"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "incident_urgency_rule.0.during_support_hours.0.type", "constant"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "incident_urgency_rule.0.during_support_hours.0.urgency", "high"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "incident_urgency_rule.0.outside_support_hours.#", "1"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "incident_urgency_rule.0.outside_support_hours.0.type", "constant"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "incident_urgency_rule.0.outside_support_hours.0.urgency", "low"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "incident_urgency_rule.0.type", "use_support_hours"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "scheduled_actions.#", "1"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "scheduled_actions.0.at.#", "1"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "scheduled_actions.0.at.0.name", "support_hours_start"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "scheduled_actions.0.to_urgency", "high"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "scheduled_actions.0.type", "urgency_change"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "support_hours.#", "1"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "support_hours.0.days_of_week.#", "5"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "support_hours.0.days_of_week.0", "1"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "support_hours.0.days_of_week.1", "2"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "support_hours.0.days_of_week.2", "3"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "support_hours.0.days_of_week.3", "4"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "support_hours.0.days_of_week.4", "5"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "support_hours.0.end_time", "17:00:00"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "support_hours.0.start_time", "09:00:00"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "support_hours.0.time_zone", "America/Lima"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "support_hours.0.type", "fixed_time_per_day"), + ), + }, + }, + }) +} + +func TestAccPagerDutyService_SupportHoursChange(t *testing.T) { + username := fmt.Sprintf("tf-%s", acctest.RandString(5)) + email := fmt.Sprintf("%s@foo.test", username) + escalationPolicy := fmt.Sprintf("tf-%s", acctest.RandString(5)) + service := fmt.Sprintf("tf-%s", acctest.RandString(5)) + service_id := "" + p_service_id := &service_id + updated_service_id := "" + p_updated_service_id := &updated_service_id + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV5ProviderFactories: testAccProtoV5ProviderFactories(), + CheckDestroy: testAccCheckPagerDutyServiceDestroy, + Steps: []resource.TestStep{ + { + Config: testAccCheckPagerDutyServiceWithIncidentUrgencyRulesConfig(username, email, escalationPolicy, service), + Check: resource.ComposeTestCheckFunc( + testAccCheckPagerDutyServiceExists("pagerduty_service.foo"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "name", service), + testAccCheckPagerDutyServiceSaveServiceId(p_service_id, "pagerduty_service.foo"), + ), + }, + { + Config: testAccCheckPagerDutyServiceWithSupportHoursConfigUpdated(username, email, escalationPolicy, service), + Check: resource.ComposeTestCheckFunc( + testAccCheckPagerDutyServiceExists("pagerduty_service.foo"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "name", service), + testAccCheckPagerDutyServiceSaveServiceId(p_updated_service_id, "pagerduty_service.foo"), + ), + }, + }, + }) + + if service_id != updated_service_id { + t.Error(fmt.Errorf("Expected service id to be %s, but found %s", service_id, updated_service_id)) + } +} + +func TestAccPagerDutyService_ResponsePlay(t *testing.T) { + username := fmt.Sprintf("tf-%s", acctest.RandString(5)) + email := fmt.Sprintf("%s@foo.test", username) + escalationPolicy := fmt.Sprintf("tf-%s", acctest.RandString(5)) + responsePlay := fmt.Sprintf("tf-%s", acctest.RandString(5)) + service := fmt.Sprintf("tf-%s", acctest.RandString(5)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV5ProviderFactories: testAccProtoV5ProviderFactories(), + CheckDestroy: testAccCheckPagerDutyServiceDestroy, + Steps: []resource.TestStep{ + { + Config: testAccCheckPagerDutyServiceWithResponsePlayConfig(username, email, escalationPolicy, responsePlay, service), + Check: resource.ComposeTestCheckFunc( + testAccCheckPagerDutyServiceExists("pagerduty_service.foo"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "name", service), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "description", "foo"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "auto_resolve_timeout", "1800"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "acknowledgement_timeout", "1800"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "alert_creation", "create_alerts_and_incidents"), + resource.TestCheckNoResourceAttr( + "pagerduty_service.foo", "alert_grouping"), + resource.TestCheckNoResourceAttr( + "pagerduty_service.foo", "alert_grouping_timeout"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "incident_urgency_rule.#", "1"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "incident_urgency_rule.0.urgency", "high"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "incident_urgency_rule.0.type", "constant"), + resource.TestCheckResourceAttrSet( + "pagerduty_service.foo", "html_url"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "type", "service"), + resource.TestCheckResourceAttrSet( + "pagerduty_service.foo", "response_play"), + ), + }, + }, + }) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV5ProviderFactories: testAccProtoV5ProviderFactories(), + CheckDestroy: testAccCheckPagerDutyServiceDestroy, + Steps: []resource.TestStep{ + { + Config: testAccCheckPagerDutyServiceWithNullResponsePlayConfig(username, email, escalationPolicy, responsePlay, service), + Check: resource.ComposeTestCheckFunc( + testAccCheckPagerDutyServiceExists("pagerduty_service.foo"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "name", service), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "description", "foo"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "auto_resolve_timeout", "1800"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "acknowledgement_timeout", "1800"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "alert_creation", "create_alerts_and_incidents"), + resource.TestCheckNoResourceAttr( + "pagerduty_service.foo", "alert_grouping"), + resource.TestCheckNoResourceAttr( + "pagerduty_service.foo", "alert_grouping_timeout"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "incident_urgency_rule.#", "1"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "incident_urgency_rule.0.urgency", "high"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "incident_urgency_rule.0.type", "constant"), + resource.TestCheckResourceAttrSet( + "pagerduty_service.foo", "html_url"), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "type", "service"), + testAccCheckPagerDutyServiceResponsePlayNotExist("pagerduty_service.foo"), + ), + }, + }, + }) + +} + +func testAccCheckPagerDutyServiceSaveServiceId(p *string, n string) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("Not found: %s", n) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("No Service ID is set") + } + + ctx := context.Background() + found, err := testAccProvider.client.GetServiceWithContext(ctx, rs.Primary.ID, &pagerduty.GetServiceOptions{}) + if err != nil { + return err + } + + if found.ID != rs.Primary.ID { + return fmt.Errorf("Service not found: %v - %v", rs.Primary.ID, found) + } + + *p = found.ID + + return nil + } +} + +func testAccCheckPagerDutyServiceDestroy(s *terraform.State) error { + for _, rs := range s.RootModule().Resources { + if rs.Type != "pagerduty_service" { + continue + } + + ctx := context.Background() + _, err := testAccProvider.client.GetServiceWithContext(ctx, rs.Primary.ID, &pagerduty.GetServiceOptions{}) + if err == nil { + return fmt.Errorf("Service still exists") + } + + } + return nil +} + +func testAccCheckPagerDutyServiceExists(n string) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("Not found: %s", n) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("No Service ID is set") + } + + ctx := context.Background() + found, err := testAccProvider.client.GetServiceWithContext(ctx, rs.Primary.ID, &pagerduty.GetServiceOptions{}) + if err != nil { + return err + } + + if found.ID != rs.Primary.ID { + return fmt.Errorf("Service not found: %v - %v", rs.Primary.ID, found) + } + + return nil + } +} + +func testAccCheckPagerDutyServiceResponsePlayNotExist(n string) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("Not found: %s", n) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("No Service ID is set") + } + + ctx := context.Background() + found, err := testAccProvider.client.GetServiceWithContext(ctx, rs.Primary.ID, &pagerduty.GetServiceOptions{}) + if err != nil { + return err + } + + if found.ID == rs.Primary.ID && found.ResponsePlay != nil { + return fmt.Errorf("Service %s still has a response play configured", rs.Primary.ID) + } + + return nil + } +} + +func testAccCheckPagerDutyServiceConfig(username, email, escalationPolicy, service string) string { + return fmt.Sprintf(` +resource "pagerduty_user" "foo" { + name = "%s" + email = "%s" + color = "green" + role = "user" + job_title = "foo" + description = "foo" +} + +resource "pagerduty_escalation_policy" "foo" { + name = "%s" + description = "bar" + num_loops = 2 + rule { + escalation_delay_in_minutes = 10 + target { + type = "user_reference" + id = pagerduty_user.foo.id + } + } +} + +resource "pagerduty_service" "foo" { + name = "%s" + description = "foo" + auto_resolve_timeout = 1800 + acknowledgement_timeout = 1800 + escalation_policy = pagerduty_escalation_policy.foo.id +} +`, username, email, escalationPolicy, service) +} + +func testAccCheckPagerDutyServiceAlertGroupingInputValidationConfig(username, email, escalationPolicy, service, alertGroupingParams string) string { + return fmt.Sprintf(` +resource "pagerduty_user" "foo" { + name = "%s" + email = "%s" + color = "green" + role = "user" + job_title = "foo" + description = "foo" +} + +resource "pagerduty_escalation_policy" "foo" { + name = "%s" + description = "bar" + num_loops = 2 + rule { + escalation_delay_in_minutes = 10 + target { + type = "user_reference" + id = pagerduty_user.foo.id + } + } +} + +resource "pagerduty_service" "foo" { + name = "%s" + description = "foo" + auto_resolve_timeout = 1800 + acknowledgement_timeout = 1800 + escalation_policy = pagerduty_escalation_policy.foo.id + alert_creation = "create_alerts_and_incidents" + %s +} +`, username, email, escalationPolicy, service, alertGroupingParams) +} + +func testAccCheckPagerDutyServiceConfigWithAlertGrouping(username, email, escalationPolicy, service string) string { + return fmt.Sprintf(` +resource "pagerduty_user" "foo" { + name = "%s" + email = "%s" + color = "green" + role = "user" + job_title = "foo" + description = "foo" +} + +resource "pagerduty_escalation_policy" "foo" { + name = "%s" + description = "bar" + num_loops = 2 + rule { + escalation_delay_in_minutes = 10 + target { + type = "user_reference" + id = pagerduty_user.foo.id + } + } +} + +resource "pagerduty_service" "foo" { + name = "%s" + description = "foo" + auto_resolve_timeout = 1800 + acknowledgement_timeout = 1800 + escalation_policy = pagerduty_escalation_policy.foo.id + alert_creation = "create_alerts_and_incidents" + alert_grouping = "time" + alert_grouping_timeout = 1800 +} +`, username, email, escalationPolicy, service) +} + +func testAccCheckPagerDutyServiceConfigWithAlertContentGrouping(username, email, escalationPolicy, service string) string { + return fmt.Sprintf(` +resource "pagerduty_user" "foo" { + name = "%s" + email = "%s" + color = "green" + role = "user" + job_title = "foo" + description = "foo" +} + +resource "pagerduty_escalation_policy" "foo" { + name = "%s" + description = "bar" + num_loops = 2 + rule { + escalation_delay_in_minutes = 10 + target { + type = "user_reference" + id = pagerduty_user.foo.id + } + } +} + +resource "pagerduty_service" "foo" { + name = "%s" + description = "foo" + auto_resolve_timeout = 1800 + acknowledgement_timeout = 1800 + escalation_policy = pagerduty_escalation_policy.foo.id + alert_creation = "create_alerts_and_incidents" + alert_grouping_parameters { + type = "content_based" + config { + aggregate = "all" + fields = ["custom_details.field1"] + } + } +} +`, username, email, escalationPolicy, service) +} + +func testAccCheckPagerDutyServiceConfigWithAlertContentGroupingIntelligentTimeWindow(username, email, escalationPolicy, service string) string { + return fmt.Sprintf(` +resource "pagerduty_user" "foo" { + name = "%s" + email = "%s" + color = "green" + role = "user" + job_title = "foo" + description = "foo" +} + +resource "pagerduty_escalation_policy" "foo" { + name = "%s" + description = "bar" + num_loops = 2 + rule { + escalation_delay_in_minutes = 10 + target { + type = "user_reference" + id = pagerduty_user.foo.id + } + } +} + +resource "pagerduty_service" "foo" { + name = "%s" + description = "foo" + auto_resolve_timeout = 1800 + acknowledgement_timeout = 1800 + escalation_policy = pagerduty_escalation_policy.foo.id + alert_creation = "create_alerts_and_incidents" + alert_grouping_parameters { + type = "intelligent" + } +} +`, username, email, escalationPolicy, service) +} +func testAccCheckPagerDutyServiceConfigWithAlertContentGroupingIntelligentTimeWindowUpdated(username, email, escalationPolicy, service string) string { + return fmt.Sprintf(` +resource "pagerduty_user" "foo" { + name = "%s" + email = "%s" + color = "green" + role = "user" + job_title = "foo" + description = "foo" +} + +resource "pagerduty_escalation_policy" "foo" { + name = "%s" + description = "bar" + num_loops = 2 + rule { + escalation_delay_in_minutes = 10 + target { + type = "user_reference" + id = pagerduty_user.foo.id + } + } +} + +resource "pagerduty_service" "foo" { + name = "%s" + description = "foo" + auto_resolve_timeout = 1800 + acknowledgement_timeout = 1800 + escalation_policy = pagerduty_escalation_policy.foo.id + alert_creation = "create_alerts_and_incidents" + alert_grouping_parameters { + type = "intelligent" + config { + time_window = 900 + } + } +} +`, username, email, escalationPolicy, service) +} + +func testAccCheckPagerDutyServiceConfigWithAlertContentGroupingUpdated(username, email, escalationPolicy, service string) string { + return fmt.Sprintf(` +resource "pagerduty_user" "foo" { + name = "%s" + email = "%s" + color = "green" + role = "user" + job_title = "foo" + description = "foo" +} + +resource "pagerduty_escalation_policy" "foo" { + name = "%s" + description = "bar" + num_loops = 2 + rule { + escalation_delay_in_minutes = 10 + target { + type = "user_reference" + id = pagerduty_user.foo.id + } + } +} + +resource "pagerduty_service" "foo" { + name = "%s" + description = "foo" + auto_resolve_timeout = 1800 + acknowledgement_timeout = 1800 + escalation_policy = pagerduty_escalation_policy.foo.id + alert_creation = "create_alerts_and_incidents" + alert_grouping_parameters { + type = null + } +} +`, username, email, escalationPolicy, service) +} + +func testAccCheckPagerDutyServiceConfigWithAlertTimeGroupingUpdated(username, email, escalationPolicy, service string) string { + return fmt.Sprintf(` +resource "pagerduty_user" "foo" { + name = "%s" + email = "%s" + color = "green" + role = "user" + job_title = "foo" + description = "foo" +} + +resource "pagerduty_escalation_policy" "foo" { + name = "%s" + description = "bar" + num_loops = 2 + rule { + escalation_delay_in_minutes = 10 + target { + type = "user_reference" + id = pagerduty_user.foo.id + } + } +} + +resource "pagerduty_service" "foo" { + name = "%s" + description = "foo" + auto_resolve_timeout = 1800 + acknowledgement_timeout = 1800 + escalation_policy = pagerduty_escalation_policy.foo.id + alert_creation = "create_alerts_and_incidents" + alert_grouping_parameters { + type = "time" + config { + timeout = 5 + } + } +} +`, username, email, escalationPolicy, service) +} + +func testAccCheckPagerDutyServiceConfigWithAlertTimeGroupingTimeoutZeroUpdated(username, email, escalationPolicy, service string) string { + return fmt.Sprintf(` +resource "pagerduty_user" "foo" { + name = "%s" + email = "%s" + color = "green" + role = "user" + job_title = "foo" + description = "foo" +} + +resource "pagerduty_escalation_policy" "foo" { + name = "%s" + description = "bar" + num_loops = 2 + rule { + escalation_delay_in_minutes = 10 + target { + type = "user_reference" + id = pagerduty_user.foo.id + } + } +} + +resource "pagerduty_service" "foo" { + name = "%s" + description = "foo" + auto_resolve_timeout = 1800 + acknowledgement_timeout = 1800 + escalation_policy = pagerduty_escalation_policy.foo.id + alert_creation = "create_alerts_and_incidents" + alert_grouping_parameters { + type = "time" + config { + timeout = 0 + } + } +} +`, username, email, escalationPolicy, service) +} + +func testAccCheckPagerDutyServiceConfigWithAlertGroupingUpdated(username, email, escalationPolicy, service string) string { + return fmt.Sprintf(` +resource "pagerduty_user" "foo" { + name = "%s" + email = "%s" + color = "green" + role = "user" + job_title = "foo" + description = "foo" +} + +resource "pagerduty_escalation_policy" "foo" { + name = "%s" + description = "bar" + num_loops = 2 + rule { + escalation_delay_in_minutes = 10 + target { + type = "user_reference" + id = pagerduty_user.foo.id + } + } +} + +resource "pagerduty_service" "foo" { + name = "%s" + description = "foo" + auto_resolve_timeout = 1800 + acknowledgement_timeout = 1800 + escalation_policy = pagerduty_escalation_policy.foo.id + alert_creation = "create_alerts_and_incidents" + alert_grouping = "intelligent" + alert_grouping_timeout = 1900 +} +`, username, email, escalationPolicy, service) +} + +func testAccCheckPagerDutyServiceConfigWithAlertIntelligentGroupingUpdated(username, email, escalationPolicy, service string) string { + return fmt.Sprintf(` +resource "pagerduty_user" "foo" { + name = "%s" + email = "%s" + color = "green" + role = "user" + job_title = "foo" + description = "foo" +} + +resource "pagerduty_escalation_policy" "foo" { + name = "%s" + description = "bar" + num_loops = 2 + rule { + escalation_delay_in_minutes = 10 + target { + type = "user_reference" + id = pagerduty_user.foo.id + } + } +} + +resource "pagerduty_service" "foo" { + name = "%s" + description = "foo" + auto_resolve_timeout = 1800 + acknowledgement_timeout = 1800 + escalation_policy = pagerduty_escalation_policy.foo.id + alert_creation = "create_alerts_and_incidents" + alert_grouping_parameters { + type = "intelligent" + config { + fields = null + timeout = 0 + } + } +} +`, username, email, escalationPolicy, service) +} + +func testAccCheckPagerDutyServiceConfigWithAlertIntelligentGroupingDescriptionUpdated(username, email, escalationPolicy, service string) string { + return fmt.Sprintf(` +resource "pagerduty_user" "foo" { + name = "%s" + email = "%s" + color = "green" + role = "user" + job_title = "foo" + description = "foo" +} + +resource "pagerduty_escalation_policy" "foo" { + name = "%s" + description = "bar" + num_loops = 2 + rule { + escalation_delay_in_minutes = 10 + target { + type = "user_reference" + id = pagerduty_user.foo.id + } + } +} + +resource "pagerduty_service" "foo" { + name = "%s" + description = "bar" + auto_resolve_timeout = 1800 + acknowledgement_timeout = 1800 + escalation_policy = pagerduty_escalation_policy.foo.id + alert_creation = "create_alerts_and_incidents" + alert_grouping_parameters { + type = "intelligent" + config {} + } +} +`, username, email, escalationPolicy, service) +} + +func testAccCheckPagerDutyServiceConfigWithAlertIntelligentGroupingOmittingConfig(username, email, escalationPolicy, service string) string { + return fmt.Sprintf(` +resource "pagerduty_user" "foo" { + name = "%s" + email = "%s" + color = "green" + role = "user" + job_title = "foo" + description = "foo" +} + +resource "pagerduty_escalation_policy" "foo" { + name = "%s" + description = "bar" + num_loops = 2 + rule { + escalation_delay_in_minutes = 10 + target { + type = "user_reference" + id = pagerduty_user.foo.id + } + } +} + +resource "pagerduty_service" "foo" { + name = "%s" + description = "bar" + auto_resolve_timeout = 1800 + acknowledgement_timeout = 1800 + escalation_policy = pagerduty_escalation_policy.foo.id + alert_creation = "create_alerts_and_incidents" + alert_grouping_parameters { + type = "intelligent" + } +} +`, username, email, escalationPolicy, service) +} + +func testAccCheckPagerDutyServiceConfigWithAlertIntelligentGroupingTypeNullEmptyConfigConfig(username, email, escalationPolicy, service string) string { + return fmt.Sprintf(` +resource "pagerduty_user" "foo" { + name = "%s" + email = "%s" + color = "green" + role = "user" + job_title = "foo" + description = "foo" +} + +resource "pagerduty_escalation_policy" "foo" { + name = "%s" + description = "bar" + num_loops = 2 + rule { + escalation_delay_in_minutes = 10 + target { + type = "user_reference" + id = pagerduty_user.foo.id + } + } +} + +resource "pagerduty_service" "foo" { + name = "%s" + description = "bar" + auto_resolve_timeout = 1800 + acknowledgement_timeout = 1800 + escalation_policy = pagerduty_escalation_policy.foo.id + alert_creation = "create_alerts_and_incidents" + alert_grouping_parameters { + type = null + config {} + } +} +`, username, email, escalationPolicy, service) +} + +func testAccCheckPagerDutyServiceConfigWithAutoPauseNotificationsParameters(username, email, escalationPolicy, service string) string { + return fmt.Sprintf(` +resource "pagerduty_user" "foo" { + name = "%s" + email = "%s" + color = "green" + role = "user" + job_title = "foo" + description = "foo" +} + +resource "pagerduty_escalation_policy" "foo" { + name = "%s" + description = "bar" + num_loops = 2 + rule { + escalation_delay_in_minutes = 10 + target { + type = "user_reference" + id = pagerduty_user.foo.id + } + } +} + +resource "pagerduty_service" "foo" { + name = "%s" + description = "foo" + auto_resolve_timeout = 1800 + acknowledgement_timeout = 1800 + escalation_policy = pagerduty_escalation_policy.foo.id + alert_creation = "create_alerts_and_incidents" + auto_pause_notifications_parameters { + enabled = true + timeout = 300 + } +} +`, username, email, escalationPolicy, service) +} + +func testAccCheckPagerDutyServiceConfigWithAutoPauseNotificationsParametersUpdated(username, email, escalationPolicy, service string) string { + return fmt.Sprintf(` +resource "pagerduty_user" "foo" { + name = "%s" + email = "%s" + color = "green" + role = "user" + job_title = "foo" + description = "foo" +} + +resource "pagerduty_escalation_policy" "foo" { + name = "%s" + description = "bar" + num_loops = 2 + rule { + escalation_delay_in_minutes = 10 + target { + type = "user_reference" + id = pagerduty_user.foo.id + } + } +} + +resource "pagerduty_service" "foo" { + name = "%s" + description = "foo" + auto_resolve_timeout = 1800 + acknowledgement_timeout = 1800 + escalation_policy = pagerduty_escalation_policy.foo.id + alert_creation = "create_alerts_and_incidents" + auto_pause_notifications_parameters { + enabled = false + timeout = null + } +} +`, username, email, escalationPolicy, service) +} + +func testAccCheckPagerDutyServiceConfigWithAutoPauseNotificationsParametersRemoved(username, email, escalationPolicy, service string) string { + return fmt.Sprintf(` +resource "pagerduty_user" "foo" { + name = "%s" + email = "%s" + color = "green" + role = "user" + job_title = "foo" + description = "foo" +} + +resource "pagerduty_escalation_policy" "foo" { + name = "%s" + description = "bar" + num_loops = 2 + rule { + escalation_delay_in_minutes = 10 + target { + type = "user_reference" + id = pagerduty_user.foo.id + } + } +} + +resource "pagerduty_service" "foo" { + name = "%s" + description = "foo" + auto_resolve_timeout = 1800 + acknowledgement_timeout = 1800 + escalation_policy = pagerduty_escalation_policy.foo.id + alert_creation = "create_alerts_and_incidents" +} +`, username, email, escalationPolicy, service) +} + +func testAccCheckPagerDutyServiceConfigUpdated(username, email, escalationPolicy, service string) string { + return fmt.Sprintf(` +resource "pagerduty_user" "foo" { + name = "%s" + email = "%s" + color = "green" + role = "user" + job_title = "foo" + description = "foo" +} + +resource "pagerduty_escalation_policy" "foo" { + name = "%s" + description = "bar" + num_loops = 2 + + rule { + escalation_delay_in_minutes = 10 + target { + type = "user_reference" + id = pagerduty_user.foo.id + } + } +} + +resource "pagerduty_service" "foo" { + name = "%s" + description = "bar" + auto_resolve_timeout = 3600 + acknowledgement_timeout = 3600 + + escalation_policy = pagerduty_escalation_policy.foo.id + incident_urgency_rule { + type = "constant" + urgency = "high" + } +} +`, username, email, escalationPolicy, service) +} + +func testAccCheckPagerDutyServiceConfigUpdatedWithDisabledTimeouts(username, email, escalationPolicy, service string) string { + return fmt.Sprintf(` +resource "pagerduty_user" "foo" { + name = "%s" + email = "%s" + color = "green" + role = "user" + job_title = "foo" + description = "foo" +} + +resource "pagerduty_escalation_policy" "foo" { + name = "%s" + description = "bar" + num_loops = 2 + + rule { + escalation_delay_in_minutes = 10 + target { + type = "user_reference" + id = pagerduty_user.foo.id + } + } +} + +resource "pagerduty_service" "foo" { + name = "%s" + description = "bar" + auto_resolve_timeout = "null" + acknowledgement_timeout = "null" + + escalation_policy = pagerduty_escalation_policy.foo.id + incident_urgency_rule { + type = "constant" + urgency = "high" + } +} +`, username, email, escalationPolicy, service) +} + +func testAccCheckPagerDutyServiceWithIncidentUrgencyRulesConfig(username, email, escalationPolicy, service string) string { + return fmt.Sprintf(` +resource "pagerduty_user" "foo" { + name = "%s" + email = "%s" + color = "green" + role = "user" + job_title = "foo" + description = "foo" +} + +resource "pagerduty_escalation_policy" "foo" { + name = "%s" + description = "bar" + num_loops = 2 + + rule { + escalation_delay_in_minutes = 10 + target { + type = "user_reference" + id = pagerduty_user.foo.id + } + } +} + +resource "pagerduty_service" "foo" { + name = "%s" + description = "foo" + auto_resolve_timeout = 1800 + acknowledgement_timeout = 1800 + escalation_policy = pagerduty_escalation_policy.foo.id + + incident_urgency_rule { + type = "use_support_hours" + + during_support_hours { + type = "constant" + urgency = "high" + } + outside_support_hours { + type = "constant" + urgency = "low" + } + } + + support_hours { + type = "fixed_time_per_day" + time_zone = "America/Lima" + start_time = "09:00:00" + end_time = "17:00:00" + days_of_week = [ 1, 2, 3, 4, 5 ] + } + + scheduled_actions { + type = "urgency_change" + to_urgency = "high" + at { + type = "named_time" + name = "support_hours_start" + } + } +} +`, username, email, escalationPolicy, service) +} + +func testAccCheckPagerDutyServiceWithIncidentUrgencyRulesConfigError(username, email, escalationPolicy, service string) string { + return fmt.Sprintf(` +resource "pagerduty_user" "foo" { + name = "%s" + email = "%s" + color = "green" + role = "user" + job_title = "foo" + description = "foo" +} + +resource "pagerduty_escalation_policy" "foo" { + name = "%s" + description = "bar" + num_loops = 2 + + rule { + escalation_delay_in_minutes = 10 + target { + type = "user_reference" + id = pagerduty_user.foo.id + } + } +} + +resource "pagerduty_service" "foo" { + name = "%s" + description = "foo" + auto_resolve_timeout = 1800 + acknowledgement_timeout = 1800 + escalation_policy = pagerduty_escalation_policy.foo.id + + incident_urgency_rule { + type = "use_support_hours" + urgency = "high" + during_support_hours { + type = "constant" + urgency = "high" + } + outside_support_hours { + type = "constant" + urgency = "low" + } + } + + support_hours { + type = "fixed_time_per_day" + time_zone = "America/Lima" + start_time = "09:00:00" + end_time = "17:00:00" + days_of_week = [ 1, 2, 3, 4, 5 ] + } + + scheduled_actions { + type = "urgency_change" + to_urgency = "high" + at { + type = "named_time" + name = "support_hours_start" + } + } +} +`, username, email, escalationPolicy, service) +} + +func testAccCheckPagerDutyServiceWithIncidentUrgencyRulesWithoutScheduledActionsConfig(username, email, escalationPolicy, service string) string { + return fmt.Sprintf(` +resource "pagerduty_user" "foo" { + name = "%s" + email = "%s" + color = "green" + role = "user" + job_title = "foo" + description = "foo" +} + +resource "pagerduty_escalation_policy" "foo" { + name = "%s" + description = "bar" + num_loops = 2 + + rule { + escalation_delay_in_minutes = 10 + target { + type = "user_reference" + id = pagerduty_user.foo.id + } + } +} + +resource "pagerduty_service" "foo" { + name = "%s" + description = "foo" + auto_resolve_timeout = 1800 + acknowledgement_timeout = 1800 + escalation_policy = pagerduty_escalation_policy.foo.id + + incident_urgency_rule { + type = "use_support_hours" + + during_support_hours { + type = "constant" + urgency = "high" + } + outside_support_hours { + type = "constant" + urgency = "severity_based" + } + } + + support_hours { + type = "fixed_time_per_day" + time_zone = "America/Lima" + start_time = "09:00:00" + end_time = "17:00:00" + days_of_week = [ 1, 2, 3, 4, 5 ] + } +} +`, username, email, escalationPolicy, service) +} + +func testAccCheckPagerDutyServiceWithIncidentUrgencyRulesConfigUpdated(username, email, escalationPolicy, service string) string { + return fmt.Sprintf(` + resource "pagerduty_user" "foo" { + name = "%s" + email = "%s" + color = "green" + role = "user" + job_title = "foo" + description = "foo" +} + +resource "pagerduty_escalation_policy" "foo" { + name = "%s" + description = "bar" + num_loops = 2 + + rule { + escalation_delay_in_minutes = 10 + target { + type = "user_reference" + id = pagerduty_user.foo.id + } + } +} + +resource "pagerduty_service" "foo" { + name = "%s" + description = "bar bar bar" + auto_resolve_timeout = 3600 + acknowledgement_timeout = 3600 + escalation_policy = pagerduty_escalation_policy.foo.id + + incident_urgency_rule { + type = "use_support_hours" + during_support_hours { + type = "constant" + urgency = "high" + } + outside_support_hours { + type = "constant" + urgency = "low" + } + } + + support_hours { + type = "fixed_time_per_day" + time_zone = "America/Lima" + start_time = "09:00:00" + end_time = "17:00:00" + days_of_week = [ 1, 2, 3, 4, 5 ] + } + + scheduled_actions { + type = "urgency_change" + to_urgency = "high" + at { + type = "named_time" + name = "support_hours_start" + } + } +} +`, username, email, escalationPolicy, service) +} + +func testAccCheckPagerDutyServiceWithSupportHoursConfigUpdated(username, email, escalationPolicy, service string) string { + return fmt.Sprintf(` +resource "pagerduty_user" "foo" { + name = "%s" + email = "%s" + color = "green" + role = "user" + job_title = "foo" + description = "foo" +} + +resource "pagerduty_escalation_policy" "foo" { + name = "%s" + description = "bar" + num_loops = 2 + + rule { + escalation_delay_in_minutes = 10 + target { + type = "user_reference" + id = pagerduty_user.foo.id + } + } +} + +resource "pagerduty_service" "foo" { + name = "%s" + description = "foo" + auto_resolve_timeout = 1800 + acknowledgement_timeout = 1800 + escalation_policy = pagerduty_escalation_policy.foo.id + + incident_urgency_rule { + type = "constant" + urgency = "high" + } + +} +`, username, email, escalationPolicy, service) +} + +func testAccCheckPagerDutyServiceWithResponsePlayConfig(username, email, escalationPolicy, responsePlay, service string) string { + return fmt.Sprintf(` +resource "pagerduty_user" "foo" { + name = "%s" + email = "%s" + color = "green" + role = "user" + job_title = "foo" + description = "foo" +} + +resource "pagerduty_escalation_policy" "foo" { + name = "%s" + description = "bar" + num_loops = 2 + rule { + escalation_delay_in_minutes = 10 + target { + type = "user_reference" + id = pagerduty_user.foo.id + } + } +} + +resource "pagerduty_response_play" "foo" { + name = "%s" + from = pagerduty_user.foo.email + + responder { + type = "escalation_policy_reference" + id = pagerduty_escalation_policy.foo.id + } + + subscriber { + type = "user_reference" + id = pagerduty_user.foo.id + } + + runnability = "services" +} + +resource "pagerduty_service" "foo" { + name = "%s" + description = "foo" + auto_resolve_timeout = 1800 + acknowledgement_timeout = 1800 + escalation_policy = pagerduty_escalation_policy.foo.id + response_play = pagerduty_response_play.foo.id +} +`, username, email, escalationPolicy, responsePlay, service) +} + +func testAccCheckPagerDutyServiceWithNullResponsePlayConfig(username, email, escalationPolicy, responsePlay, service string) string { + return fmt.Sprintf(` +resource "pagerduty_user" "foo" { + name = "%s" + email = "%s" + color = "green" + role = "user" + job_title = "foo" + description = "foo" +} + +resource "pagerduty_escalation_policy" "foo" { + name = "%s" + description = "bar" + num_loops = 2 + rule { + escalation_delay_in_minutes = 10 + target { + type = "user_reference" + id = pagerduty_user.foo.id + } + } +} + +resource "pagerduty_response_play" "foo" { + name = "%s" + from = pagerduty_user.foo.email + + responder { + type = "escalation_policy_reference" + id = pagerduty_escalation_policy.foo.id + } + + subscriber { + type = "user_reference" + id = pagerduty_user.foo.id + } + + runnability = "services" +} + +resource "pagerduty_service" "foo" { + name = "%s" + description = "foo" + auto_resolve_timeout = 1800 + acknowledgement_timeout = 1800 + escalation_policy = pagerduty_escalation_policy.foo.id + response_play = null +} +`, username, email, escalationPolicy, responsePlay, service) +} + +func TestFlattenAlertGroupingParameters_Basic(t *testing.T) { + var diags diag.Diagnostics + var timeout uint = 1000 + params := &pagerduty.AlertGroupingParameters{ + Type: "foo", + Config: &pagerduty.AlertGroupParamsConfig{ + Timeout: &timeout, + Aggregate: "aggregate", + Fields: []string{"a", "b", "c"}, + }, + } + li := flattenAlertGroupingParameters(context.Background(), params, &diags) + if diags.HasError() { + t.Fatalf("unexpected error %s", diags) + } + t.Logf("%#v", li) +} diff --git a/util/build.go b/util/build.go new file mode 100644 index 000000000..80bec4b65 --- /dev/null +++ b/util/build.go @@ -0,0 +1,23 @@ +package util + +import ( + "fmt" + "strconv" + + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +func StringToUintPointer(p path.Path, s types.String, diags *diag.Diagnostics) *uint { + if s.IsNull() || s.IsUnknown() || s.ValueString() == "" || s.ValueString() == "null" { + return nil + } + if val, err := strconv.Atoi(s.ValueString()); err == nil { + uintvalue := uint(val) + return &uintvalue + } else { + diags.AddError(fmt.Sprintf("Value for %q is not a valid number", p), err.Error()) + } + return nil +} diff --git a/util/enumtypes/int64.go b/util/enumtypes/int64.go new file mode 100644 index 000000000..0aab6bf94 --- /dev/null +++ b/util/enumtypes/int64.go @@ -0,0 +1,105 @@ +package enumtypes + +import ( + "context" + "fmt" + "math/big" + "slices" + + "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/types/basetypes" + "github.com/hashicorp/terraform-plugin-go/tftypes" +) + +type Int64Value struct { + basetypes.Int64Value + EnumType Int64Type +} + +func NewInt64Null(t Int64Type) Int64Value { + return Int64Value{Int64Value: basetypes.NewInt64Null(), EnumType: t} +} + +func NewInt64Value(v int64, t Int64Type) Int64Value { + return Int64Value{Int64Value: basetypes.NewInt64Value(v), EnumType: t} +} + +func (s Int64Value) Type(_ context.Context) attr.Type { + return s.EnumType +} + +type Int64Type struct { + basetypes.Int64Type + OneOf []int64 +} + +func (t Int64Type) Int64() string { + return "enumtypes.Int64Type" +} + +func (t Int64Type) Equal(o attr.Type) bool { + if t2, ok := o.(Int64Type); ok { + return slices.Equal(t.OneOf, t2.OneOf) + } + return t.Int64Type.Equal(o) +} + +func (t Int64Type) Validate(ctx context.Context, in tftypes.Value, path path.Path) (diags diag.Diagnostics) { + if in.Type() == nil { + return + } + + if !in.Type().Is(tftypes.Number) { + err := fmt.Errorf("expected Int64 value, received %T with value: %v", in, in) + diags.AddAttributeError( + path, + "Type Validation Error", + "An unexpected error was encountered trying to validate an attribute value. This is always an error in the provider. "+ + "Please report the following to the provider developer:\n\n"+err.Error(), + ) + return diags + } + + if !in.IsKnown() || in.IsNull() { + return diags + } + + var valueFloat big.Float + if err := in.As(&valueFloat); err != nil { + diags.AddAttributeError( + path, + "Type Validation Error", + "An unexpected error was encountered trying to validate an attribute value. This is always an error in the provider. "+ + "Please report the following to the provider developer:\n\n"+err.Error(), + ) + return + } + valueInt64, _ := valueFloat.Int64() + + found := false + for _, v := range t.OneOf { + if v == valueInt64 { + found = true + break + } + } + + if !found { + diags.AddAttributeError( + path, + "Invalid Int64 Value", + fmt.Sprintf( + "A string value was provided that is not valid.\n"+ + "Given Value: %v\n"+ + "Expecting One Of: %v", + valueInt64, + t.OneOf, + ), + ) + return + } + + return +} diff --git a/util/enumtypes/string.go b/util/enumtypes/string.go new file mode 100644 index 000000000..1d3a522c0 --- /dev/null +++ b/util/enumtypes/string.go @@ -0,0 +1,100 @@ +package enumtypes + +import ( + "context" + "fmt" + "slices" + "strings" + + "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/types/basetypes" + "github.com/hashicorp/terraform-plugin-go/tftypes" +) + +type StringValue struct { + basetypes.StringValue + EnumType StringType +} + +func NewStringNull(t StringType) StringValue { + return StringValue{StringValue: basetypes.NewStringNull(), EnumType: t} +} + +func NewStringValue(v string, t StringType) StringValue { + return StringValue{StringValue: basetypes.NewStringValue(v), EnumType: t} +} + +func (s StringValue) Type(_ context.Context) attr.Type { + return s.EnumType +} + +type StringType struct { + basetypes.StringType + OneOf []string +} + +func (t StringType) String() string { + return "enumtypes.StringType" +} + +func (t StringType) Equal(o attr.Type) bool { + if t2, ok := o.(StringType); ok { + return slices.Equal(t.OneOf, t2.OneOf) + } + return t.StringType.Equal(o) +} + +func (t StringType) Validate(ctx context.Context, in tftypes.Value, path path.Path) (diags diag.Diagnostics) { + if in.Type() == nil { + return + } + + if !in.Type().Is(tftypes.String) { + err := fmt.Errorf("expected String value, received %T with value: %v", in, in) + diags.AddAttributeError( + path, + "Type Validation Error", + "An unexpected error was encountered trying to validate an attribute value. This is always an error in the provider. "+ + "Please report the following to the provider developer:\n\n"+err.Error(), + ) + return diags + } + + if !in.IsKnown() || in.IsNull() { + return diags + } + + var valueString string + if err := in.As(&valueString); err != nil { + diags.AddAttributeError( + path, + "Type Validation Error", + "An unexpected error was encountered trying to validate an attribute value. This is always an error in the provider. "+ + "Please report the following to the provider developer:\n\n"+err.Error(), + ) + return + } + + found := false + for _, v := range t.OneOf { + if v == valueString { + found = true + break + } + } + + if !found { + diags.AddAttributeError( + path, + "Invalid String Value", + "A string value was provided that is not valid.\n"+ + "Given Value: "+valueString+"\n"+ + "Expecting One Of: "+strings.Join(t.OneOf, ", "), + ) + return + } + + return +} diff --git a/util/rangetypes/int64.go b/util/rangetypes/int64.go new file mode 100644 index 000000000..c23467fb1 --- /dev/null +++ b/util/rangetypes/int64.go @@ -0,0 +1,91 @@ +package rangetypes + +import ( + "context" + "fmt" + "math/big" + + "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/types/basetypes" + "github.com/hashicorp/terraform-plugin-go/tftypes" +) + +type Int64Value struct { + basetypes.Int64Value + RangeType Int64Type +} + +func NewInt64Null(t Int64Type) Int64Value { + return Int64Value{Int64Value: basetypes.NewInt64Null(), RangeType: t} +} + +func NewInt64Value(v int64, t Int64Type) Int64Value { + return Int64Value{Int64Value: basetypes.NewInt64Value(v), RangeType: t} +} + +func (s Int64Value) Type(_ context.Context) attr.Type { + return s.RangeType +} + +type Int64Type struct { + basetypes.Int64Type + Start int64 + End int64 +} + +func (t Int64Type) String() string { + return "rangetypes.Int64Type" +} + +func (t Int64Type) Equal(o attr.Type) bool { + if t2, ok := o.(Int64Type); ok { + return t.Start == t2.Start && t.End == t2.End + } + return t.Int64Type.Equal(o) +} + +func (t Int64Type) addTypeValidationError(err error, path path.Path, diags *diag.Diagnostics) { + diags.AddAttributeError( + path, + "Type Validation Error", + "An unexpected error was encountered trying to validate an attribute value. This is always an error in the provider. "+ + "Please report the following to the provider developer:\n\n"+err.Error(), + ) +} + +func (t Int64Type) Validate(ctx context.Context, in tftypes.Value, path path.Path) (diags diag.Diagnostics) { + if in.Type() == nil { + return + } + + if !in.Type().Is(tftypes.Number) { + err := fmt.Errorf("expected Int64 value, received %T with value: %v", in, in) + t.addTypeValidationError(err, path, &diags) + return + } + + if !in.IsKnown() || in.IsNull() { + return + } + + var valueFloat big.Float + if err := in.As(&valueFloat); err != nil { + t.addTypeValidationError(err, path, &diags) + return + } + valueInt64, _ := valueFloat.Int64() + + if valueInt64 < t.Start || valueInt64 > int64(t.End) { + diags.AddAttributeError( + path, + "Invalid Int64 Value", + fmt.Sprintf("A value was provided that is not inside valid range (%v, %v).\n"+ + "Given Value: %v", t.Start, t.End, valueInt64), + ) + return + } + + return +} diff --git a/util/string_describer.go b/util/string_describer.go new file mode 100644 index 000000000..9528a0d6a --- /dev/null +++ b/util/string_describer.go @@ -0,0 +1,13 @@ +package util + +import "context" + +type StringDescriber struct{ Value string } + +func (d StringDescriber) MarkdownDescription(context.Context) string { + return d.Value +} + +func (d StringDescriber) Description(ctx context.Context) string { + return d.MarkdownDescription(ctx) +} diff --git a/util/tztypes/string.go b/util/tztypes/string.go new file mode 100644 index 000000000..315d9edaf --- /dev/null +++ b/util/tztypes/string.go @@ -0,0 +1,90 @@ +package tztypes + +import ( + "context" + "fmt" + + "github.com/PagerDuty/terraform-provider-pagerduty/util" + "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/types/basetypes" + "github.com/hashicorp/terraform-plugin-go/tftypes" +) + +type StringValue struct { + basetypes.StringValue +} + +func NewStringNull() StringValue { + return StringValue{StringValue: basetypes.NewStringNull()} +} + +func NewStringValue(v string) StringValue { + return StringValue{StringValue: basetypes.NewStringValue(v)} +} + +func (s StringValue) Type(_ context.Context) attr.Type { + return StringType{} +} + +type StringType struct { + basetypes.StringType +} + +func (t StringType) String() string { + return "tztypes.StringType" +} + +func (t StringType) Equal(o attr.Type) bool { + _, ok := o.(StringType) + if ok { + return true + } + + return t.StringType.Equal(o) +} + +func (t StringType) Validate(ctx context.Context, in tftypes.Value, path path.Path) (diags diag.Diagnostics) { + if in.Type() == nil { + return + } + + if !in.Type().Is(tftypes.String) { + err := fmt.Errorf("expected String value, received %T with value: %v", in, in) + diags.AddAttributeError( + path, + "Type Validation Error", + "An unexpected error was encountered trying to validate an attribute value. This is always an error in the provider. "+ + "Please report the following to the provider developer:\n\n"+err.Error(), + ) + return diags + } + + if !in.IsKnown() || in.IsNull() { + return diags + } + + var valueString string + if err := in.As(&valueString); err != nil { + diags.AddAttributeError( + path, + "Type Validation Error", + "An unexpected error was encountered trying to validate an attribute value. This is always an error in the provider. "+ + "Please report the following to the provider developer:\n\n"+err.Error(), + ) + return + } + + if !util.IsValidTZ(valueString) { + diags.AddAttributeError( + path, + "Invalid String Value", + "A string value was provided that is not a valid timezone.\n"+ + "Given Value: "+valueString, + ) + return + } + + return +} diff --git a/util/util.go b/util/util.go index 4f24958ba..6971a312b 100644 --- a/util/util.go +++ b/util/util.go @@ -278,16 +278,16 @@ func ResourcePagerDutyParseColonCompoundID(id string) (string, string, error) { return parts[0], parts[1], nil } +func IsValidTZ(v string) bool { + foundAt := sort.SearchStrings(validTZ, v) + return foundAt < len(validTZ) && validTZ[foundAt] == v +} + func ValidateTZValueDiagFunc(v interface{}, p cty.Path) diag.Diagnostics { var diags diag.Diagnostics value := v.(string) - valid := false - - foundAt := sort.SearchStrings(validTZ, value) - if foundAt < len(validTZ) && validTZ[foundAt] == value { - valid = true - } + valid := IsValidTZ(value) if !valid { diags = append(diags, diag.Diagnostic{ diff --git a/util/validate/alternatives_for_path.go b/util/validate/alternatives_for_path.go new file mode 100644 index 000000000..599a7990a --- /dev/null +++ b/util/validate/alternatives_for_path.go @@ -0,0 +1,28 @@ +package validate + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" +) + +func AlternativesForPath(p path.Path, alt []attr.Value) *alternativesForPathValidator { + return &alternativesForPathValidator{Path: p, Alternatives: alt} +} + +type alternativesForPathValidator struct { + Path path.Path + Alternatives []attr.Value +} + +var _ validator.String = (*alternativesForPathValidator)(nil) + +func (v *alternativesForPathValidator) Description(_ context.Context) string { return "" } +func (v *alternativesForPathValidator) MarkdownDescription(ctx context.Context) string { + return v.Description(ctx) +} + +func (v *alternativesForPathValidator) ValidateString(ctx context.Context, req validator.StringRequest, resp *validator.StringResponse) { +} diff --git a/util/validate/is_allowed_string.go b/util/validate/is_allowed_string.go new file mode 100644 index 000000000..4424544ec --- /dev/null +++ b/util/validate/is_allowed_string.go @@ -0,0 +1,43 @@ +package validate + +import ( + "context" + "strings" + "unicode" + + "github.com/PagerDuty/terraform-provider-pagerduty/util" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" +) + +type validateIsAllowedString struct { + validateFn func(s string) bool + util.StringDescriber +} + +func (v validateIsAllowedString) ValidateString(ctx context.Context, req validator.StringRequest, resp *validator.StringResponse) { + if ok := v.validateFn(req.ConfigValue.ValueString()); !ok { + resp.Diagnostics.AddError(v.Value, "") + } +} + +func IsAllowedString(mode util.StringContentValidationMode) validator.String { + switch mode { + case util.NoNonPrintableChars: + return validateIsAllowedString{ + func(s string) bool { + for _, char := range s { + if !unicode.IsPrint(char) { + return false + } + } + return s != "" && !strings.HasSuffix(s, " ") + }, + util.StringDescriber{Value: "Name can not be blank, nor contain non-printable characters. Trailing white spaces are not allowed either."}, + } + default: + return validateIsAllowedString{ + func(s string) bool { return false }, + util.StringDescriber{Value: "Invalid mode while using func IsAllowedStringValidator(mode StringContentValidationMode)"}, + } + } +} diff --git a/util/validate/require.go b/util/validate/require.go new file mode 100644 index 000000000..903bef2b1 --- /dev/null +++ b/util/validate/require.go @@ -0,0 +1,53 @@ +package validate + +import ( + "context" + "fmt" + + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/resource" +) + +// Require checks a path is not null. +func Require(p path.Path) resource.ConfigValidator { + return &requirePath{Path: p} +} + +type requirePath struct { + path.Path +} + +func (v *requirePath) Description(ctx context.Context) string { + return "Forces item to be present if its parent is present" +} + +func (v *requirePath) MarkdownDescription(ctx context.Context) string { + return v.Description(ctx) +} + +func (v *requirePath) ValidateResource(ctx context.Context, req resource.ValidateConfigRequest, resp *resource.ValidateConfigResponse) { + var parent attr.Value + resp.Diagnostics.Append(req.Config.GetAttribute(ctx, v.Path.ParentPath(), &parent)...) + if resp.Diagnostics.HasError() { + return + } + if parent.IsNull() { + return + } + + var src attr.Value + resp.Diagnostics.Append(req.Config.GetAttribute(ctx, v.Path, &src)...) + if resp.Diagnostics.HasError() { + return + } + + if src.IsNull() { + resp.Diagnostics.AddAttributeError( + v.Path, + fmt.Sprintf("Required %s", v.Path), + fmt.Sprintf("Field %s must have an explicit value", v.Path), + ) + return + } +} diff --git a/util/validate/require_a_if_b_equal.go b/util/validate/require_a_if_b_equal.go new file mode 100644 index 000000000..df03c7d55 --- /dev/null +++ b/util/validate/require_a_if_b_equal.go @@ -0,0 +1,57 @@ +package validate + +import ( + "context" + "fmt" + + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/resource" +) + +// RequireAIfBEqual checks path `a` is not null when path `b` is equal to `expected`. +func RequireAIfBEqual(a, b path.Path, expected attr.Value) resource.ConfigValidator { + return &requireIfEqual{ + dst: a, + src: b, + expected: expected, + } +} + +type requireIfEqual struct { + dst path.Path + src path.Path + expected attr.Value +} + +func (v *requireIfEqual) Description(ctx context.Context) string { return "" } +func (v *requireIfEqual) MarkdownDescription(ctx context.Context) string { return "" } + +func (v *requireIfEqual) ValidateResource(ctx context.Context, req resource.ValidateConfigRequest, resp *resource.ValidateConfigResponse) { + var src attr.Value + resp.Diagnostics.Append(req.Config.GetAttribute(ctx, v.src, &src)...) + if resp.Diagnostics.HasError() { + return + } + + if src.IsNull() || src.IsUnknown() { + return + } + + if src.Equal(v.expected) { + var dst attr.Value + resp.Diagnostics.Append(req.Config.GetAttribute(ctx, v.dst, &dst)...) + if resp.Diagnostics.HasError() { + return + } + + if dst.IsNull() || dst.IsUnknown() { + resp.Diagnostics.AddAttributeError( + v.dst, + fmt.Sprintf("Required %s", v.dst), + fmt.Sprintf("When the value of %s equals %s, field %s must have an explicit value", v.src, v.expected, v.dst), + ) + return + } + } +} diff --git a/util/validate/require_list_size.go b/util/validate/require_list_size.go new file mode 100644 index 000000000..c500c8386 --- /dev/null +++ b/util/validate/require_list_size.go @@ -0,0 +1,39 @@ +package validate + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/resource" +) + +// RequireList checks path `p` is a list at least with size 1. +func RequireList(p path.Path) resource.ConfigValidator { + return &requireListSize{Path: p} +} + +type requireListSize struct { + path.Path +} + +func (v *requireListSize) Description(ctx context.Context) string { return "" } +func (v *requireListSize) MarkdownDescription(ctx context.Context) string { return "" } + +func (v *requireListSize) ValidateResource(ctx context.Context, req resource.ValidateConfigRequest, resp *resource.ValidateConfigResponse) { + var src attr.Value + resp.Diagnostics.Append(req.Config.GetAttribute(ctx, v.Path, &src)...) + if resp.Diagnostics.HasError() { + return + } + + if src.IsNull() || src.IsUnknown() { + return + } + + size := 1 + if size < 1 { + resp.Diagnostics.AddAttributeError(v.Path, "Required to be a list with items", "") + return + } +} diff --git a/util/validate/timezone.go b/util/validate/timezone.go new file mode 100644 index 000000000..53d5ab358 --- /dev/null +++ b/util/validate/timezone.go @@ -0,0 +1,33 @@ +package validate + +import ( + "context" + "fmt" + "time" + + "github.com/PagerDuty/terraform-provider-pagerduty/util" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" +) + +type timezoneValidator struct { + util.StringDescriber +} + +func Timezone() validator.String { + return &timezoneValidator{ + util.StringDescriber{Value: "checks time zone is supported by the machine's tzdata"}, + } +} + +func (v timezoneValidator) ValidateString(ctx context.Context, req validator.StringRequest, resp *validator.StringResponse) { + if req.ConfigValue.IsNull() { + return + } + value := req.ConfigValue.ValueString() + _, err := time.LoadLocation(value) + if err != nil { + resp.Diagnostics.AddAttributeError( + req.Path, fmt.Sprintf("Timezone %q is invalid", value), err.Error(), + ) + } +} diff --git a/util/validator.go b/util/validator.go new file mode 100644 index 000000000..1db899498 --- /dev/null +++ b/util/validator.go @@ -0,0 +1,63 @@ +package util + +import ( + "strings" + "unicode" + + "github.com/hashicorp/go-cty/cty" + v2diag "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +// ValidateIsAllowedString will always validate if string provided is not empty, +// neither has trailing white spaces. Additionally the string content validation +// will be done based on the `mode` set. +// +// mode: NoContentValidation | NoNonPrintableChars | NoNonPrintableCharsOrSpecialChars +func ReValidateIsAllowedString(mode StringContentValidationMode) schema.SchemaValidateDiagFunc { + return func(v interface{}, p cty.Path) v2diag.Diagnostics { + var diags v2diag.Diagnostics + + fillDiags := func() { + summary := "Name can not be blank. Trailing white spaces are not allowed either." + switch mode { + case NoNonPrintableChars: + summary = "Name can not be blank, nor contain non-printable characters. Trailing white spaces are not allowed either." + case NoNonPrintableCharsOrSpecialChars: + summary = "Name can not be blank, nor contain the characters '\\', '/', '&', '<', '>', or any non-printable characters. Trailing white spaces are not allowed either." + } + diags = append(diags, v2diag.Diagnostic{ + Severity: v2diag.Error, + Summary: summary, + AttributePath: p, + }) + } + + value := v.(string) + if value == "" { + fillDiags() + return diags + } + + for _, char := range value { + if (mode == NoNonPrintableChars || mode == NoNonPrintableCharsOrSpecialChars) && !unicode.IsPrint(char) { + fillDiags() + return diags + } + if mode == NoNonPrintableCharsOrSpecialChars { + switch char { + case '\\', '/', '&', '<', '>': + fillDiags() + return diags + } + } + } + + if strings.HasSuffix(value, " ") { + fillDiags() + return diags + } + + return diags + } +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-framework-validators/int64validator/all.go b/vendor/github.com/hashicorp/terraform-plugin-framework-validators/int64validator/all.go new file mode 100644 index 000000000..374bdbeaa --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-framework-validators/int64validator/all.go @@ -0,0 +1,57 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package int64validator + +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.Int64) validator.Int64 { + return allValidator{ + validators: validators, + } +} + +var _ validator.Int64 = allValidator{} + +// allValidator implements the validator. +type allValidator struct { + validators []validator.Int64 +} + +// 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) +} + +// ValidateInt64 performs the validation. +func (v allValidator) ValidateInt64(ctx context.Context, req validator.Int64Request, resp *validator.Int64Response) { + for _, subValidator := range v.validators { + validateResp := &validator.Int64Response{} + + subValidator.ValidateInt64(ctx, req, validateResp) + + resp.Diagnostics.Append(validateResp.Diagnostics...) + } +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-framework-validators/int64validator/also_requires.go b/vendor/github.com/hashicorp/terraform-plugin-framework-validators/int64validator/also_requires.go new file mode 100644 index 000000000..6e6de2780 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-framework-validators/int64validator/also_requires.go @@ -0,0 +1,26 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package int64validator + +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 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 being +// validated. +func AlsoRequires(expressions ...path.Expression) validator.Int64 { + return schemavalidator.AlsoRequiresValidator{ + PathExpressions: expressions, + } +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-framework-validators/int64validator/any.go b/vendor/github.com/hashicorp/terraform-plugin-framework-validators/int64validator/any.go new file mode 100644 index 000000000..a44d082a5 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-framework-validators/int64validator/any.go @@ -0,0 +1,65 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package int64validator + +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.Int64) validator.Int64 { + return anyValidator{ + validators: validators, + } +} + +var _ validator.Int64 = anyValidator{} + +// anyValidator implements the validator. +type anyValidator struct { + validators []validator.Int64 +} + +// 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) +} + +// ValidateInt64 performs the validation. +func (v anyValidator) ValidateInt64(ctx context.Context, req validator.Int64Request, resp *validator.Int64Response) { + for _, subValidator := range v.validators { + validateResp := &validator.Int64Response{} + + subValidator.ValidateInt64(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/int64validator/any_with_all_warnings.go b/vendor/github.com/hashicorp/terraform-plugin-framework-validators/int64validator/any_with_all_warnings.go new file mode 100644 index 000000000..1cd3ee365 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-framework-validators/int64validator/any_with_all_warnings.go @@ -0,0 +1,67 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package int64validator + +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.Int64) validator.Int64 { + return anyWithAllWarningsValidator{ + validators: validators, + } +} + +var _ validator.Int64 = anyWithAllWarningsValidator{} + +// anyWithAllWarningsValidator implements the validator. +type anyWithAllWarningsValidator struct { + validators []validator.Int64 +} + +// 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) +} + +// ValidateInt64 performs the validation. +func (v anyWithAllWarningsValidator) ValidateInt64(ctx context.Context, req validator.Int64Request, resp *validator.Int64Response) { + anyValid := false + + for _, subValidator := range v.validators { + validateResp := &validator.Int64Response{} + + subValidator.ValidateInt64(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/int64validator/at_least.go b/vendor/github.com/hashicorp/terraform-plugin-framework-validators/int64validator/at_least.go new file mode 100644 index 000000000..092a94790 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-framework-validators/int64validator/at_least.go @@ -0,0 +1,58 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package int64validator + +import ( + "context" + "fmt" + + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + + "github.com/hashicorp/terraform-plugin-framework-validators/helpers/validatordiag" +) + +var _ validator.Int64 = atLeastValidator{} + +// atLeastValidator validates that an integer Attribute's value is at least a certain value. +type atLeastValidator struct { + min int64 +} + +// Description describes the validation in plain text formatting. +func (validator atLeastValidator) Description(_ context.Context) string { + return fmt.Sprintf("value must be at least %d", validator.min) +} + +// MarkdownDescription describes the validation in Markdown formatting. +func (validator atLeastValidator) MarkdownDescription(ctx context.Context) string { + return validator.Description(ctx) +} + +// ValidateInt64 performs the validation. +func (v atLeastValidator) ValidateInt64(ctx context.Context, request validator.Int64Request, response *validator.Int64Response) { + if request.ConfigValue.IsNull() || request.ConfigValue.IsUnknown() { + return + } + + if request.ConfigValue.ValueInt64() < v.min { + response.Diagnostics.Append(validatordiag.InvalidAttributeValueDiagnostic( + request.Path, + v.Description(ctx), + fmt.Sprintf("%d", request.ConfigValue.ValueInt64()), + )) + } +} + +// AtLeast returns an AttributeValidator which ensures that any configured +// attribute value: +// +// - Is a number, which can be represented by a 64-bit integer. +// - Is greater than or equal to the given minimum. +// +// Null (unconfigured) and unknown (known after apply) values are skipped. +func AtLeast(min int64) validator.Int64 { + return atLeastValidator{ + min: min, + } +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-framework-validators/int64validator/at_least_one_of.go b/vendor/github.com/hashicorp/terraform-plugin-framework-validators/int64validator/at_least_one_of.go new file mode 100644 index 000000000..cf59b99e4 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-framework-validators/int64validator/at_least_one_of.go @@ -0,0 +1,27 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package int64validator + +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 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 being +// validated. +func AtLeastOneOf(expressions ...path.Expression) validator.Int64 { + return schemavalidator.AtLeastOneOfValidator{ + PathExpressions: expressions, + } +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-framework-validators/int64validator/at_least_sum_of.go b/vendor/github.com/hashicorp/terraform-plugin-framework-validators/int64validator/at_least_sum_of.go new file mode 100644 index 000000000..a8bb10694 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-framework-validators/int64validator/at_least_sum_of.go @@ -0,0 +1,116 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package int64validator + +import ( + "context" + "fmt" + "strings" + + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/tfsdk" + "github.com/hashicorp/terraform-plugin-framework/types" + + "github.com/hashicorp/terraform-plugin-framework-validators/helpers/validatordiag" +) + +var _ validator.Int64 = atLeastSumOfValidator{} + +// atLeastSumOfValidator validates that an integer Attribute's value is at least the sum of one +// or more integer Attributes retrieved via the given path expressions. +type atLeastSumOfValidator struct { + attributesToSumPathExpressions path.Expressions +} + +// Description describes the validation in plain text formatting. +func (av atLeastSumOfValidator) Description(_ context.Context) string { + var attributePaths []string + for _, p := range av.attributesToSumPathExpressions { + attributePaths = append(attributePaths, p.String()) + } + + return fmt.Sprintf("value must be at least sum of %s", strings.Join(attributePaths, " + ")) +} + +// MarkdownDescription describes the validation in Markdown formatting. +func (av atLeastSumOfValidator) MarkdownDescription(ctx context.Context) string { + return av.Description(ctx) +} + +// ValidateInt64 performs the validation. +func (av atLeastSumOfValidator) ValidateInt64(ctx context.Context, request validator.Int64Request, response *validator.Int64Response) { + if request.ConfigValue.IsNull() || request.ConfigValue.IsUnknown() { + return + } + + // Ensure input path expressions resolution against the current attribute + expressions := request.PathExpression.MergeExpressions(av.attributesToSumPathExpressions...) + + // Sum the value of all the attributes involved, but only if they are all known. + var sumOfAttribs int64 + for _, expression := range expressions { + matchedPaths, diags := request.Config.PathMatches(ctx, expression) + response.Diagnostics.Append(diags...) + + // Collect all errors + if diags.HasError() { + continue + } + + for _, mp := range matchedPaths { + // If the user specifies the same attribute this validator is applied to, + // also as part of the input, skip it + if mp.Equal(request.Path) { + continue + } + + // Get the value + var matchedValue attr.Value + diags := request.Config.GetAttribute(ctx, mp, &matchedValue) + response.Diagnostics.Append(diags...) + if diags.HasError() { + continue + } + + if matchedValue.IsUnknown() { + return + } + + if matchedValue.IsNull() { + continue + } + + // We know there is a value, convert it to the expected type + var attribToSum types.Int64 + diags = tfsdk.ValueAs(ctx, matchedValue, &attribToSum) + response.Diagnostics.Append(diags...) + if diags.HasError() { + continue + } + + sumOfAttribs += attribToSum.ValueInt64() + } + } + + if request.ConfigValue.ValueInt64() < sumOfAttribs { + response.Diagnostics.Append(validatordiag.InvalidAttributeValueDiagnostic( + request.Path, + av.Description(ctx), + fmt.Sprintf("%d", request.ConfigValue.ValueInt64()), + )) + } +} + +// AtLeastSumOf returns an AttributeValidator which ensures that any configured +// attribute value: +// +// - Is a number, which can be represented by a 64-bit integer. +// - Is at least the sum of the attributes retrieved via the given path expression(s). +// +// Null (unconfigured) and unknown (known after apply) values are skipped. +func AtLeastSumOf(attributesToSumPathExpressions ...path.Expression) validator.Int64 { + return atLeastSumOfValidator{attributesToSumPathExpressions} +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-framework-validators/int64validator/at_most.go b/vendor/github.com/hashicorp/terraform-plugin-framework-validators/int64validator/at_most.go new file mode 100644 index 000000000..b564a6e5d --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-framework-validators/int64validator/at_most.go @@ -0,0 +1,58 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package int64validator + +import ( + "context" + "fmt" + + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + + "github.com/hashicorp/terraform-plugin-framework-validators/helpers/validatordiag" +) + +var _ validator.Int64 = atMostValidator{} + +// atMostValidator validates that an integer Attribute's value is at most a certain value. +type atMostValidator struct { + max int64 +} + +// Description describes the validation in plain text formatting. +func (validator atMostValidator) Description(_ context.Context) string { + return fmt.Sprintf("value must be at most %d", validator.max) +} + +// MarkdownDescription describes the validation in Markdown formatting. +func (validator atMostValidator) MarkdownDescription(ctx context.Context) string { + return validator.Description(ctx) +} + +// ValidateInt64 performs the validation. +func (v atMostValidator) ValidateInt64(ctx context.Context, request validator.Int64Request, response *validator.Int64Response) { + if request.ConfigValue.IsNull() || request.ConfigValue.IsUnknown() { + return + } + + if request.ConfigValue.ValueInt64() > v.max { + response.Diagnostics.Append(validatordiag.InvalidAttributeValueDiagnostic( + request.Path, + v.Description(ctx), + fmt.Sprintf("%d", request.ConfigValue.ValueInt64()), + )) + } +} + +// AtMost returns an AttributeValidator which ensures that any configured +// attribute value: +// +// - Is a number, which can be represented by a 64-bit integer. +// - Is less than or equal to the given maximum. +// +// Null (unconfigured) and unknown (known after apply) values are skipped. +func AtMost(max int64) validator.Int64 { + return atMostValidator{ + max: max, + } +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-framework-validators/int64validator/at_most_sum_of.go b/vendor/github.com/hashicorp/terraform-plugin-framework-validators/int64validator/at_most_sum_of.go new file mode 100644 index 000000000..cfdf77104 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-framework-validators/int64validator/at_most_sum_of.go @@ -0,0 +1,116 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package int64validator + +import ( + "context" + "fmt" + "strings" + + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/tfsdk" + "github.com/hashicorp/terraform-plugin-framework/types" + + "github.com/hashicorp/terraform-plugin-framework-validators/helpers/validatordiag" +) + +var _ validator.Int64 = atMostSumOfValidator{} + +// atMostSumOfValidator validates that an integer Attribute's value is at most the sum of one +// or more integer Attributes retrieved via the given path expressions. +type atMostSumOfValidator struct { + attributesToSumPathExpressions path.Expressions +} + +// Description describes the validation in plain text formatting. +func (av atMostSumOfValidator) Description(_ context.Context) string { + var attributePaths []string + for _, p := range av.attributesToSumPathExpressions { + attributePaths = append(attributePaths, p.String()) + } + + return fmt.Sprintf("value must be at most sum of %s", strings.Join(attributePaths, " + ")) +} + +// MarkdownDescription describes the validation in Markdown formatting. +func (av atMostSumOfValidator) MarkdownDescription(ctx context.Context) string { + return av.Description(ctx) +} + +// ValidateInt64 performs the validation. +func (av atMostSumOfValidator) ValidateInt64(ctx context.Context, request validator.Int64Request, response *validator.Int64Response) { + if request.ConfigValue.IsNull() || request.ConfigValue.IsUnknown() { + return + } + + // Ensure input path expressions resolution against the current attribute + expressions := request.PathExpression.MergeExpressions(av.attributesToSumPathExpressions...) + + // Sum the value of all the attributes involved, but only if they are all known. + var sumOfAttribs int64 + for _, expression := range expressions { + matchedPaths, diags := request.Config.PathMatches(ctx, expression) + response.Diagnostics.Append(diags...) + + // Collect all errors + if diags.HasError() { + continue + } + + for _, mp := range matchedPaths { + // If the user specifies the same attribute this validator is applied to, + // also as part of the input, skip it + if mp.Equal(request.Path) { + continue + } + + // Get the value + var matchedValue attr.Value + diags := request.Config.GetAttribute(ctx, mp, &matchedValue) + response.Diagnostics.Append(diags...) + if diags.HasError() { + continue + } + + if matchedValue.IsUnknown() { + return + } + + if matchedValue.IsNull() { + continue + } + + // We know there is a value, convert it to the expected type + var attribToSum types.Int64 + diags = tfsdk.ValueAs(ctx, matchedValue, &attribToSum) + response.Diagnostics.Append(diags...) + if diags.HasError() { + continue + } + + sumOfAttribs += attribToSum.ValueInt64() + } + } + + if request.ConfigValue.ValueInt64() > sumOfAttribs { + response.Diagnostics.Append(validatordiag.InvalidAttributeValueDiagnostic( + request.Path, + av.Description(ctx), + fmt.Sprintf("%d", request.ConfigValue.ValueInt64()), + )) + } +} + +// AtMostSumOf returns an AttributeValidator which ensures that any configured +// attribute value: +// +// - Is a number, which can be represented by a 64-bit integer. +// - Is at most the sum of the given attributes retrieved via the given path expression(s). +// +// Null (unconfigured) and unknown (known after apply) values are skipped. +func AtMostSumOf(attributesToSumPathExpressions ...path.Expression) validator.Int64 { + return atMostSumOfValidator{attributesToSumPathExpressions} +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-framework-validators/int64validator/between.go b/vendor/github.com/hashicorp/terraform-plugin-framework-validators/int64validator/between.go new file mode 100644 index 000000000..879aeff01 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-framework-validators/int64validator/between.go @@ -0,0 +1,63 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package int64validator + +import ( + "context" + "fmt" + + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + + "github.com/hashicorp/terraform-plugin-framework-validators/helpers/validatordiag" +) + +var _ validator.Int64 = betweenValidator{} + +// betweenValidator validates that an integer Attribute's value is in a range. +type betweenValidator struct { + min, max int64 +} + +// Description describes the validation in plain text formatting. +func (validator betweenValidator) Description(_ context.Context) string { + return fmt.Sprintf("value must be between %d and %d", validator.min, validator.max) +} + +// MarkdownDescription describes the validation in Markdown formatting. +func (validator betweenValidator) MarkdownDescription(ctx context.Context) string { + return validator.Description(ctx) +} + +// ValidateInt64 performs the validation. +func (v betweenValidator) ValidateInt64(ctx context.Context, request validator.Int64Request, response *validator.Int64Response) { + if request.ConfigValue.IsNull() || request.ConfigValue.IsUnknown() { + return + } + + if request.ConfigValue.ValueInt64() < v.min || request.ConfigValue.ValueInt64() > v.max { + response.Diagnostics.Append(validatordiag.InvalidAttributeValueDiagnostic( + request.Path, + v.Description(ctx), + fmt.Sprintf("%d", request.ConfigValue.ValueInt64()), + )) + } +} + +// Between returns an AttributeValidator which ensures that any configured +// attribute value: +// +// - Is a number, which can be represented by a 64-bit integer. +// - Is greater than or equal to the given minimum and less than or equal to the given maximum. +// +// Null (unconfigured) and unknown (known after apply) values are skipped. +func Between(min, max int64) validator.Int64 { + if min > max { + return nil + } + + return betweenValidator{ + min: min, + max: max, + } +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-framework-validators/int64validator/conflicts_with.go b/vendor/github.com/hashicorp/terraform-plugin-framework-validators/int64validator/conflicts_with.go new file mode 100644 index 000000000..2a73e72a0 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-framework-validators/int64validator/conflicts_with.go @@ -0,0 +1,27 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package int64validator + +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 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 being +// validated. +func ConflictsWith(expressions ...path.Expression) validator.Int64 { + return schemavalidator.ConflictsWithValidator{ + PathExpressions: expressions, + } +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-framework-validators/int64validator/doc.go b/vendor/github.com/hashicorp/terraform-plugin-framework-validators/int64validator/doc.go new file mode 100644 index 000000000..0e65c174a --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-framework-validators/int64validator/doc.go @@ -0,0 +1,5 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +// Package int64validator provides validators for types.Int64 attributes. +package int64validator diff --git a/vendor/github.com/hashicorp/terraform-plugin-framework-validators/int64validator/equal_to_product_of.go b/vendor/github.com/hashicorp/terraform-plugin-framework-validators/int64validator/equal_to_product_of.go new file mode 100644 index 000000000..75c23ec20 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-framework-validators/int64validator/equal_to_product_of.go @@ -0,0 +1,116 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package int64validator + +import ( + "context" + "fmt" + "strings" + + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/tfsdk" + "github.com/hashicorp/terraform-plugin-framework/types" + + "github.com/hashicorp/terraform-plugin-framework-validators/helpers/validatordiag" +) + +var _ validator.Int64 = equalToProductOfValidator{} + +// equalToProductOfValidator validates that an integer Attribute's value equals the product of one +// or more integer Attributes retrieved via the given path expressions. +type equalToProductOfValidator struct { + attributesToMultiplyPathExpressions path.Expressions +} + +// Description describes the validation in plain text formatting. +func (av equalToProductOfValidator) Description(_ context.Context) string { + var attributePaths []string + for _, p := range av.attributesToMultiplyPathExpressions { + attributePaths = append(attributePaths, p.String()) + } + + return fmt.Sprintf("value must be equal to the product of %s", strings.Join(attributePaths, " + ")) +} + +// MarkdownDescription describes the validation in Markdown formatting. +func (av equalToProductOfValidator) MarkdownDescription(ctx context.Context) string { + return av.Description(ctx) +} + +// ValidateInt64 performs the validation. +func (av equalToProductOfValidator) ValidateInt64(ctx context.Context, request validator.Int64Request, response *validator.Int64Response) { + if request.ConfigValue.IsNull() || request.ConfigValue.IsUnknown() { + return + } + + // Ensure input path expressions resolution against the current attribute + expressions := request.PathExpression.MergeExpressions(av.attributesToMultiplyPathExpressions...) + + // Multiply the value of all the attributes involved, but only if they are all known. + productOfAttribs := int64(1) + for _, expression := range expressions { + matchedPaths, diags := request.Config.PathMatches(ctx, expression) + response.Diagnostics.Append(diags...) + + // Collect all errors + if diags.HasError() { + continue + } + + for _, mp := range matchedPaths { + // If the user specifies the same attribute this validator is applied to, + // also as part of the input, skip it + if mp.Equal(request.Path) { + continue + } + + // Get the value + var matchedValue attr.Value + diags := request.Config.GetAttribute(ctx, mp, &matchedValue) + response.Diagnostics.Append(diags...) + if diags.HasError() { + continue + } + + if matchedValue.IsUnknown() { + return + } + + if matchedValue.IsNull() { + return + } + + // We know there is a value, convert it to the expected type + var attribToMultiply types.Int64 + diags = tfsdk.ValueAs(ctx, matchedValue, &attribToMultiply) + response.Diagnostics.Append(diags...) + if diags.HasError() { + continue + } + + productOfAttribs *= attribToMultiply.ValueInt64() + } + } + + if request.ConfigValue.ValueInt64() != productOfAttribs { + response.Diagnostics.Append(validatordiag.InvalidAttributeValueDiagnostic( + request.Path, + av.Description(ctx), + fmt.Sprintf("%d", request.ConfigValue.ValueInt64()), + )) + } +} + +// EqualToProductOf returns an AttributeValidator which ensures that any configured +// attribute value: +// +// - Is a number, which can be represented by a 64-bit integer. +// - Is equal to the product of the given attributes retrieved via the given path expression(s). +// +// Validation is skipped if any null (unconfigured) and/or unknown (known after apply) values are present. +func EqualToProductOf(attributesToMultiplyPathExpressions ...path.Expression) validator.Int64 { + return equalToProductOfValidator{attributesToMultiplyPathExpressions} +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-framework-validators/int64validator/equal_to_sum_of.go b/vendor/github.com/hashicorp/terraform-plugin-framework-validators/int64validator/equal_to_sum_of.go new file mode 100644 index 000000000..cf322c76c --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-framework-validators/int64validator/equal_to_sum_of.go @@ -0,0 +1,116 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package int64validator + +import ( + "context" + "fmt" + "strings" + + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/tfsdk" + "github.com/hashicorp/terraform-plugin-framework/types" + + "github.com/hashicorp/terraform-plugin-framework-validators/helpers/validatordiag" +) + +var _ validator.Int64 = equalToSumOfValidator{} + +// equalToSumOfValidator validates that an integer Attribute's value equals the sum of one +// or more integer Attributes retrieved via the given path expressions. +type equalToSumOfValidator struct { + attributesToSumPathExpressions path.Expressions +} + +// Description describes the validation in plain text formatting. +func (av equalToSumOfValidator) Description(_ context.Context) string { + var attributePaths []string + for _, p := range av.attributesToSumPathExpressions { + attributePaths = append(attributePaths, p.String()) + } + + return fmt.Sprintf("value must be equal to the sum of %s", strings.Join(attributePaths, " + ")) +} + +// MarkdownDescription describes the validation in Markdown formatting. +func (av equalToSumOfValidator) MarkdownDescription(ctx context.Context) string { + return av.Description(ctx) +} + +// ValidateInt64 performs the validation. +func (av equalToSumOfValidator) ValidateInt64(ctx context.Context, request validator.Int64Request, response *validator.Int64Response) { + if request.ConfigValue.IsNull() || request.ConfigValue.IsUnknown() { + return + } + + // Ensure input path expressions resolution against the current attribute + expressions := request.PathExpression.MergeExpressions(av.attributesToSumPathExpressions...) + + // Sum the value of all the attributes involved, but only if they are all known. + var sumOfAttribs int64 + for _, expression := range expressions { + matchedPaths, diags := request.Config.PathMatches(ctx, expression) + response.Diagnostics.Append(diags...) + + // Collect all errors + if diags.HasError() { + continue + } + + for _, mp := range matchedPaths { + // If the user specifies the same attribute this validator is applied to, + // also as part of the input, skip it + if mp.Equal(request.Path) { + continue + } + + // Get the value + var matchedValue attr.Value + diags := request.Config.GetAttribute(ctx, mp, &matchedValue) + response.Diagnostics.Append(diags...) + if diags.HasError() { + continue + } + + if matchedValue.IsUnknown() { + return + } + + if matchedValue.IsNull() { + continue + } + + // We know there is a value, convert it to the expected type + var attribToSum types.Int64 + diags = tfsdk.ValueAs(ctx, matchedValue, &attribToSum) + response.Diagnostics.Append(diags...) + if diags.HasError() { + continue + } + + sumOfAttribs += attribToSum.ValueInt64() + } + } + + if request.ConfigValue.ValueInt64() != sumOfAttribs { + response.Diagnostics.Append(validatordiag.InvalidAttributeValueDiagnostic( + request.Path, + av.Description(ctx), + fmt.Sprintf("%d", request.ConfigValue.ValueInt64()), + )) + } +} + +// EqualToSumOf returns an AttributeValidator which ensures that any configured +// attribute value: +// +// - Is a number, which can be represented by a 64-bit integer. +// - Is equal to the sum of the given attributes retrieved via the given path expression(s). +// +// Null (unconfigured) and unknown (known after apply) values are skipped. +func EqualToSumOf(attributesToSumPathExpressions ...path.Expression) validator.Int64 { + return equalToSumOfValidator{attributesToSumPathExpressions} +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-framework-validators/int64validator/exactly_one_of.go b/vendor/github.com/hashicorp/terraform-plugin-framework-validators/int64validator/exactly_one_of.go new file mode 100644 index 000000000..9edfa4f28 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-framework-validators/int64validator/exactly_one_of.go @@ -0,0 +1,28 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package int64validator + +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 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 being +// validated. +func ExactlyOneOf(expressions ...path.Expression) validator.Int64 { + return schemavalidator.ExactlyOneOfValidator{ + PathExpressions: expressions, + } +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-framework-validators/int64validator/none_of.go b/vendor/github.com/hashicorp/terraform-plugin-framework-validators/int64validator/none_of.go new file mode 100644 index 000000000..749fe5547 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-framework-validators/int64validator/none_of.go @@ -0,0 +1,65 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package int64validator + +import ( + "context" + "fmt" + + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/types" + + "github.com/hashicorp/terraform-plugin-framework-validators/helpers/validatordiag" +) + +var _ validator.Int64 = noneOfValidator{} + +// noneOfValidator validates that the value does not match one of the values. +type noneOfValidator struct { + values []types.Int64 +} + +func (v noneOfValidator) Description(ctx context.Context) string { + return v.MarkdownDescription(ctx) +} + +func (v noneOfValidator) MarkdownDescription(_ context.Context) string { + return fmt.Sprintf("value must be none of: %q", v.values) +} + +func (v noneOfValidator) ValidateInt64(ctx context.Context, request validator.Int64Request, response *validator.Int64Response) { + if request.ConfigValue.IsNull() || request.ConfigValue.IsUnknown() { + return + } + + value := request.ConfigValue + + for _, otherValue := range v.values { + if !value.Equal(otherValue) { + continue + } + + response.Diagnostics.Append(validatordiag.InvalidAttributeValueMatchDiagnostic( + request.Path, + v.Description(ctx), + value.String(), + )) + + break + } +} + +// NoneOf checks that the Int64 held in the attribute +// is none of the given `values`. +func NoneOf(values ...int64) validator.Int64 { + frameworkValues := make([]types.Int64, 0, len(values)) + + for _, value := range values { + frameworkValues = append(frameworkValues, types.Int64Value(value)) + } + + return noneOfValidator{ + values: frameworkValues, + } +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-framework-validators/int64validator/one_of.go b/vendor/github.com/hashicorp/terraform-plugin-framework-validators/int64validator/one_of.go new file mode 100644 index 000000000..3a1e1db48 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-framework-validators/int64validator/one_of.go @@ -0,0 +1,63 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package int64validator + +import ( + "context" + "fmt" + + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/types" + + "github.com/hashicorp/terraform-plugin-framework-validators/helpers/validatordiag" +) + +var _ validator.Int64 = oneOfValidator{} + +// oneOfValidator validates that the value matches one of expected values. +type oneOfValidator struct { + values []types.Int64 +} + +func (v oneOfValidator) Description(ctx context.Context) string { + return v.MarkdownDescription(ctx) +} + +func (v oneOfValidator) MarkdownDescription(_ context.Context) string { + return fmt.Sprintf("value must be one of: %q", v.values) +} + +func (v oneOfValidator) ValidateInt64(ctx context.Context, request validator.Int64Request, response *validator.Int64Response) { + if request.ConfigValue.IsNull() || request.ConfigValue.IsUnknown() { + return + } + + value := request.ConfigValue + + for _, otherValue := range v.values { + if value.Equal(otherValue) { + return + } + } + + response.Diagnostics.Append(validatordiag.InvalidAttributeValueMatchDiagnostic( + request.Path, + v.Description(ctx), + value.String(), + )) +} + +// OneOf checks that the Int64 held in the attribute +// is one of the given `values`. +func OneOf(values ...int64) validator.Int64 { + frameworkValues := make([]types.Int64, 0, len(values)) + + for _, value := range values { + frameworkValues = append(frameworkValues, types.Int64Value(value)) + } + + return oneOfValidator{ + values: frameworkValues, + } +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 414048df9..ea78933a5 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -176,6 +176,7 @@ github.com/hashicorp/terraform-plugin-framework/types/basetypes # github.com/hashicorp/terraform-plugin-framework-validators v0.12.0 ## explicit; go 1.19 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/stringvalidator