From 4a64b822ebda165600599d5af12de326551b4da1 Mon Sep 17 00:00:00 2001 From: Carlos Gajardo Date: Thu, 2 May 2024 22:50:23 -0400 Subject: [PATCH] Migrate resource pagerduty_schedule --- pagerduty/provider.go | 1 + ..._source_pagerduty_extension_schema_test.go | 18 - .../import_pagerduty_schedule_test.go | 53 + .../resource_pagerduty_schedule.go | 993 ++++++++ .../resource_pagerduty_schedule_test.go | 2230 +++++++++++++++++ 5 files changed, 3277 insertions(+), 18 deletions(-) create mode 100644 pagerdutyplugin/import_pagerduty_schedule_test.go create mode 100644 pagerdutyplugin/resource_pagerduty_schedule.go create mode 100644 pagerdutyplugin/resource_pagerduty_schedule_test.go diff --git a/pagerduty/provider.go b/pagerduty/provider.go index d0c38576b..e1f92f8be 100644 --- a/pagerduty/provider.go +++ b/pagerduty/provider.go @@ -152,6 +152,7 @@ func Provider(isMux bool) *schema.Provider { delete(p.DataSourcesMap, "pagerduty_business_service") delete(p.ResourcesMap, "pagerduty_business_service") + delete(p.ResourcesMap, "pagerduty_schedule") } p.ConfigureContextFunc = func(ctx context.Context, d *schema.ResourceData) (interface{}, diag.Diagnostics) { diff --git a/pagerdutyplugin/data_source_pagerduty_extension_schema_test.go b/pagerdutyplugin/data_source_pagerduty_extension_schema_test.go index 55eaaa591..b12739304 100644 --- a/pagerdutyplugin/data_source_pagerduty_extension_schema_test.go +++ b/pagerdutyplugin/data_source_pagerduty_extension_schema_test.go @@ -1,11 +1,9 @@ package pagerduty import ( - "context" "fmt" "testing" - "github.com/PagerDuty/go-pagerduty" "github.com/hashicorp/terraform-plugin-testing/helper/resource" "github.com/hashicorp/terraform-plugin-testing/terraform" ) @@ -56,19 +54,3 @@ data "pagerduty_extension_schema" "foo" { name = "ServiceNow (v7)" } ` - -func testAccCheckPagerDutyScheduleDestroy(s *terraform.State) error { - for _, r := range s.RootModule().Resources { - if r.Type != "pagerduty_schedule" { - continue - } - - ctx := context.Background() - opts := pagerduty.GetScheduleOptions{} - if _, err := testAccProvider.client.GetScheduleWithContext(ctx, r.Primary.ID, opts); err == nil { - return fmt.Errorf("Schedule still exists") - } - - } - return nil -} diff --git a/pagerdutyplugin/import_pagerduty_schedule_test.go b/pagerdutyplugin/import_pagerduty_schedule_test.go new file mode 100644 index 000000000..4a53419aa --- /dev/null +++ b/pagerdutyplugin/import_pagerduty_schedule_test.go @@ -0,0 +1,53 @@ +package pagerduty + +import ( + "context" + "fmt" + "testing" + "time" + + "github.com/PagerDuty/go-pagerduty" + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/terraform" +) + +func TestAccPagerDutySchedule_import(t *testing.T) { + username := fmt.Sprintf("tf-%s", acctest.RandString(5)) + email := fmt.Sprintf("%s@foo.test", username) + schedule := fmt.Sprintf("tf-%s", acctest.RandString(5)) + location := "Europe/Berlin" + t.Setenv(location) + start := testAccTimeNow().Add(24 * time.Hour).Round(1 * time.Hour).Format(time.RFC3339) + rotationVirtualStart := testAccTimeNow().Add(24 * time.Hour).Round(1 * time.Hour).Format(time.RFC3339) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV5ProviderFactories: ProtoV5ProviderFactories(), + CheckDestroy: testAccCheckPagerDutyUserDestroy, + Steps: []resource.TestStep{ + { + Config: testAccCheckPagerDutyScheduleConfig(username, email, schedule, location, start, rotationVirtualStart), + }, + { + ResourceName: "pagerduty_schedule.foo", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccCheckPagerDutyUserDestroy(s *terraform.State) error { + for _, r := range s.RootModule().Resources { + if r.Type != "pagerduty_user" { + continue + } + + ctx := context.Background() + if _, err := testAccProvider.client.GetUserWithContext(ctx, r.Primary.ID, &pagerduty.GetUserOptions{}); err == nil { + return fmt.Errorf("User still exists") + } + } + return nil +} diff --git a/pagerdutyplugin/resource_pagerduty_schedule.go b/pagerdutyplugin/resource_pagerduty_schedule.go new file mode 100644 index 000000000..9595372af --- /dev/null +++ b/pagerdutyplugin/resource_pagerduty_schedule.go @@ -0,0 +1,993 @@ +package pagerduty + +import ( + "context" + "fmt" + "log" + "strconv" + "time" + + "github.com/PagerDuty/go-pagerduty" + "github.com/PagerDuty/terraform-provider-pagerduty/util" + "github.com/hashicorp/terraform-plugin-framework-jsontypes/jsontypes" + "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/stringdefault" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry" +) + +type resourceSchedule struct{ client *pagerduty.Client } + +var ( + _ resource.ResourceWithConfigure = (*resourceSchedule)(nil) + _ resource.ResourceWithImportState = (*resourceSchedule)(nil) +) + +func (r *resourceSchedule) Metadata(_ context.Context, _ resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = "pagerduty_schedule" +} + +func (r *resourceSchedule) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{Computed: true}, + "name": schema.StringAttribute{Optional: true}, + "time_zone": schema.StringAttribute{ + Required: true, + // CustomType: tztypes.String{}, + }, + "overflow": schema.BoolAttribute{Optional: true}, + "description": schema.StringAttribute{ + Default: stringdefault.StaticString("Managed by terraform"), + }, + "layer": schema.ListAttribute{ + Required: true, + ElementType: scheduleLayerObjectType, + }, + }, + } +} + +var scheduleLayerObjectType = types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "id": types.StringType, + "name": types.StringType, + "start": types.StringType, // required, custom validator + /* + ValidateFunc: func(v interface{}, k string) ([]string, []error) { + var errors []error + value := v.(string) + _, err := time.Parse(time.RFC3339, value) + if err != nil { + errors = append(errors, genErrorTimeFormatRFC339(value, k)) + } + + return nil, errors + }, + DiffSuppressFunc: suppressScheduleLayerStartDiff, + */ + "end": jsontypes.NormalizedType, // rfc3339 + "rotation_virtual_start": jsontypes.NormalizedType, // rfc3339 + "rotation_turn_length_seconds": types.Int64Type, // required, validation.IntBetween(3600, 365*24*3600), + "users": types.ListType{ + // required, min 1 + ElemType: types.StringType, + }, + "rendered_coverage_percentage": types.StringType, + "restriction": types.ListType{ + ElemType: scheduleLayerRestrictionObjectType, + }, + "teams": types.ListType{ + ElemType: types.StringType, + }, + "final_schedule": types.ListType{ + ElemType: scheduleFinalScheduleObjectType, + }, + }, +} + +var scheduleLayerRestrictionObjectType = types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "type": types.StringType, // required. "daily_restriction", "weekly_restriction", + "start_time_of_day": types.StringType, // required. validation.StringMatch(regexp.MustCompile(`([0-1][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9]`), "must be of 00:00:00 format"), + "start_day_of_week": types.Int64Type, // required. [1,7] + "duration_seconds": types.Int64Type, // required. [1, 7*24*3600 - 1] + }, +} + +var scheduleFinalScheduleObjectType = types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "name": types.StringType, + "rendered_coverage_percentage": types.StringType, + }, +} + +func (r *resourceSchedule) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var model resourceScheduleModel + + resp.Diagnostics.Append(req.Plan.Get(ctx, &model)...) + if resp.Diagnostics.HasError() { + return + } + plan := buildPagerdutySchedule(&model) + log.Printf("[INFO] Creating PagerDuty schedule %s", plan.Name) + + err := retry.RetryContext(ctx, 2*time.Minute, func() *retry.RetryError { + response, err := r.client.CreateScheduleWithContext(ctx, plan) + if err != nil { + if util.IsBadRequestError(err) { + return retry.NonRetryableError(err) + } + return retry.RetryableError(err) + } + plan.ID = response.ID + return nil + }) + if err != nil { + resp.Diagnostics.AddError( + fmt.Sprintf("Error creating PagerDuty schedule %s", plan.Name), + err.Error(), + ) + return + } + + model, err = requestGetSchedule(ctx, r.client, plan.ID, false, &resp.Diagnostics) + if err != nil { + resp.Diagnostics.AddError( + fmt.Sprintf("Error reading PagerDuty schedule %s", plan.ID), + err.Error(), + ) + return + } + + resp.Diagnostics.Append(resp.State.Set(ctx, &model)...) +} + +func (r *resourceSchedule) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + var id types.String + + resp.Diagnostics.Append(req.State.GetAttribute(ctx, path.Root("id"), &id)...) + if resp.Diagnostics.HasError() { + return + } + log.Printf("[INFO] Reading PagerDuty schedule %s", id) + + state, err := requestGetSchedule(ctx, r.client, id.ValueString(), false, &resp.Diagnostics) + if err != nil { + resp.Diagnostics.AddError( + fmt.Sprintf("Error reading PagerDuty schedule %s", id), + err.Error(), + ) + return + } + resp.Diagnostics.Append(resp.State.Set(ctx, state)...) +} + +func (r *resourceSchedule) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var model resourceScheduleModel + + resp.Diagnostics.Append(req.Plan.Get(ctx, &model)...) + if resp.Diagnostics.HasError() { + return + } + + plan := buildPagerdutySchedule(&model) + if plan.ID == "" { + var id string + req.State.GetAttribute(ctx, path.Root("id"), &id) + plan.ID = id + } + log.Printf("[INFO] Updating PagerDuty schedule %s", plan.ID) + + schedule, err := r.client.UpdateScheduleWithContext(ctx, plan.ID, plan) + if err != nil { + resp.Diagnostics.AddError( + fmt.Sprintf("Error updating PagerDuty schedule %s", plan.ID), + err.Error(), + ) + return + } + model = flattenSchedule(schedule) + + resp.Diagnostics.Append(resp.State.Set(ctx, &model)...) +} + +func (r *resourceSchedule) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + var id types.String + + resp.Diagnostics.Append(req.State.GetAttribute(ctx, path.Root("id"), &id)...) + if resp.Diagnostics.HasError() { + return + } + log.Printf("[INFO] Deleting PagerDuty schedule %s", id) + + err := r.client.DeleteScheduleWithContext(ctx, id.ValueString()) + if err != nil && !util.IsNotFoundError(err) { + resp.Diagnostics.AddError( + fmt.Sprintf("Error deleting PagerDuty schedule %s", id), + err.Error(), + ) + return + } + resp.State.RemoveResource(ctx) +} + +func (r *resourceSchedule) Configure(_ context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { + resp.Diagnostics.Append(ConfigurePagerdutyClient(&r.client, req.ProviderData)...) +} + +func (r *resourceSchedule) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { + resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp) +} + +type resourceScheduleModel struct { + ID types.String `tfsdk:"id"` +} + +func requestGetSchedule(ctx context.Context, client *pagerduty.Client, id string, retryNotFound bool, _ *diag.Diagnostics) (resourceScheduleModel, error) { + var model resourceScheduleModel + + err := retry.RetryContext(ctx, 2*time.Minute, func() *retry.RetryError { + o := pagerduty.GetScheduleOptions{} + schedule, err := client.GetScheduleWithContext(ctx, id, o) + if err != nil { + if util.IsBadRequestError(err) { + return retry.NonRetryableError(err) + } + if !retryNotFound && util.IsNotFoundError(err) { + return retry.NonRetryableError(err) + } + return retry.RetryableError(err) + } + model = flattenSchedule(schedule) + return nil + }) + + return model, err +} + +func buildPagerdutySchedule(model *resourceScheduleModel) pagerduty.Schedule { + schedule := pagerduty.Schedule{ + Name: model.Name.ValueString(), + TimeZone: model.TimeZone.ValueString(), + ScheduleLayers: buildScheduleLayers(model.Layers), + Teams: buildPagerdutyAPIObjectFromIDs(model.Teams, "team_reference", diags), + Description: model.Description.ValueString(), + } + return schedule +} + +func buildScheduleLayers(list types.List, diags *diag.Diagnostics) []ScheduleLayer { + if list.IsNull() || list.IsUnknown() { + return nil + } + + var target []struct { + ID types.String `tfsdk:"id"` + Name types.String `tfsdk:"name"` + Start types.String `tfsdk:"start"` + End types.String `tfsdk:"end"` + RenderedCoveragePercentage types.String `tfsdk:"rendered_coverage_percentage"` + RotationTurnLengthSeconds types.String `tfsdk:"rotation_turn_length_seconds"` + RotationVirtualStart types.String `tfsdk:"rotation_virtual_start"` + FilterSchedule types.List `tfsdk:"filter_schedule"` + Restriction types.List `tfsdk:"restriction"` + Teams types.List `tfsdk:"teams"` + Users types.List `tfsdk:"users"` + } + d := list.ElementsAs(ctx, target, false) + diags.Append(d...) + if d.HasError() { + return nil + } + + scheduleLayers := make([]ScheduleLayer, 0, len(target)) + for _, item := range target { + // This is a temporary fix to prevent getting back the wrong rotation_virtual_start time. + // The background here is that if a user specifies a rotation_virtual_start time to be: + // "2017-09-01T10:00:00+02:00" the API returns back "2017-09-01T12:00:00+02:00". + // With this fix in place, we get the correct rotation_virtual_start time, thus + // eliminating the diff issues we've been seeing in the past. + // This has been confirmed working by PagerDuty support. + rvs, err := util.TimeToUTC(layer.RotationVirtualStart.ValueString()) + if err != nil { + diags.AddAttributeError( + path.Root("rotation_virtual_start"), + "Cannot convert to UTC", + err.Error(), + ) + return nil + } + + rtls, err := strconv.Atoi(item.RotationTurnLengthSeconds.ValueString()) + if err != nil { + diags.AddAttributeError( + path.Root("rotation_turn_length_seconds"), + "Is not a number", err.Error(), + ) + return nil + } + + layer := pagerduty.ScheduleLayer{ + ID: item.ID.ValueString(), + Name: item.Name.ValueString(), + Start: item.Start.ValueString(), + End: item.End.ValueString(), + RotationVirtualStart: rvs.String(), + RotationTurnLengthSeconds: uint(rtls), + // RotationVirtualStart: rvs.Format(time.RFC3339), + } + + userList := buildPagerdutyAPIObjectFromIDs(item.Users, "user", diags) + layer.Users = append(layer.Users, userList...) + + var restrictionList []struct { + Type types.String `tfsdk:"type"` + StartTimeOfDay types.String `tfsdk:"start_time_of_day"` + StartDayOfWeek types.Int64 `tfsdk:"start_day_of_week"` + DurationSeconds types.Int64 `tfsdk:"duration_seconds"` + } + diags.Append(item.Users.ElementsAs(ctx, &restrictionList, false)...) + if diags.HasError() { + return + } + for _, restriction := range restrictionList { + layer.Restrictions = append(layer.Restrictions, pagerduty.Restriction{ + Type: restriction.Type.ValueString(), + StartTimeOfDay: restriction.StartTimeOfDay.ValueString(), + StartDayOfWeek: uint(restriction.StartDayOfWeek.ValueInt64()), + DurationSeconds: uint(restriction.DurationSeconds.ValueInt64()), + }) + } + + scheduleLayers = append(scheduleLayers, layer) + } + + return []pagerduty.ScheduleLayer{} +} + +func buildPagerdutyAPIObjectFromIDs(list types.List, apiType string, diags *diag.Diagnostics) []APIObject { + if list.IsNull() || list.IsUnknown() { + return nil + } + + var target []types.String + diags.Append(item.Users.ElementsAs(ctx, &target, false)...) + if diags.HasError() { + return nil + } + + response := make([]APIObject, 0, len(target)) + for _, id := range target { + response = append(response, pagerduty.APIObject{ + ID: id.ValueString(), + Type: apiType, + }) + } +} + +func flattenSchedule(response *pagerduty.Schedule) resourceScheduleModel { + model := resourceScheduleModel{ + ID: types.StringValue(response.ID), + } + return model +} + +/* +func resourcePagerDutySchedule() *schema.Resource { + return &schema.Resource{ + CustomizeDiff: func(context context.Context, diff *schema.ResourceDiff, i interface{}) error { + ln := diff.Get("layer.#").(int) + for li := 0; li <= ln; li++ { + rn := diff.Get(fmt.Sprintf("layer.%d.restriction.#", li)).(int) + for ri := 0; ri <= rn; ri++ { + t := diff.Get(fmt.Sprintf("layer.%d.restriction.%d.type", li, ri)).(string) + isStartDayOfWeekSetWhenDailyRestrictionType := t == "daily_restriction" && diff.Get(fmt.Sprintf("layer.%d.restriction.%d.start_day_of_week", li, ri)).(int) != 0 + if isStartDayOfWeekSetWhenDailyRestrictionType { + return fmt.Errorf("start_day_of_week must only be set for a weekly_restriction schedule restriction type") + } + isStartDayOfWeekNotSetWhenWeeklyRestrictionType := t == "weekly_restriction" && diff.Get(fmt.Sprintf("layer.%d.restriction.%d.start_day_of_week", li, ri)).(int) == 0 + if isStartDayOfWeekNotSetWhenWeeklyRestrictionType { + return fmt.Errorf("start_day_of_week must be set for a weekly_restriction schedule restriction type") + } + ds := diff.Get(fmt.Sprintf("layer.%d.restriction.%d.duration_seconds", li, ri)).(int) + if t == "daily_restriction" && ds >= 3600*24 { + return fmt.Errorf("duration_seconds for a daily_restriction schedule restriction type must be shorter than a day") + } + } + } + return nil + }, + } +} + +func resourcePagerDutyScheduleCreate(d *schema.ResourceData, meta interface{}) error { + client, err := meta.(*Config).Client() + if err != nil { + return err + } + + schedule, err := buildScheduleStruct(d) + if err != nil { + return err + } + + o := &pagerduty.CreateScheduleOptions{} + + if v, ok := d.GetOk("overflow"); ok { + o.Overflow = v.(bool) + } + + log.Printf("[INFO] Creating PagerDuty schedule: %s", schedule.Name) + + schedule, _, err = client.Schedules.Create(schedule, o) + if err != nil { + return err + } + + d.SetId(schedule.ID) + + return resourcePagerDutyScheduleRead(d, meta) +} + +func fetchSchedule(d *schema.ResourceData, meta interface{}, errCallback func(error, *schema.ResourceData) error) error { + client, err := meta.(*Config).Client() + if err != nil { + return err + } + + retryErr := retry.Retry(2*time.Minute, func() *retry.RetryError { + schedule, _, err := client.Schedules.Get(d.Id(), &pagerduty.GetScheduleOptions{}) + if err != nil { + log.Printf("[WARN] Schedule read error") + if isErrCode(err, http.StatusBadRequest) { + return retry.NonRetryableError(err) + } + + errResp := errCallback(err, d) + if errResp != nil { + time.Sleep(2 * time.Second) + return retry.RetryableError(err) + } + return nil + } + if schedule != nil { + d.Set("name", schedule.Name) + d.Set("time_zone", schedule.TimeZone) + d.Set("description", schedule.Description) + + layers, err := flattenScheduleLayers(schedule.ScheduleLayers) + if err != nil { + return retry.NonRetryableError(err) + } + + if err := d.Set("layer", layers); err != nil { + return retry.NonRetryableError(err) + } + if err := d.Set("teams", flattenShedTeams(schedule.Teams)); err != nil { + return retry.NonRetryableError(fmt.Errorf("error setting teams: %s", err)) + } + if err := d.Set("final_schedule", flattenScheFinalSchedule(schedule.FinalSchedule)); err != nil { + return retry.NonRetryableError(fmt.Errorf("error setting final_schedule: %s", err)) + } + + } + return nil + }) + + if retryErr != nil { + time.Sleep(2 * time.Second) + return retryErr + } + + return nil +} + +func resourcePagerDutyScheduleUpdate(d *schema.ResourceData, meta interface{}) error { + client, err := meta.(*Config).Client() + if err != nil { + return err + } + + schedule, err := buildScheduleStruct(d) + if err != nil { + return err + } + + opts := &pagerduty.UpdateScheduleOptions{} + + if v, ok := d.GetOk("overflow"); ok { + opts.Overflow = v.(bool) + } + + // A schedule layer can never be removed but it can be ended. + // Here we determine which layer has been removed from the configuration + // and we mark it as ended. This is to avoid diff issues. + + if d.HasChange("layer") { + oraw, nraw := d.GetChange("layer") + + osl, err := expandScheduleLayers(oraw.([]interface{})) + if err != nil { + return err + } + + nsl, err := expandScheduleLayers(nraw.([]interface{})) + if err != nil { + return err + } + + // Checks to see if new schedule layers (nsl) include all old schedule layers (osl) + for _, o := range osl { + found := false + for _, n := range nsl { + // layer is found in both nsl and osl + if o.ID == n.ID { + found = true + } + } + + // If layer is not found in new schedule layers (nsl) set end value for layer + if !found { + end, err := timeToUTC(time.Now().Format(time.RFC3339)) + if err != nil { + return err + } + endStr := end.String() + o.End = &endStr + schedule.ScheduleLayers = append(schedule.ScheduleLayers, o) + } + } + } + + log.Printf("[INFO] Updating PagerDuty schedule: %s", d.Id()) + + retryErr := retry.Retry(2*time.Minute, func() *retry.RetryError { + if _, _, err := client.Schedules.Update(d.Id(), schedule, opts); err != nil { + return retry.RetryableError(err) + } + return nil + }) + if retryErr != nil { + time.Sleep(2 * time.Second) + return retryErr + } + + return nil +} + +func resourcePagerDutyScheduleDelete(d *schema.ResourceData, meta interface{}) error { + client, err := meta.(*Config).Client() + if err != nil { + return err + } + scheduleId := d.Id() + + log.Printf("[INFO] Starting deletion process of Schedule %s", scheduleId) + var scheduleData *pagerduty.Schedule + retryErr := retry.Retry(2*time.Minute, func() *retry.RetryError { + resp, _, err := client.Schedules.Get(scheduleId, &pagerduty.GetScheduleOptions{}) + if err != nil { + if isErrCode(err, http.StatusBadRequest) { + return retry.NonRetryableError(err) + } + + time.Sleep(2 * time.Second) + return retry.RetryableError(err) + } + scheduleData = resp + return nil + }) + if retryErr != nil { + return retryErr + } + + log.Printf("[INFO] Listing Escalation Policies that use schedule : %s", scheduleId) + // Extracting Escalation Policies that use this Schedule + epsUsingThisSchedule, err := extractEPsUsingASchedule(client, scheduleData) + if err != nil { + return err + } + + log.Printf("[INFO] Deleting PagerDuty schedule: %s", scheduleId) + // Retrying to give other resources (such as escalation policies) to delete + retryErr = retry.Retry(2*time.Minute, func() *retry.RetryError { + if _, err := client.Schedules.Delete(scheduleId); err != nil { + if !isErrCode(err, 400) { + return retry.RetryableError(err) + } + isErrorScheduleUsedByEP := func(e *pagerduty.Error) bool { + return strings.Compare(fmt.Sprintf("%v", e.Errors), "[Schedule can't be deleted if it's being used by escalation policies]") == 0 + } + isErrorScheduleWOpenIncidents := func(e *pagerduty.Error) bool { + return strings.Compare(fmt.Sprintf("%v", e.Errors), "[Schedule can't be deleted if it's being used by an escalation policy snapshot with open incidents]") == 0 + } + + // Handling of specific http 400 errors from API call DELETE /schedules + e, ok := err.(*pagerduty.Error) + if !ok || !isErrorScheduleUsedByEP(e) && !isErrorScheduleWOpenIncidents(e) { + return retry.NonRetryableError(err) + } + + var workaroundErr error + // An Schedule with open incidents related can't be remove till those + // incidents have been resolved. + linksToIncidentsOpen, workaroundErr := listIncidentsOpenedRelatedToSchedule(client, scheduleData, epsUsingThisSchedule) + if workaroundErr != nil { + err = fmt.Errorf("%v; %w", err, workaroundErr) + return retry.NonRetryableError(err) + } + + hasToShowIncidentRemediationMessage := len(linksToIncidentsOpen) > 0 + if hasToShowIncidentRemediationMessage { + var urlLinksMessage string + for _, incident := range linksToIncidentsOpen { + urlLinksMessage = fmt.Sprintf("%s\n%s", urlLinksMessage, incident) + } + return retry.NonRetryableError(fmt.Errorf("Before destroying Schedule %q You must first resolve or reassign the following incidents related with Escalation Policies using this Schedule... %s", scheduleId, urlLinksMessage)) + } + + // Returning at this point because the open incident (s) blocking the + // deletion of the Schedule can't be tracked. + if isErrorScheduleWOpenIncidents(e) && !hasToShowIncidentRemediationMessage { + return retry.NonRetryableError(e) + } + + epsDataUsingThisSchedule, errFetchingFullEPs := fetchEPsDataUsingASchedule(epsUsingThisSchedule, client) + if errFetchingFullEPs != nil { + err = fmt.Errorf("%v; %w", err, errFetchingFullEPs) + return retry.RetryableError(err) + } + + errBlockingBecauseOfEPs := detectUseOfScheduleByEPsWithOneLayer(scheduleId, epsDataUsingThisSchedule) + if errBlockingBecauseOfEPs != nil { + return retry.NonRetryableError(errBlockingBecauseOfEPs) + } + + // Workaround for Schedule being used by escalation policies error + log.Printf("[INFO] Dissociating Escalation Policies that use the Schedule: %s", scheduleId) + workaroundErr = dissociateScheduleFromEPs(client, scheduleId, epsDataUsingThisSchedule) + if workaroundErr != nil { + err = fmt.Errorf("%v; %w", err, workaroundErr) + } + return retry.RetryableError(err) + } + return nil + }) + if retryErr != nil { + time.Sleep(2 * time.Second) + return retryErr + } + + d.SetId("") + + return nil +} + +func flattenScheduleLayers(v []*pagerduty.ScheduleLayer) ([]map[string]interface{}, error) { + var scheduleLayers []map[string]interface{} + + for _, sl := range v { + // A schedule layer can never be removed but it can be ended. + // Here we check each layer and if it has been ended we don't read it back + // because it's not relevant anymore. + endStr := stringPtrToStringType(sl.End) + if endStr != "" { + end, err := timeToUTC(endStr) + if err != nil { + return nil, err + } + + if time.Now().UTC().After(end) { + continue + } + } + scheduleLayer := map[string]interface{}{ + "id": sl.ID, + "name": sl.Name, + "end": endStr, + "start": sl.Start, + "rotation_virtual_start": sl.RotationVirtualStart, + "rotation_turn_length_seconds": sl.RotationTurnLengthSeconds, + "rendered_coverage_percentage": renderRoundedPercentage(sl.RenderedCoveragePercentage), + } + + var users []string + + for _, slu := range sl.Users { + users = append(users, slu.User.ID) + } + + scheduleLayer["users"] = users + + var restrictions []map[string]interface{} + + for _, slr := range sl.Restrictions { + restriction := map[string]interface{}{ + "duration_seconds": slr.DurationSeconds, + "start_time_of_day": slr.StartTimeOfDay, + "type": slr.Type, + } + + if slr.StartDayOfWeek > 0 { + restriction["start_day_of_week"] = slr.StartDayOfWeek + } + + restrictions = append(restrictions, restriction) + } + + scheduleLayer["restriction"] = restrictions + + scheduleLayers = append(scheduleLayers, scheduleLayer) + } + + // Reverse the final result and return it + resultReversed := make([]map[string]interface{}, 0, len(scheduleLayers)) + + for i := len(scheduleLayers) - 1; i >= 0; i-- { + resultReversed = append(resultReversed, scheduleLayers[i]) + } + + return resultReversed, nil +} + +// the expandShedTeams and flattenSchedTeams are based on the expandTeams and flattenTeams functions in the user +// resource. added these functions here for maintainability +func expandSchedTeams(v interface{}) []*pagerduty.TeamReference { + var teams []*pagerduty.TeamReference + + for _, t := range v.([]interface{}) { + team := &pagerduty.TeamReference{ + ID: t.(string), + Type: "team_reference", + } + teams = append(teams, team) + } + + return teams +} + +func flattenShedTeams(teams []*pagerduty.TeamReference) []string { + res := make([]string, len(teams)) + for i, t := range teams { + res[i] = t.ID + } + + return res +} + +func flattenScheFinalSchedule(finalSche *pagerduty.SubSchedule) []map[string]interface{} { + var res []map[string]interface{} + elem := make(map[string]interface{}) + elem["name"] = finalSche.Name + elem["rendered_coverage_percentage"] = renderRoundedPercentage(finalSche.RenderedCoveragePercentage) + res = append(res, elem) + + return res +} + +func listIncidentsOpenedRelatedToSchedule(c *pagerduty.Client, schedule *pagerduty.Schedule, epIDs []string) ([]string, error) { + var incidents []*pagerduty.Incident + retryErr := retry.Retry(2*time.Minute, func() *retry.RetryError { + var err error + options := &pagerduty.ListIncidentsOptions{ + DateRange: "all", + Statuses: []string{"triggered", "acknowledged"}, + Limit: 100, + } + if len(schedule.Users) > 0 { + for _, u := range schedule.Users { + options.UserIDs = append(options.UserIDs, u.ID) + } + } + + incidents, err = c.Incidents.ListAll(options) + if err != nil { + if isErrCode(err, http.StatusBadRequest) { + return retry.NonRetryableError(err) + } + + time.Sleep(2 * time.Second) + return retry.RetryableError(err) + } + return nil + }) + if retryErr != nil { + return nil, retryErr + } + + filterIncidentsByEPs := func(incidents []*pagerduty.Incident, eps []string) []*pagerduty.Incident { + var r []*pagerduty.Incident + + matchIndex := make(map[string]bool) + for _, ep := range eps { + matchIndex[ep] = true + } + for _, inc := range incidents { + if matchIndex[inc.EscalationPolicy.ID] { + r = append(r, inc) + } + } + return r + } + incidents = filterIncidentsByEPs(incidents, epIDs) + + var linksToIncidents []string + for _, inc := range incidents { + linksToIncidents = append(linksToIncidents, inc.HTMLURL) + } + return linksToIncidents, nil +} + +func extractEPsUsingASchedule(c *pagerduty.Client, schedule *pagerduty.Schedule) ([]string, error) { + eps := []string{} + for _, ep := range schedule.EscalationPolicies { + eps = append(eps, ep.ID) + } + return eps, nil +} + +func dissociateScheduleFromEPs(c *pagerduty.Client, scheduleID string, eps []*pagerduty.EscalationPolicy) error { + for _, ep := range eps { + errorMessage := fmt.Sprintf("Error while trying to dissociate Schedule %q from Escalation Policy %q", scheduleID, ep.ID) + err := removeScheduleFromEP(c, scheduleID, ep) + if err != nil { + return fmt.Errorf("%w; %s", err, errorMessage) + } + } + + return nil +} + +func removeScheduleFromEP(c *pagerduty.Client, scheduleID string, ep *pagerduty.EscalationPolicy) error { + needsToUpdate := false + epr := ep.EscalationRules + // If the Escalation Policy using this Schedule has only one layer then this + // workaround isn't applicable. + if len(epr) < 2 { + return nil + } + + for ri, r := range epr { + for index, target := range r.Targets { + isScheduleConfiguredInEscalationRule := target.Type == "schedule_reference" && target.ID == scheduleID + if !isScheduleConfiguredInEscalationRule { + continue + } + + if len(r.Targets) > 1 { + // Removing Schedule as a configured Target from the Escalation Rules + // slice. + r.Targets = append(r.Targets[:index], r.Targets[index+1:]...) + } else { + // Removing Escalation Rules that will end up having no target configured. + isLastRule := ri == len(epr)-1 + if isLastRule { + epr = epr[:ri] + } else { + epr = append(epr[:ri], epr[ri+1:]...) + } + } + needsToUpdate = true + } + } + if !needsToUpdate { + return nil + } + ep.EscalationRules = epr + + retryErr := retry.Retry(2*time.Minute, func() *retry.RetryError { + _, _, err := c.EscalationPolicies.Update(ep.ID, ep) + if err != nil { + if !isErrCode(err, 404) { + return retry.RetryableError(err) + } + } + return nil + }) + if retryErr != nil { + return retryErr + } + + return nil +} + +func detectUseOfScheduleByEPsWithOneLayer(scheduleId string, eps []*pagerduty.EscalationPolicy) error { + epsFound := []*pagerduty.EscalationPolicy{} + for _, ep := range eps { + epHasNoLayers := len(ep.EscalationRules) == 0 + if epHasNoLayers { + continue + } + + epHasOneLayer := len(ep.EscalationRules) == 1 && len(ep.EscalationRules[0].Targets) == 1 + epHasMultipleLayersButAllTargetThisSchedule := func() bool { + var meetCondition bool + if len(ep.EscalationRules) == 1 { + return meetCondition + } + meetConditionMapping := make(map[int]bool) + for epli, epLayer := range ep.EscalationRules { + meetConditionMapping[epli] = false + isTargetingThisSchedule := epLayer.Targets[0].Type == "schedule_reference" && epLayer.Targets[0].ID == scheduleId + if len(epLayer.Targets) == 1 && isTargetingThisSchedule { + meetConditionMapping[epli] = true + } + } + for _, mc := range meetConditionMapping { + if !mc { + meetCondition = false + break + } + meetCondition = true + } + + return meetCondition + } + + if !epHasOneLayer && !epHasMultipleLayersButAllTargetThisSchedule() { + continue + } + epsFound = append(epsFound, ep) + } + + if len(epsFound) == 0 { + return nil + } + + tfState, err := getTFStateSnapshot() + if err != nil { + return err + } + + epsNames := []string{} + for _, ep := range epsFound { + epState := tfState.GetResourceStateById(ep.ID) + + // To cover the case when the Schedule is used by an Escalation Policy which + // is not being managed by the same TF config which is managing this Schedule. + if epState == nil { + return fmt.Errorf("It is not possible to continue with the destruction of the Schedule %q, because it is being used by Escalation Policy %q which has only one layer configured. Nevertheless, the mentioned Escalation Policy is not managed by this Terraform configuration. So in order to unblock this resource destruction, We suggest you to first make the appropiate changes on the Escalation Policy %s and come back for retrying.", scheduleId, ep.ID, ep.HTMLURL) + } + epsNames = append(epsNames, epState.Name) + } + + displayError := fmt.Errorf(`It is not possible to continue with the destruction of the Schedule %q, because it is being used by the Escalation Policy %[2]q which has only one layer configured. Therefore in order to unblock this resource destruction, We suggest you to first execute "terraform apply (or destroy, please act accordingly) -target=pagerduty_escalation_policy.%[2]s"`, scheduleId, epsNames[0]) + if len(epsNames) > 1 { + var epsListMessage string + for _, ep := range epsNames { + epsListMessage = fmt.Sprintf("%s\n%s", epsListMessage, ep) + } + displayError = fmt.Errorf(`It is not possible to continue with the destruction of the Schedule %q, because it is being used by multiple Escalation Policies which have only one layer configured. Therefore in order to unblock this resource destruction, We suggest you to first execute "terraform apply (or destroy, please act accordingly) -target=pagerduty_escalation_policy.". e.g: "terraform apply -target=pagerduty_escalation_policy.example". Replacing the example name with the following Escalation Policies which are blocking the deletion of the Schedule...%s`, scheduleId, epsListMessage) + } + + return displayError +} + +func fetchEPsDataUsingASchedule(eps []string, c *pagerduty.Client) ([]*pagerduty.EscalationPolicy, error) { + fullEPs := []*pagerduty.EscalationPolicy{} + for _, epID := range eps { + retryErr := retry.Retry(2*time.Minute, func() *retry.RetryError { + ep, _, err := c.EscalationPolicies.Get(epID, &pagerduty.GetEscalationPolicyOptions{}) + if err != nil { + if isErrCode(err, http.StatusBadRequest) { + return retry.NonRetryableError(err) + } + + return retry.RetryableError(err) + } + fullEPs = append(fullEPs, ep) + return nil + }) + if retryErr != nil { + return fullEPs, retryErr + } + } + + return fullEPs, nil +} +*/ diff --git a/pagerdutyplugin/resource_pagerduty_schedule_test.go b/pagerdutyplugin/resource_pagerduty_schedule_test.go new file mode 100644 index 000000000..fd85560e9 --- /dev/null +++ b/pagerdutyplugin/resource_pagerduty_schedule_test.go @@ -0,0 +1,2230 @@ +package pagerduty + +import ( + "context" + "fmt" + "log" + "os" + "regexp" + "strings" + "testing" + "time" + + "github.com/PagerDuty/go-pagerduty" + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/terraform" +) + +func init() { + resource.AddTestSweepers("pagerduty_schedule", &resource.Sweeper{ + Name: "pagerduty_schedule", + F: testSweepSchedule, + }) +} + +func testSweepSchedule(_ string) error { + ctx := context.Background() + resp, err := testAccProvider.client.ListSchedulesWithContext(ctx, pagerduty.ListSchedulesOptions{}) + if err != nil { + return err + } + + for _, schedule := range resp.Schedules { + if strings.HasPrefix(schedule.Name, "test") || strings.HasPrefix(schedule.Name, "tf-") { + log.Printf("Destroying schedule %s (%s)", schedule.Name, schedule.ID) + if err := testAccProvider.client.DeleteScheduleWithContext(ctx, schedule.ID); err != nil { + return err + } + } + } + + return nil +} + +func TestAccPagerDutySchedule_Basic(t *testing.T) { + username := fmt.Sprintf("tf-%s", acctest.RandString(5)) + email := fmt.Sprintf("%s@foo.test", username) + schedule := fmt.Sprintf("tf-%s", acctest.RandString(5)) + scheduleUpdated := fmt.Sprintf("tf-%s", acctest.RandString(5)) + location := "America/New_York" + t.Setenv("PAGERDUTY_TIME_ZONE", location) + start := testAccTimeNow().Add(24 * time.Hour).Round(1 * time.Hour).Format(time.RFC3339) + startWrongFormated := testAccTimeNow().Add(24 * time.Hour).Round(1 * time.Hour).Format(time.RFC1123) + startNotRounded := testAccTimeNow().Add(24 * time.Hour).Round(1 * time.Hour).Add(5 * time.Second).Format(time.RFC3339) + rotationVirtualStart := testAccTimeNow().Add(24 * time.Hour).Round(1 * time.Hour).Format(time.RFC3339) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV5ProviderFactories: testAccProtoV5ProviderFactories(), + CheckDestroy: testAccCheckPagerDutyScheduleDestroy, + Steps: []resource.TestStep{ + { + Config: testAccCheckPagerDutyScheduleConfig(username, email, schedule, location, start, rotationVirtualStart), + Check: resource.ComposeTestCheckFunc( + testAccCheckPagerDutyScheduleExists("pagerduty_schedule.foo"), + resource.TestCheckResourceAttr( + "pagerduty_schedule.foo", "name", schedule), + resource.TestCheckResourceAttr( + "pagerduty_schedule.foo", "description", "foo"), + resource.TestCheckResourceAttr( + "pagerduty_schedule.foo", "time_zone", location), + resource.TestCheckResourceAttr( + "pagerduty_schedule.foo", "layer.#", "1"), + resource.TestCheckResourceAttr( + "pagerduty_schedule.foo", "layer.0.name", "foo"), + resource.TestCheckResourceAttr( + "pagerduty_schedule.foo", "layer.0.start", start), + resource.TestCheckResourceAttr( + "pagerduty_schedule.foo", "layer.0.rendered_coverage_percentage", "0.00"), + resource.TestCheckResourceAttr( + "pagerduty_schedule.foo", "final_schedule.0.rendered_coverage_percentage", "0.00"), + resource.TestCheckResourceAttr( + "pagerduty_schedule.foo", "layer.0.rotation_virtual_start", rotationVirtualStart), + ), + }, + { + Config: testAccCheckPagerDutyScheduleConfigUpdated(username, email, scheduleUpdated, location, start, rotationVirtualStart), + Check: resource.ComposeTestCheckFunc( + testAccCheckPagerDutyScheduleExists("pagerduty_schedule.foo"), + resource.TestCheckResourceAttr( + "pagerduty_schedule.foo", "name", scheduleUpdated), + resource.TestCheckResourceAttr( + "pagerduty_schedule.foo", "description", "Managed by Terraform"), + resource.TestCheckResourceAttr( + "pagerduty_schedule.foo", "time_zone", location), + resource.TestCheckResourceAttr( + "pagerduty_schedule.foo", "layer.#", "1"), + resource.TestCheckResourceAttr( + "pagerduty_schedule.foo", "layer.0.name", "foo"), + resource.TestCheckResourceAttr( + "pagerduty_schedule.foo", "layer.0.start", start), + resource.TestCheckResourceAttr( + "pagerduty_schedule.foo", "layer.0.rotation_virtual_start", rotationVirtualStart), + ), + }, + { + Config: testAccCheckPagerDutyScheduleConfigRestrictionType(username, email, schedule, location, start, rotationVirtualStart), + ExpectError: regexp.MustCompile("start_day_of_week must only be set for a weekly_restriction schedule restriction type"), + }, + // Validating that a Weekly Restriction with no Start Day of Week set + // returns a format error. + { + Config: testAccCheckPagerDutyScheduleConfigRestrictionTypeWeeklyWithoutStartDayOfWeekSet(username, email, schedule, location, start, rotationVirtualStart), + PlanOnly: true, + ExpectError: regexp.MustCompile("start_day_of_week must be set for a weekly_restriction schedule restriction type"), + }, + // Validating that wrong formatted values for "start" attribute return a + // format error. + { + Config: testAccCheckPagerDutyScheduleConfig(username, email, schedule, location, startWrongFormated, rotationVirtualStart), + ExpectError: regexp.MustCompile("is not a valid format for argument:"), + }, + // Validating that dates not minute rounded for "start" attribute are + // acepted. + { + Config: testAccCheckPagerDutyScheduleConfig(username, email, schedule, location, startNotRounded, rotationVirtualStart), + Check: resource.ComposeTestCheckFunc( + testAccCheckPagerDutyScheduleExists("pagerduty_schedule.foo"), + resource.TestCheckResourceAttr( + "pagerduty_schedule.foo", "layer.0.start", startNotRounded), + ), + }, + }, + }) +} + +func TestAccPagerDutyScheduleWithTeams_Basic(t *testing.T) { + username := fmt.Sprintf("tf-%s", acctest.RandString(5)) + email := fmt.Sprintf("%s@foo.test", username) + schedule := fmt.Sprintf("tf-%s", acctest.RandString(5)) + scheduleUpdated := fmt.Sprintf("tf-%s", acctest.RandString(5)) + location := "America/New_York" + t.Setenv("PAGERDUTY_TIME_ZONE", location) + start := testAccTimeNow().Add(24 * time.Hour).Round(1 * time.Hour).Format(time.RFC3339) + rotationVirtualStart := testAccTimeNow().Add(24 * time.Hour).Round(1 * time.Hour).Format(time.RFC3339) + team := fmt.Sprintf("tf-%s", acctest.RandString(5)) + teamUpdated := fmt.Sprintf("tf-%s", acctest.RandString(5)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV5ProviderFactories: testAccProtoV5ProviderFactories(), + CheckDestroy: testAccCheckPagerDutyScheduleDestroy, + Steps: []resource.TestStep{ + { + Config: testAccCheckPagerDutyScheduleWithTeamsConfig(username, email, schedule, location, start, rotationVirtualStart, team), + Check: resource.ComposeTestCheckFunc( + testAccCheckPagerDutyScheduleExists("pagerduty_schedule.foo"), + resource.TestCheckResourceAttr( + "pagerduty_schedule.foo", "name", schedule), + resource.TestCheckResourceAttr( + "pagerduty_schedule.foo", "description", "foo"), + resource.TestCheckResourceAttr( + "pagerduty_schedule.foo", "time_zone", location), + resource.TestCheckResourceAttr( + "pagerduty_schedule.foo", "layer.#", "1"), + resource.TestCheckResourceAttr( + "pagerduty_schedule.foo", "layer.0.name", "foo"), + resource.TestCheckResourceAttr( + "pagerduty_schedule.foo", "layer.0.start", start), + resource.TestCheckResourceAttr( + "pagerduty_schedule.foo", "layer.0.rotation_virtual_start", rotationVirtualStart), + resource.TestCheckResourceAttr( + "pagerduty_schedule.foo", "teams.#", "1"), + ), + }, + { + Config: testAccCheckPagerDutyScheduleWithTeamsConfigUpdated(username, email, scheduleUpdated, location, start, rotationVirtualStart, teamUpdated), + Check: resource.ComposeTestCheckFunc( + testAccCheckPagerDutyScheduleExists("pagerduty_schedule.foo"), + resource.TestCheckResourceAttr( + "pagerduty_schedule.foo", "name", scheduleUpdated), + resource.TestCheckResourceAttr( + "pagerduty_schedule.foo", "description", "Managed by Terraform"), + resource.TestCheckResourceAttr( + "pagerduty_schedule.foo", "time_zone", location), + resource.TestCheckResourceAttr( + "pagerduty_schedule.foo", "layer.#", "1"), + resource.TestCheckResourceAttr( + "pagerduty_schedule.foo", "layer.0.name", "foo"), + resource.TestCheckResourceAttr( + "pagerduty_schedule.foo", "layer.0.start", start), + resource.TestCheckResourceAttr( + "pagerduty_schedule.foo", "layer.0.rotation_virtual_start", rotationVirtualStart), + resource.TestCheckResourceAttr( + "pagerduty_schedule.foo", "teams.#", "1"), + ), + }, + }, + }) +} + +func TestAccPagerDutySchedule_BasicWithExternalDestroyHandling(t *testing.T) { + username := fmt.Sprintf("tf-%s", acctest.RandString(5)) + email := fmt.Sprintf("%s@foo.test", username) + schedule := fmt.Sprintf("tf-%s", acctest.RandString(5)) + location := "America/New_York" + t.Setenv("PAGERDUTY_TIME_ZONE", location) + start := testAccTimeNow().Add(24 * time.Hour).Round(1 * time.Hour).Format(time.RFC3339) + rotationVirtualStart := testAccTimeNow().Add(24 * time.Hour).Round(1 * time.Hour).Format(time.RFC3339) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV5ProviderFactories: testAccProtoV5ProviderFactories(), + CheckDestroy: testAccCheckPagerDutyScheduleDestroy, + Steps: []resource.TestStep{ + { + Config: testAccCheckPagerDutyScheduleConfig(username, email, schedule, location, start, rotationVirtualStart), + Check: resource.ComposeTestCheckFunc( + testAccCheckPagerDutyScheduleExists("pagerduty_schedule.foo"), + ), + }, + // Validating that externally removed schedule are detected and planed for + // re-creation + { + Config: testAccCheckPagerDutyScheduleConfig(username, email, schedule, location, start, rotationVirtualStart), + Check: resource.ComposeTestCheckFunc( + testAccExternallyDestroySchedule("pagerduty_schedule.foo"), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +func TestAccPagerDutyScheduleWithTeams_EscalationPolicyDependant(t *testing.T) { + username := fmt.Sprintf("tf-%s", acctest.RandString(5)) + email := fmt.Sprintf("%s@foo.test", username) + schedule := fmt.Sprintf("tf-%s", acctest.RandString(5)) + location := "America/New_York" + t.Setenv("PAGERDUTY_TIME_ZONE", location) + start := testAccTimeNow().Add(24 * time.Hour).Round(1 * time.Hour).Format(time.RFC3339) + rotationVirtualStart := testAccTimeNow().Add(24 * time.Hour).Round(1 * time.Hour).Format(time.RFC3339) + team := fmt.Sprintf("tf-%s", acctest.RandString(5)) + escalationPolicy := fmt.Sprintf("ts-%s", acctest.RandString(5)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV5ProviderFactories: testAccProtoV5ProviderFactories(), + CheckDestroy: testAccCheckPagerDutyScheduleDestroy, + Steps: []resource.TestStep{ + { + Config: testAccCheckPagerDutyScheduleWithTeamsEscalationPolicyDependantConfig(username, email, schedule, location, start, rotationVirtualStart, team, escalationPolicy), + Check: resource.ComposeTestCheckFunc( + testAccCheckPagerDutyScheduleExists("pagerduty_schedule.foo"), + resource.TestCheckResourceAttr( + "pagerduty_schedule.foo", "name", schedule), + resource.TestCheckResourceAttr( + "pagerduty_schedule.foo", "description", "foo"), + resource.TestCheckResourceAttr( + "pagerduty_escalation_policy.foo", "name", escalationPolicy), + ), + }, + { + Config: testAccCheckPagerDutyScheduleWithTeamsEscalationPolicyDependantConfigUpdated(username, email, team, escalationPolicy), + Check: resource.ComposeTestCheckFunc( + testAccCheckPagerDutyScheduleNoExists("pagerduty_schedule.foo"), + resource.TestCheckResourceAttr( + "pagerduty_escalation_policy.foo", "name", escalationPolicy), + ), + }, + }, + }) +} + +func TestAccPagerDutyScheduleWithTeams_EscalationPolicyDependantWithOneLayer(t *testing.T) { + username := fmt.Sprintf("tf-%s", acctest.RandString(5)) + email := fmt.Sprintf("%s@foo.test", username) + schedule := fmt.Sprintf("tf-%s", acctest.RandString(5)) + location := "America/New_York" + t.Setenv("PAGERDUTY_TIME_ZONE", location) + start := testAccTimeNow().Add(24 * time.Hour).Round(1 * time.Hour).Format(time.RFC3339) + rotationVirtualStart := testAccTimeNow().Add(24 * time.Hour).Round(1 * time.Hour).Format(time.RFC3339) + team := fmt.Sprintf("tf-%s", acctest.RandString(5)) + escalationPolicy1 := fmt.Sprintf("ts-%s", acctest.RandString(5)) + escalationPolicy2 := fmt.Sprintf("ts-%s", acctest.RandString(5)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { + testAccPreCheck(t) + testAccPreCheckScheduleUsedByEPWithOneLayer(t) + }, + ProtoV5ProviderFactories: testAccProtoV5ProviderFactories(), + CheckDestroy: testAccCheckPagerDutyScheduleDestroy, + Steps: []resource.TestStep{ + { + Config: testAccCheckPagerDutyScheduleWithTeamsEscalationPolicyDependantWithOneLayerConfig(username, email, schedule, location, start, rotationVirtualStart, team, escalationPolicy1), + Check: resource.ComposeTestCheckFunc( + testAccCheckPagerDutyScheduleExists("pagerduty_schedule.foo"), + resource.TestCheckResourceAttr( + "pagerduty_schedule.foo", "name", schedule), + resource.TestCheckResourceAttr( + "pagerduty_schedule.foo", "description", "foo"), + resource.TestCheckResourceAttr( + "pagerduty_escalation_policy.foo", "name", escalationPolicy1), + ), + }, + // Validating that deleting a Schedule used by an Escalation Policy with + // one configured layer prompts the expected error. + { + Config: testAccCheckPagerDutyScheduleWithTeamsEscalationPolicyDependantConfigUpdated(username, email, team, escalationPolicy1), + ExpectError: regexp.MustCompile("It is not possible to continue with the destruction of the Schedule \".*\", because it is being used by the Escalation Policy \".*\" which has only one layer configured"), + }, + { + Config: testAccCheckPagerDutyScheduleWithTeamsEscalationPolicyDependantWithMultipleLayersUsingTheSameScheduleAsTargetConfig(username, email, schedule, location, start, rotationVirtualStart, team, escalationPolicy1), + Check: resource.ComposeTestCheckFunc( + testAccCheckPagerDutyScheduleExists("pagerduty_schedule.foo"), + resource.TestCheckResourceAttr( + "pagerduty_schedule.foo", "name", schedule), + resource.TestCheckResourceAttr( + "pagerduty_schedule.foo", "description", "foo"), + resource.TestCheckResourceAttr( + "pagerduty_escalation_policy.foo", "name", escalationPolicy1), + ), + }, + // Validating that deleting a Schedule used by an Escalation Policy with + // multiple configured layer but each layer has configured only the + // Schedule try to be deleted + { + Config: testAccCheckPagerDutyScheduleWithTeamsEscalationPolicyDependantWithMultipleLayersUsingTheSameScheduleAsTargetConfigUpdated(username, email, team, escalationPolicy1), + ExpectError: regexp.MustCompile("It is not possible to continue with the destruction of the Schedule \".*\", because it is being used by the Escalation Policy \".*\" which has only one layer configured"), + }, + { + Config: testAccCheckPagerDutyScheduleWithTeamsEscalationPolicyDependantMultipleWithOneLayerConfig(username, email, schedule, location, start, rotationVirtualStart, team, escalationPolicy1, escalationPolicy2), + Check: resource.ComposeTestCheckFunc( + testAccCheckPagerDutyScheduleExists("pagerduty_schedule.foo"), + resource.TestCheckResourceAttr( + "pagerduty_schedule.foo", "name", schedule), + resource.TestCheckResourceAttr( + "pagerduty_schedule.foo", "description", "foo"), + resource.TestCheckResourceAttr( + "pagerduty_escalation_policy.foo", "name", escalationPolicy1), + ), + }, + // Validation that deleting a Schedule used by multiple Escalation + // Policies with one configured layer prompts the expected error. + { + Config: testAccCheckPagerDutyScheduleWithTeamsEscalationPolicyDependantMultipleWithOneLayerConfigUpdated(username, email, team, escalationPolicy1, escalationPolicy2), + ExpectError: regexp.MustCompile("It is not possible to continue with the destruction of the Schedule \".*\", because it is being used by multiple Escalation Policies which have only one layer configured."), + }, + }, + }) +} + +func TestAccPagerDutyScheduleWithTeams_EscalationPolicyDependantWithOpenIncidents(t *testing.T) { + service1 := fmt.Sprintf("tf-%s", acctest.RandString(5)) + service2 := fmt.Sprintf("tf-%s", acctest.RandString(5)) + username := fmt.Sprintf("tf-%s", acctest.RandString(5)) + email := fmt.Sprintf("%s@foo.test", username) + schedule := fmt.Sprintf("tf-%s", acctest.RandString(5)) + location := "America/New_York" + t.Setenv("PAGERDUTY_TIME_ZONE", location) + start := testAccTimeNow().Add(24 * time.Hour).Round(1 * time.Hour).Format(time.RFC3339) + rotationVirtualStart := testAccTimeNow().Add(24 * time.Hour).Round(1 * time.Hour).Format(time.RFC3339) + team := fmt.Sprintf("tf-%s", acctest.RandString(5)) + escalationPolicy1 := fmt.Sprintf("ts-%s", acctest.RandString(5)) + escalationPolicy2 := fmt.Sprintf("ts-%s", acctest.RandString(5)) + incidentID := "" + pIncidentID := &incidentID + unrelatedIncidentID := "" + pUnrelatedIncidentID := &unrelatedIncidentID + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV5ProviderFactories: testAccProtoV5ProviderFactories(), + CheckDestroy: testAccCheckPagerDutyScheduleDestroy, + Steps: []resource.TestStep{ + { + Config: testAccCheckPagerDutyScheduleWithTeamsEscalationPolicyDependantWithOpenIncidentConfig(username, email, schedule, location, start, rotationVirtualStart, team, escalationPolicy1, service1), + Check: resource.ComposeTestCheckFunc( + testAccCheckPagerDutyScheduleExists("pagerduty_schedule.foo"), + resource.TestCheckResourceAttr( + "pagerduty_schedule.foo", "name", schedule), + resource.TestCheckResourceAttr( + "pagerduty_schedule.foo", "description", "foo"), + resource.TestCheckResourceAttr( + "pagerduty_escalation_policy.foo", "name", escalationPolicy1), + testAccCheckPagerDutyScheduleOpenIncidentOnService(pIncidentID, "pagerduty_service.foo", "pagerduty_escalation_policy.foo"), + ), + }, + { + Config: testAccCheckPagerDutyScheduleWithTeamsEscalationPolicyDependantWithOpenIncidentConfigUpdated(username, email, team, escalationPolicy1, service1), + ExpectError: regexp.MustCompile("Before destroying Schedule \".*\" You must first resolve or reassign the following incidents related with Escalation Policies using this Schedule"), + }, + { + // Extra intermediate step with the original plan for resolving the + // outstanding incident and retrying the schedule destroy after that. + Config: testAccCheckPagerDutyScheduleWithTeamsEscalationPolicyDependantWithOpenIncidentConfig(username, email, schedule, location, start, rotationVirtualStart, team, escalationPolicy1, service1), + Check: resource.ComposeTestCheckFunc( + testAccPagerDutyScheduleResolveIncident(pIncidentID, "pagerduty_escalation_policy.foo"), + ), + }, + { + Config: testAccCheckPagerDutyScheduleWithTeamsEscalationPolicyDependantWithOpenIncidentConfigUpdated(username, email, team, escalationPolicy1, service1), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr( + "pagerduty_escalation_policy.foo", "name", escalationPolicy1), + ), + }, + { + Config: testAccCheckPagerDutyScheduleWithTeamsEscalationPolicyDependantWithUnrelatedOpenIncidentConfig(username, email, schedule, location, start, rotationVirtualStart, team, escalationPolicy1, escalationPolicy2, service1, service2), + Check: resource.ComposeTestCheckFunc( + testAccCheckPagerDutyScheduleExists("pagerduty_schedule.foo"), + resource.TestCheckResourceAttr( + "pagerduty_schedule.foo", "name", schedule), + resource.TestCheckResourceAttr( + "pagerduty_schedule.foo", "description", "foo"), + resource.TestCheckResourceAttr( + "pagerduty_escalation_policy.foo", "name", escalationPolicy1), + resource.TestCheckResourceAttr( + "pagerduty_escalation_policy.bar", "name", escalationPolicy2), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "name", service1), + resource.TestCheckResourceAttr( + "pagerduty_service.bar", "name", service2), + testAccCheckPagerDutyScheduleOpenIncidentOnService(pUnrelatedIncidentID, "pagerduty_service.bar", "pagerduty_escalation_policy.bar"), + ), + }, + { + Config: testAccCheckPagerDutyScheduleWithTeamsEscalationPolicyDependantWithUnrelatedOpenIncidentConfigUpdated(username, email, schedule, location, start, rotationVirtualStart, team, escalationPolicy1, escalationPolicy2, service1, service2), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr( + "pagerduty_escalation_policy.foo", "name", escalationPolicy1), + resource.TestCheckResourceAttr( + "pagerduty_escalation_policy.bar", "name", escalationPolicy2), + testAccPagerDutyScheduleResolveIncident(pUnrelatedIncidentID, "pagerduty_escalation_policy.bar"), + ), + }, + }, + }) +} + +func TestAccPagerDutySchedule_EscalationPolicyDependantWithOpenIncidents(t *testing.T) { + service1 := fmt.Sprintf("tf-%s", acctest.RandString(5)) + service2 := fmt.Sprintf("tf-%s", acctest.RandString(5)) + username := fmt.Sprintf("tf-%s", acctest.RandString(5)) + email := fmt.Sprintf("%s@foo.test", username) + schedule1 := fmt.Sprintf("tf-%s", acctest.RandString(5)) + schedule2 := fmt.Sprintf("tf-%s", acctest.RandString(5)) + location := "America/New_York" + t.Setenv("PAGERDUTY_TIME_ZONE", location) + start := testAccTimeNow().Add(24 * time.Hour).Round(1 * time.Hour).Format(time.RFC3339) + rotationVirtualStart := testAccTimeNow().Add(24 * time.Hour).Round(1 * time.Hour).Format(time.RFC3339) + escalationPolicy1 := fmt.Sprintf("ts-%s", acctest.RandString(5)) + escalationPolicy2 := fmt.Sprintf("ts-%s", acctest.RandString(5)) + incidentID := "" + pIncidentID := &incidentID + unrelatedIncidentID := "" + pUnrelatedIncidentID := &unrelatedIncidentID + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV5ProviderFactories: testAccProtoV5ProviderFactories(), + CheckDestroy: testAccCheckPagerDutyScheduleDestroy, + Steps: []resource.TestStep{ + { + Config: testAccCheckPagerDutyScheduleEscalationPolicyDependantWithOpenIncidentConfig(username, email, schedule1, location, start, rotationVirtualStart, escalationPolicy1, service1), + Check: resource.ComposeTestCheckFunc( + testAccCheckPagerDutyScheduleExists("pagerduty_schedule.foo"), + resource.TestCheckResourceAttr( + "pagerduty_schedule.foo", "name", schedule1), + resource.TestCheckResourceAttr( + "pagerduty_schedule.foo", "description", "foo"), + resource.TestCheckResourceAttr( + "pagerduty_escalation_policy.foo", "name", escalationPolicy1), + testAccCheckPagerDutyScheduleOpenIncidentOnService(pIncidentID, "pagerduty_service.foo", "pagerduty_escalation_policy.foo"), + ), + }, + { + Config: testAccCheckPagerDutyScheduleEscalationPolicyDependantWithOpenIncidentConfigUpdated(username, email, escalationPolicy1, service1), + ExpectError: regexp.MustCompile("Before destroying Schedule \".*\" You must first resolve or reassign the following incidents related with Escalation Policies using this Schedule"), + }, + { + // Extra intermediate step with the original plan for resolving the + // outstanding incident and retrying the schedule destroy after that. + Config: testAccCheckPagerDutyScheduleEscalationPolicyDependantWithOpenIncidentConfig(username, email, schedule1, location, start, rotationVirtualStart, escalationPolicy1, service1), + Check: resource.ComposeTestCheckFunc( + testAccPagerDutyScheduleResolveIncident(pIncidentID, "pagerduty_escalation_policy.foo"), + ), + }, + { + Config: testAccCheckPagerDutyScheduleEscalationPolicyDependantWithOpenIncidentConfigUpdated(username, email, escalationPolicy1, service1), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr( + "pagerduty_escalation_policy.foo", "name", escalationPolicy1), + ), + }, + { + Config: testAccCheckPagerDutyScheduleEscalationPolicyDependantWithUnrelatedOpenIncidentConfig(username, email, schedule1, schedule2, location, start, rotationVirtualStart, escalationPolicy1, escalationPolicy2, service1, service2), + Check: resource.ComposeTestCheckFunc( + testAccCheckPagerDutyScheduleExists("pagerduty_schedule.foo"), + resource.TestCheckResourceAttr( + "pagerduty_schedule.foo", "name", schedule1), + resource.TestCheckResourceAttr( + "pagerduty_schedule.foo", "description", "foo"), + resource.TestCheckResourceAttr( + "pagerduty_schedule.bar", "name", schedule2), + resource.TestCheckResourceAttr( + "pagerduty_schedule.bar", "description", "bar"), + resource.TestCheckResourceAttr( + "pagerduty_escalation_policy.foo", "name", escalationPolicy1), + resource.TestCheckResourceAttr( + "pagerduty_escalation_policy.bar", "name", escalationPolicy2), + resource.TestCheckResourceAttr( + "pagerduty_service.foo", "name", service1), + resource.TestCheckResourceAttr( + "pagerduty_service.bar", "name", service2), + testAccCheckPagerDutyScheduleOpenIncidentOnService(pUnrelatedIncidentID, "pagerduty_service.bar", "pagerduty_escalation_policy.bar"), + ), + }, + { + Config: testAccCheckPagerDutyScheduleEscalationPolicyDependantWithUnrelatedOpenIncidentConfigUpdated(username, email, schedule1, schedule2, location, start, rotationVirtualStart, escalationPolicy1, escalationPolicy2, service1, service2), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr( + "pagerduty_schedule.bar", "name", schedule2), + resource.TestCheckResourceAttr( + "pagerduty_schedule.bar", "description", "bar"), + resource.TestCheckResourceAttr( + "pagerduty_escalation_policy.foo", "name", escalationPolicy1), + resource.TestCheckResourceAttr( + "pagerduty_escalation_policy.bar", "name", escalationPolicy2), + testAccPagerDutyScheduleResolveIncident(pUnrelatedIncidentID, "pagerduty_escalation_policy.bar"), + ), + }, + }, + }) +} + +func TestAccPagerDutyScheduleOverflow_Basic(t *testing.T) { + username := fmt.Sprintf("tf-%s", acctest.RandString(5)) + email := fmt.Sprintf("%s@foo.test", username) + schedule := fmt.Sprintf("tf-%s", acctest.RandString(5)) + scheduleUpdated := fmt.Sprintf("tf-%s", acctest.RandString(5)) + location := "America/New_York" + t.Setenv("PAGERDUTY_TIME_ZONE", location) + start := testAccTimeNow().Add(30 * time.Hour).Round(1 * time.Hour).Format(time.RFC3339) + rotationVirtualStart := testAccTimeNow().Add(30 * time.Hour).Round(1 * time.Hour).Format(time.RFC3339) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV5ProviderFactories: testAccProtoV5ProviderFactories(), + CheckDestroy: testAccCheckPagerDutyScheduleDestroy, + Steps: []resource.TestStep{ + { + Config: testAccCheckPagerDutyScheduleOverflowConfig(username, email, schedule, location, start, rotationVirtualStart), + Check: resource.ComposeTestCheckFunc( + testAccCheckPagerDutyScheduleExists("pagerduty_schedule.foo"), + ), + }, + { + Config: testAccCheckPagerDutyScheduleOverflowConfigUpdated(username, email, scheduleUpdated, location, start, rotationVirtualStart), + Check: resource.ComposeTestCheckFunc( + testAccCheckPagerDutyScheduleExists("pagerduty_schedule.foo"), + ), + }, + }, + }) +} + +func TestAccPagerDutySchedule_BasicWeek(t *testing.T) { + username := fmt.Sprintf("tf-%s", acctest.RandString(5)) + email := fmt.Sprintf("%s@foo.test", username) + schedule := fmt.Sprintf("tf-%s", acctest.RandString(5)) + scheduleUpdated := fmt.Sprintf("tf-%s", acctest.RandString(5)) + location := "Australia/Melbourne" + t.Setenv("PAGERDUTY_TIME_ZONE", location) + start := testAccTimeNow().Add(24 * time.Hour).Round(1 * time.Hour).Format(time.RFC3339) + rotationVirtualStart := testAccTimeNow().Add(24 * time.Hour).Round(1 * time.Hour).Format(time.RFC3339) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV5ProviderFactories: testAccProtoV5ProviderFactories(), + CheckDestroy: testAccCheckPagerDutyScheduleDestroy, + Steps: []resource.TestStep{ + { + Config: testAccCheckPagerDutyScheduleConfigWeek(username, email, schedule, location, start, rotationVirtualStart), + Check: resource.ComposeTestCheckFunc( + testAccCheckPagerDutyScheduleExists("pagerduty_schedule.foo"), + resource.TestCheckResourceAttr( + "pagerduty_schedule.foo", "name", schedule), + resource.TestCheckResourceAttr( + "pagerduty_schedule.foo", "description", "foo"), + resource.TestCheckResourceAttr( + "pagerduty_schedule.foo", "time_zone", location), + resource.TestCheckResourceAttr( + "pagerduty_schedule.foo", "layer.#", "1"), + resource.TestCheckResourceAttr( + "pagerduty_schedule.foo", "layer.0.name", "foo"), + resource.TestCheckResourceAttr( + "pagerduty_schedule.foo", "layer.0.restriction.0.start_day_of_week", "1"), + resource.TestCheckResourceAttr( + "pagerduty_schedule.foo", "layer.0.start", start), + resource.TestCheckResourceAttr( + "pagerduty_schedule.foo", "layer.0.rotation_virtual_start", rotationVirtualStart), + ), + }, + { + Config: testAccCheckPagerDutyScheduleConfigWeekUpdated(username, email, scheduleUpdated, location, start, rotationVirtualStart), + Check: resource.ComposeTestCheckFunc( + testAccCheckPagerDutyScheduleExists("pagerduty_schedule.foo"), + resource.TestCheckResourceAttr( + "pagerduty_schedule.foo", "name", scheduleUpdated), + resource.TestCheckResourceAttr( + "pagerduty_schedule.foo", "description", "Managed by Terraform"), + resource.TestCheckResourceAttr( + "pagerduty_schedule.foo", "time_zone", location), + resource.TestCheckResourceAttr( + "pagerduty_schedule.foo", "layer.#", "1"), + resource.TestCheckResourceAttr( + "pagerduty_schedule.foo", "layer.0.name", "foo"), + resource.TestCheckResourceAttr( + "pagerduty_schedule.foo", "layer.0.restriction.0.start_day_of_week", "5"), + resource.TestCheckResourceAttr( + "pagerduty_schedule.foo", "layer.0.start", start), + resource.TestCheckResourceAttr( + "pagerduty_schedule.foo", "layer.0.rotation_virtual_start", rotationVirtualStart), + ), + }, + }, + }) +} + +func TestAccPagerDutySchedule_Multi(t *testing.T) { + username := fmt.Sprintf("tf-%s", acctest.RandString(5)) + email := fmt.Sprintf("%s@foo.test", username) + schedule := fmt.Sprintf("tf-%s", acctest.RandString(5)) + location := "Europe/Berlin" + t.Setenv("PAGERDUTY_TIME_ZONE", location) + start := testAccTimeNow().Add(24 * time.Hour).Round(1 * time.Hour).Format(time.RFC3339) + end := testAccTimeNow().Add(72 * time.Hour).Round(1 * time.Hour).Format(time.RFC3339) + rotationVirtualStart := testAccTimeNow().Add(24 * time.Hour).Round(1 * time.Hour).Format(time.RFC3339) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV5ProviderFactories: testAccProtoV5ProviderFactories(), + CheckDestroy: testAccCheckPagerDutyScheduleDestroy, + Steps: []resource.TestStep{ + { + Config: testAccCheckPagerDutyScheduleConfigMulti(username, email, schedule, location, start, rotationVirtualStart, end), + Check: resource.ComposeTestCheckFunc( + testAccCheckPagerDutyScheduleExists("pagerduty_schedule.foo"), + resource.TestCheckResourceAttr( + "pagerduty_schedule.foo", "name", schedule), + resource.TestCheckResourceAttr( + "pagerduty_schedule.foo", "description", "foo"), + resource.TestCheckResourceAttr( + "pagerduty_schedule.foo", "time_zone", location), + + resource.TestCheckResourceAttr( + "pagerduty_schedule.foo", "layer.#", "3"), + resource.TestCheckResourceAttr( + "pagerduty_schedule.foo", "layer.0.name", "foo"), + resource.TestCheckResourceAttr( + "pagerduty_schedule.foo", "layer.0.restriction.#", "1"), + resource.TestCheckResourceAttr( + "pagerduty_schedule.foo", "layer.0.restriction.0.duration_seconds", "32101"), + resource.TestCheckResourceAttr( + "pagerduty_schedule.foo", "layer.0.restriction.0.start_time_of_day", "08:00:00"), + resource.TestCheckResourceAttr( + "pagerduty_schedule.foo", "layer.0.rotation_turn_length_seconds", "86400"), + resource.TestCheckResourceAttr( + "pagerduty_schedule.foo", "layer.0.users.#", "1"), + resource.TestCheckResourceAttr( + "pagerduty_schedule.foo", "layer.0.start", start), + resource.TestCheckResourceAttr( + "pagerduty_schedule.foo", "layer.0.rotation_virtual_start", rotationVirtualStart), + + resource.TestCheckResourceAttr( + "pagerduty_schedule.foo", "layer.1.name", "bar"), + resource.TestCheckResourceAttr( + "pagerduty_schedule.foo", "layer.1.restriction.#", "1"), + resource.TestCheckResourceAttr( + "pagerduty_schedule.foo", "layer.1.restriction.0.duration_seconds", "32101"), + resource.TestCheckResourceAttr( + "pagerduty_schedule.foo", "layer.1.restriction.0.start_time_of_day", "08:00:00"), + resource.TestCheckResourceAttr( + "pagerduty_schedule.foo", "layer.1.restriction.0.start_day_of_week", "5"), + resource.TestCheckResourceAttr( + "pagerduty_schedule.foo", "layer.1.rotation_turn_length_seconds", "86400"), + resource.TestCheckResourceAttr( + "pagerduty_schedule.foo", "layer.1.users.#", "1"), + resource.TestCheckResourceAttr( + "pagerduty_schedule.foo", "layer.1.start", start), + resource.TestCheckResourceAttr( + "pagerduty_schedule.foo", "layer.1.end", end), + resource.TestCheckResourceAttr( + "pagerduty_schedule.foo", "layer.1.rotation_virtual_start", rotationVirtualStart), + + resource.TestCheckResourceAttr( + "pagerduty_schedule.foo", "layer.2.name", "foobar"), + resource.TestCheckResourceAttr( + "pagerduty_schedule.foo", "layer.2.restriction.#", "1"), + resource.TestCheckResourceAttr( + "pagerduty_schedule.foo", "layer.2.restriction.0.duration_seconds", "32101"), + resource.TestCheckResourceAttr( + "pagerduty_schedule.foo", "layer.2.restriction.0.start_time_of_day", "08:00:00"), + resource.TestCheckResourceAttr( + "pagerduty_schedule.foo", "layer.2.restriction.0.start_day_of_week", "1"), + resource.TestCheckResourceAttr( + "pagerduty_schedule.foo", "layer.2.rotation_turn_length_seconds", "86400"), + resource.TestCheckResourceAttr( + "pagerduty_schedule.foo", "layer.2.users.#", "1"), + resource.TestCheckResourceAttr( + "pagerduty_schedule.foo", "layer.2.start", start), + resource.TestCheckResourceAttr( + "pagerduty_schedule.foo", "layer.2.rotation_virtual_start", rotationVirtualStart), + ), + }, + { + Config: testAccCheckPagerDutyScheduleConfigMultiUpdated(username, email, schedule, location, start, rotationVirtualStart, end), + Check: resource.ComposeTestCheckFunc( + testAccCheckPagerDutyScheduleExists("pagerduty_schedule.foo"), + resource.TestCheckResourceAttr( + "pagerduty_schedule.foo", "name", schedule), + resource.TestCheckResourceAttr( + "pagerduty_schedule.foo", "description", "foo"), + resource.TestCheckResourceAttr( + "pagerduty_schedule.foo", "time_zone", location), + resource.TestCheckResourceAttr( + "pagerduty_schedule.foo", "layer.#", "2"), + ), + }, + }, + }) +} + +func testAccCheckPagerDutyScheduleDestroy(s *terraform.State) error { + for _, r := range s.RootModule().Resources { + if r.Type != "pagerduty_schedule" { + continue + } + + ctx := context.Background() + o := pagerduty.GetScheduleOptions{} + if _, err := testAccProvider.client.GetScheduleWithContext(ctx, r.Primary.ID, o); err == nil { + return fmt.Errorf("Schedule still exists") + } + } + return nil +} + +func testAccCheckPagerDutyScheduleExists(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 Schedule ID is set") + } + + ctx := context.Background() + found, err := testAccProvider.client.GetScheduleWithContext(ctx, rs.Primary.ID, pagerduty.GetScheduleOptions{}) + if err != nil { + return err + } + + if found.ID != rs.Primary.ID { + return fmt.Errorf("Schedule not found: %v - %v", rs.Primary.ID, found) + } + + return nil + } +} + +func testAccCheckPagerDutyScheduleNoExists(n string) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + if !ok { + return nil + } + if rs != nil && rs.Primary.ID == "" { + return nil + } + + ctx := context.Background() + found, err := testAccProvider.client.GetScheduleWithContext(ctx, rs.Primary.ID, pagerduty.GetScheduleOptions{}) + if err != nil { + return err + } + + if found.ID == rs.Primary.ID { + return fmt.Errorf("Schedule still exists: %v - %v", rs.Primary.ID, found) + } + + return nil + } +} + +func testAccExternallyDestroySchedule(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 Schedule ID is set") + } + + ctx := context.Background() + err := testAccProvider.client.DeleteScheduleWithContext(ctx, rs.Primary.ID) + if err != nil { + return err + } + + return nil + } +} + +func testAccCheckPagerDutyScheduleConfig(username, email, schedule, location, start, rotationVirtualStart string) string { + return fmt.Sprintf(` +resource "pagerduty_user" "foo" { + name = "%s" + email = "%s" +} + +resource "pagerduty_schedule" "foo" { + name = "%s" + + time_zone = "%s" + description = "foo" + + layer { + name = "foo" + start = "%s" + rotation_virtual_start = "%s" + rotation_turn_length_seconds = 86400 + users = [pagerduty_user.foo.id] + + restriction { + type = "daily_restriction" + start_time_of_day = "08:00:00" + duration_seconds = 32101 + } + } +} +`, username, email, schedule, location, start, rotationVirtualStart) +} + +func testAccCheckPagerDutyScheduleConfigRestrictionType(username, email, schedule, location, start, rotationVirtualStart string) string { + return fmt.Sprintf(` +resource "pagerduty_user" "foo" { + name = "%s" + email = "%s" +} + +resource "pagerduty_schedule" "foo" { + name = "%s" + + time_zone = "%s" + description = "foo" + + layer { + name = "foo" + start = "%s" + rotation_virtual_start = "%s" + rotation_turn_length_seconds = 86400 + users = [pagerduty_user.foo.id] + + restriction { + type = "daily_restriction" + start_time_of_day = "08:00:00" + duration_seconds = 32101 + start_day_of_week = 5 + } + } +} +`, username, email, schedule, location, start, rotationVirtualStart) +} + +func testAccCheckPagerDutyScheduleConfigRestrictionTypeWeeklyWithoutStartDayOfWeekSet(username, email, schedule, location, start, rotationVirtualStart string) string { + return fmt.Sprintf(` +resource "pagerduty_user" "foo" { + name = "%s" + email = "%s" +} + +resource "pagerduty_schedule" "foo" { + name = "%s" + + time_zone = "%s" + description = "foo" + + layer { + name = "foo" + start = "%s" + rotation_virtual_start = "%s" + rotation_turn_length_seconds = 86400 + users = [pagerduty_user.foo.id] + + restriction { + type = "weekly_restriction" + start_time_of_day = "08:00:00" + duration_seconds = 32101 + } + } +} +`, username, email, schedule, location, start, rotationVirtualStart) +} + +func testAccCheckPagerDutyScheduleConfigUpdated(username, email, schedule, location, start, rotationVirtualStart string) string { + return fmt.Sprintf(` +resource "pagerduty_user" "foo" { + name = "%s" + email = "%s" +} + +resource "pagerduty_schedule" "foo" { + name = "%s" + + time_zone = "%s" + + layer { + name = "foo" + start = "%s" + rotation_virtual_start = "%s" + rotation_turn_length_seconds = 86400 + users = [pagerduty_user.foo.id] + + restriction { + type = "daily_restriction" + start_time_of_day = "08:00:00" + duration_seconds = 32101 + } + } +} +`, username, email, schedule, location, start, rotationVirtualStart) +} + +func testAccCheckPagerDutyScheduleOverflowConfig(username, email, schedule, location, start, rotationVirtualStart string) string { + return fmt.Sprintf(` +resource "pagerduty_user" "foo" { + name = "%s" + email = "%s" +} + +resource "pagerduty_schedule" "foo" { + name = "%s" + overflow = true + time_zone = "%s" + + layer { + name = "foo" + start = "%s" + rotation_virtual_start = "%s" + rotation_turn_length_seconds = 86400 + users = [pagerduty_user.foo.id] + + restriction { + type = "daily_restriction" + start_time_of_day = "08:00:00" + duration_seconds = 32101 + } + } +} +`, username, email, schedule, location, start, rotationVirtualStart) +} + +func testAccCheckPagerDutyScheduleOverflowConfigUpdated(username, email, schedule, location, start, rotationVirtualStart string) string { + return fmt.Sprintf(` +resource "pagerduty_user" "foo" { + name = "%s" + email = "%s" +} + +resource "pagerduty_schedule" "foo" { + name = "%s" + overflow = false + time_zone = "%s" + + layer { + name = "foo" + start = "%s" + rotation_virtual_start = "%s" + rotation_turn_length_seconds = 86400 + users = [pagerduty_user.foo.id] + + restriction { + type = "daily_restriction" + start_time_of_day = "08:00:00" + duration_seconds = 32101 + } + } +} +`, username, email, schedule, location, start, rotationVirtualStart) +} + +func testAccCheckPagerDutyScheduleConfigWeek(username, email, schedule, location, start, rotationVirtualStart string) string { + return fmt.Sprintf(` +resource "pagerduty_user" "foo" { + name = "%s" + email = "%s" +} + +resource "pagerduty_schedule" "foo" { + name = "%s" + + time_zone = "%s" + description = "foo" + + layer { + name = "foo" + start = "%s" + rotation_virtual_start = "%s" + rotation_turn_length_seconds = 86400 + users = [pagerduty_user.foo.id] + + restriction { + type = "weekly_restriction" + start_time_of_day = "08:00:00" + start_day_of_week = 1 + duration_seconds = 32101 + } + } +} +`, username, email, schedule, location, start, rotationVirtualStart) +} + +func testAccCheckPagerDutyScheduleConfigWeekUpdated(username, email, schedule, location, start, rotationVirtualStart string) string { + return fmt.Sprintf(` +resource "pagerduty_user" "foo" { + name = "%s" + email = "%s" +} + +resource "pagerduty_schedule" "foo" { + name = "%s" + + time_zone = "%s" + + layer { + name = "foo" + start = "%s" + rotation_virtual_start = "%s" + rotation_turn_length_seconds = 86400 + users = [pagerduty_user.foo.id] + + restriction { + type = "weekly_restriction" + start_time_of_day = "08:00:00" + start_day_of_week = 5 + duration_seconds = 32101 + } + } +} +`, username, email, schedule, location, start, rotationVirtualStart) +} + +func testAccCheckPagerDutyScheduleConfigMulti(username, email, schedule, location, start, rotationVirtualStart, end string) string { + return fmt.Sprintf(` +resource "pagerduty_user" "foo" { + name = "%s" + email = "%s" +} + +resource "pagerduty_schedule" "foo" { + name = "%s" + + time_zone = "%s" + description = "foo" + + layer { + name = "foo" + start = "%[5]v" + end = null + rotation_virtual_start = "%[6]v" + rotation_turn_length_seconds = 86400 + users = [pagerduty_user.foo.id] + + restriction { + type = "daily_restriction" + start_time_of_day = "08:00:00" + duration_seconds = 32101 + } + } + + layer { + name = "bar" + start = "%[5]v" + end = "%[7]v" + rotation_virtual_start = "%[6]v" + rotation_turn_length_seconds = 86400 + users = [pagerduty_user.foo.id] + + restriction { + type = "weekly_restriction" + start_time_of_day = "08:00:00" + start_day_of_week = 5 + duration_seconds = 32101 + } + } + + layer { + name = "foobar" + start = "%[5]v" + end = null + rotation_virtual_start = "%[6]v" + rotation_turn_length_seconds = 86400 + users = [pagerduty_user.foo.id] + + restriction { + type = "weekly_restriction" + start_time_of_day = "08:00:00" + start_day_of_week = 1 + duration_seconds = 32101 + } + } +} +`, username, email, schedule, location, start, rotationVirtualStart, end) +} + +func testAccCheckPagerDutyScheduleConfigMultiUpdated(username, email, schedule, location, start, rotationVirtualStart, end string) string { + return fmt.Sprintf(` +resource "pagerduty_user" "foo" { + name = "%s" + email = "%s" +} + +resource "pagerduty_schedule" "foo" { + name = "%s" + + time_zone = "%s" + description = "foo" + + layer { + name = "foo" + start = "%[5]v" + end = null + rotation_virtual_start = "%[6]v" + rotation_turn_length_seconds = 86400 + users = [pagerduty_user.foo.id] + + restriction { + type = "daily_restriction" + start_time_of_day = "08:00:00" + duration_seconds = 32101 + } + } + + layer { + name = "bar" + start = "%[5]v" + end = "%[7]v" + rotation_virtual_start = "%[6]v" + rotation_turn_length_seconds = 86400 + users = [pagerduty_user.foo.id] + + restriction { + type = "weekly_restriction" + start_time_of_day = "08:00:00" + start_day_of_week = 5 + duration_seconds = 32101 + } + } +} +`, username, email, schedule, location, start, rotationVirtualStart, end) +} + +func testAccCheckPagerDutyScheduleWithTeamsConfig(username, email, schedule, location, start, rotationVirtualStart, team string) string { + return fmt.Sprintf(` +resource "pagerduty_user" "foo" { + name = "%s" + email = "%s" +} + +resource "pagerduty_team" "foo" { + name = "%s" + description = "fighters" +} + +resource "pagerduty_schedule" "foo" { + name = "%s" + + time_zone = "%s" + description = "foo" + + teams = [pagerduty_team.foo.id] + + layer { + name = "foo" + start = "%s" + rotation_virtual_start = "%s" + rotation_turn_length_seconds = 86400 + users = [pagerduty_user.foo.id] + + restriction { + type = "daily_restriction" + start_time_of_day = "08:00:00" + duration_seconds = 32101 + } + } +} +`, username, email, team, schedule, location, start, rotationVirtualStart) +} + +func testAccCheckPagerDutyScheduleWithTeamsConfigUpdated(username, email, schedule, location, start, rotationVirtualStart, team string) string { + return fmt.Sprintf(` +resource "pagerduty_user" "foo" { + name = "%s" + email = "%s" +} + +resource "pagerduty_team" "foo" { + name = "%s" + description = "bar" +} + +resource "pagerduty_schedule" "foo" { + name = "%s" + + time_zone = "%s" + description = "Managed by Terraform" + + teams = [pagerduty_team.foo.id] + + layer { + name = "foo" + start = "%s" + rotation_virtual_start = "%s" + rotation_turn_length_seconds = 86400 + users = [pagerduty_user.foo.id] + + restriction { + type = "daily_restriction" + start_time_of_day = "08:00:00" + duration_seconds = 32101 + } + } +} +`, username, email, team, schedule, location, start, rotationVirtualStart) +} + +func testAccCheckPagerDutyScheduleWithTeamsEscalationPolicyDependantConfig(username, email, schedule, location, start, rotationVirtualStart, team, escalationPolicy string) string { + return fmt.Sprintf(` +resource "pagerduty_user" "foo" { + name = "%s" + email = "%s" +} + +resource "pagerduty_team" "foo" { + name = "%s" + description = "fighters" +} + +resource "pagerduty_schedule" "foo" { + name = "%s" + + time_zone = "%s" + description = "foo" + + teams = [pagerduty_team.foo.id] + + layer { + name = "foo" + start = "%s" + rotation_virtual_start = "%s" + rotation_turn_length_seconds = 86400 + users = [pagerduty_user.foo.id] + + restriction { + type = "daily_restriction" + start_time_of_day = "08:00:00" + duration_seconds = 32101 + } + } +} + +resource "pagerduty_escalation_policy" "foo" { + name = "%s" + num_loops = 2 + teams = [pagerduty_team.foo.id] + + rule { + escalation_delay_in_minutes = 10 + target { + type = "user_reference" + id = pagerduty_user.foo.id + } + target { + type = "schedule_reference" + id = pagerduty_schedule.foo.id + } + } + + rule { + escalation_delay_in_minutes = 10 + target { + type = "schedule_reference" + id = pagerduty_schedule.foo.id + } + } +} +`, username, email, team, schedule, location, start, rotationVirtualStart, escalationPolicy) +} + +func testAccCheckPagerDutyScheduleWithTeamsEscalationPolicyDependantConfigUpdated(username, email, team, escalationPolicy string) string { + return fmt.Sprintf(` +resource "pagerduty_user" "foo" { + name = "%s" + email = "%s" +} + +resource "pagerduty_team" "foo" { + name = "%s" + description = "bar" +} + +resource "pagerduty_escalation_policy" "foo" { + name = "%s" + num_loops = 2 + teams = [pagerduty_team.foo.id] + + rule { + escalation_delay_in_minutes = 10 + target { + type = "user_reference" + id = pagerduty_user.foo.id + } + } +} +`, username, email, team, escalationPolicy) +} + +func testAccCheckPagerDutyScheduleWithTeamsEscalationPolicyDependantWithOneLayerConfig(username, email, schedule, location, start, rotationVirtualStart, team, escalationPolicy string) string { + return fmt.Sprintf(` +resource "pagerduty_user" "foo" { + name = "%s" + email = "%s" +} + +resource "pagerduty_team" "foo" { + name = "%s" + description = "fighters" +} + +resource "pagerduty_schedule" "foo" { + name = "%s" + + time_zone = "%s" + description = "foo" + + teams = [pagerduty_team.foo.id] + + layer { + name = "foo" + start = "%s" + rotation_virtual_start = "%s" + rotation_turn_length_seconds = 86400 + users = [pagerduty_user.foo.id] + + restriction { + type = "daily_restriction" + start_time_of_day = "08:00:00" + duration_seconds = 32101 + } + } +} + +resource "pagerduty_escalation_policy" "foo" { + name = "%s" + num_loops = 2 + teams = [pagerduty_team.foo.id] + + rule { + escalation_delay_in_minutes = 10 + target { + type = "schedule_reference" + id = pagerduty_schedule.foo.id + } + } +} +`, username, email, team, schedule, location, start, rotationVirtualStart, escalationPolicy) +} + +func testAccCheckPagerDutyScheduleWithTeamsEscalationPolicyDependantWithMultipleLayersUsingTheSameScheduleAsTargetConfig(username, email, schedule, location, start, rotationVirtualStart, team, escalationPolicy string) string { + return fmt.Sprintf(` +resource "pagerduty_user" "foo" { + name = "%s" + email = "%s" +} + +resource "pagerduty_team" "foo" { + name = "%s" + description = "fighters" +} + +resource "pagerduty_schedule" "foo" { + name = "%s" + + time_zone = "%s" + description = "foo" + + teams = [pagerduty_team.foo.id] + + layer { + name = "foo" + start = "%s" + rotation_virtual_start = "%s" + rotation_turn_length_seconds = 86400 + users = [pagerduty_user.foo.id] + + restriction { + type = "daily_restriction" + start_time_of_day = "08:00:00" + duration_seconds = 32101 + } + } +} + +resource "pagerduty_escalation_policy" "foo" { + name = "%s" + num_loops = 2 + teams = [pagerduty_team.foo.id] + + rule { + escalation_delay_in_minutes = 10 + target { + type = "schedule_reference" + id = pagerduty_schedule.foo.id + } + } + rule { + escalation_delay_in_minutes = 10 + target { + type = "schedule_reference" + id = pagerduty_schedule.foo.id + } + } +} +`, username, email, team, schedule, location, start, rotationVirtualStart, escalationPolicy) +} + +func testAccCheckPagerDutyScheduleWithTeamsEscalationPolicyDependantWithMultipleLayersUsingTheSameScheduleAsTargetConfigUpdated(username, email, team, escalationPolicy string) string { + return fmt.Sprintf(` +resource "pagerduty_user" "foo" { + name = "%s" + email = "%s" +} + +resource "pagerduty_team" "foo" { + name = "%s" + description = "bar" +} + +resource "pagerduty_escalation_policy" "foo" { + name = "%s" + num_loops = 2 + teams = [pagerduty_team.foo.id] + + rule { + escalation_delay_in_minutes = 10 + target { + type = "user_reference" + id = pagerduty_user.foo.id + } + } + rule { + escalation_delay_in_minutes = 10 + target { + type = "user_reference" + id = pagerduty_user.foo.id + } + } +} +`, username, email, team, escalationPolicy) +} + +func testAccCheckPagerDutyScheduleWithTeamsEscalationPolicyDependantMultipleWithOneLayerConfig(username, email, schedule, location, start, rotationVirtualStart, team, escalationPolicy1, escaltionPolicy2 string) string { + return fmt.Sprintf(` +resource "pagerduty_user" "foo" { + name = "%s" + email = "%s" +} + +resource "pagerduty_team" "foo" { + name = "%s" + description = "fighters" +} + +resource "pagerduty_schedule" "foo" { + name = "%s" + + time_zone = "%s" + description = "foo" + + teams = [pagerduty_team.foo.id] + + layer { + name = "foo" + start = "%s" + rotation_virtual_start = "%s" + rotation_turn_length_seconds = 86400 + users = [pagerduty_user.foo.id] + + restriction { + type = "daily_restriction" + start_time_of_day = "08:00:00" + duration_seconds = 32101 + } + } +} + +resource "pagerduty_escalation_policy" "foo" { + name = "%s" + num_loops = 2 + teams = [pagerduty_team.foo.id] + + rule { + escalation_delay_in_minutes = 10 + target { + type = "schedule_reference" + id = pagerduty_schedule.foo.id + } + } +} +resource "pagerduty_escalation_policy" "bar" { + name = "%s" + num_loops = 2 + teams = [pagerduty_team.foo.id] + + rule { + escalation_delay_in_minutes = 10 + target { + type = "schedule_reference" + id = pagerduty_schedule.foo.id + } + } +} +`, username, email, team, schedule, location, start, rotationVirtualStart, escalationPolicy1, escaltionPolicy2) +} + +func testAccCheckPagerDutyScheduleWithTeamsEscalationPolicyDependantMultipleWithOneLayerConfigUpdated(username, email, team, escalationPolicy1, escaltionPolicy2 string) string { + return fmt.Sprintf(` +resource "pagerduty_user" "foo" { + name = "%s" + email = "%s" +} + +resource "pagerduty_team" "foo" { + name = "%s" + description = "fighters" +} + +resource "pagerduty_escalation_policy" "foo" { + name = "%s" + num_loops = 2 + teams = [pagerduty_team.foo.id] + + rule { + escalation_delay_in_minutes = 10 + target { + type = "user_reference" + id = pagerduty_user.foo.id + } + } +} +resource "pagerduty_escalation_policy" "bar" { + name = "%s" + num_loops = 2 + teams = [pagerduty_team.foo.id] + + rule { + escalation_delay_in_minutes = 10 + target { + type = "user_reference" + id = pagerduty_user.foo.id + } + } +} +`, username, email, team, escalationPolicy1, escaltionPolicy2) +} + +func testAccCheckPagerDutyScheduleOpenIncidentOnService(p *string, sn, epn string) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[sn] + if !ok { + return fmt.Errorf("Not found service: %s", sn) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("No Service ID is set") + } + + rep, ok := s.RootModule().Resources[epn] + if !ok { + return fmt.Errorf("Not found escalation policy: %s", epn) + } + + if rep.Primary.ID == "" { + return fmt.Errorf("No Escalation Policy ID is set") + } + + incident := &pagerduty.CreateIncidentOptions{ + Type: "incident", + Title: fmt.Sprintf("tf-%s", acctest.RandString(5)), + Service: &pagerduty.APIReference{ + ID: rs.Primary.ID, + Type: "service_reference", + }, + EscalationPolicy: &pagerduty.APIReference{ + ID: rep.Primary.ID, + Type: "escalation_policy_reference", + }, + } + + ctx := context.Background() + // TODO: set "From" header + resp, err := testAccProvider.client.CreateIncidentWithContext(ctx, "", incident) + if err != nil { + return err + } + + *p = resp.ID + + return nil + } +} + +func testAccPagerDutyScheduleResolveIncident(p *string, _ string) resource.TestCheckFunc { + return func(_ *terraform.State) error { + ctx := context.Background() + + incident, err := testAccProvider.client.GetIncidentWithContext(ctx, *p) + if err != nil { + return err + } + + // marking incident as resolved + incident.Status = "resolved" + incidentOptions := buildPagerdutyManageIncidentsOptions(incident) + + _, err = testAccProvider.client.ManageIncidentsWithContext(ctx, "", []pagerduty.ManageIncidentsOptions{incidentOptions}) + if err != nil { + return err + } + + return nil + } +} + +func buildPagerdutyManageIncidentsOptions(incident *pagerduty.Incident) pagerduty.ManageIncidentsOptions { + var priority *pagerduty.APIReference + if incident.Priority != nil { + priority = &pagerduty.APIReference{ + ID: incident.Priority.ID, + Type: incident.Priority.Type, + } + } + + var assignments []pagerduty.Assignee + for _, assign := range incident.Assignments { + assignments = append(assignments, pagerduty.Assignee{ + Assignee: pagerduty.APIObject{ + ID: assign.Assignee.ID, + Type: assign.Assignee.Type, + }, + }) + } + + return pagerduty.ManageIncidentsOptions{ + ID: incident.ID, + Status: incident.Status, + Title: incident.Title, + Priority: priority, + Assignments: assignments, + // EscalationLevel: incident.EscalationLevel, + EscalationPolicy: &pagerduty.APIReference{ + ID: incident.EscalationPolicy.ID, + Type: incident.EscalationPolicy.Type, + }, + Resolution: "", + ConferenceBridge: incident.ConferenceBridge, + } + +} + +func testAccCheckPagerDutyScheduleWithTeamsEscalationPolicyDependantWithOpenIncidentConfig(username, email, schedule, location, start, rotationVirtualStart, team, escalationPolicy, service string) string { + return fmt.Sprintf(` +resource "pagerduty_user" "foo" { + name = "%s" + email = "%s" +} + +resource "pagerduty_team" "foo" { + name = "%s" + description = "fighters" +} + +resource "pagerduty_schedule" "foo" { + name = "%s" + + time_zone = "%s" + description = "foo" + + teams = [pagerduty_team.foo.id] + + layer { + name = "foo" + start = "%s" + rotation_virtual_start = "%s" + rotation_turn_length_seconds = 86400 + users = [pagerduty_user.foo.id] + + restriction { + type = "daily_restriction" + start_time_of_day = "08:00:00" + duration_seconds = 32101 + } + } +} + +resource "pagerduty_escalation_policy" "foo" { + name = "%s" + num_loops = 2 + teams = [pagerduty_team.foo.id] + + rule { + escalation_delay_in_minutes = 10 + target { + type = "user_reference" + id = pagerduty_user.foo.id + } + target { + type = "schedule_reference" + id = pagerduty_schedule.foo.id + } + } + + rule { + escalation_delay_in_minutes = 10 + target { + type = "schedule_reference" + id = pagerduty_schedule.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_incidents" +} +`, username, email, team, schedule, location, start, rotationVirtualStart, escalationPolicy, service) +} + +func testAccCheckPagerDutyScheduleEscalationPolicyDependantWithOpenIncidentConfig(username, email, schedule, location, start, rotationVirtualStart, escalationPolicy, service string) string { + return fmt.Sprintf(` +resource "pagerduty_user" "foo" { + name = "%s" + email = "%s" +} + +resource "pagerduty_schedule" "foo" { + name = "%s" + + time_zone = "%s" + description = "foo" + + layer { + name = "foo" + start = "%s" + rotation_virtual_start = "%s" + rotation_turn_length_seconds = 86400 + users = [pagerduty_user.foo.id] + + restriction { + type = "daily_restriction" + start_time_of_day = "08:00:00" + duration_seconds = 32101 + } + } +} + +resource "pagerduty_escalation_policy" "foo" { + name = "%s" + num_loops = 2 + + rule { + escalation_delay_in_minutes = 10 + target { + type = "user_reference" + id = pagerduty_user.foo.id + } + target { + type = "schedule_reference" + id = pagerduty_schedule.foo.id + } + } + + rule { + escalation_delay_in_minutes = 10 + target { + type = "schedule_reference" + id = pagerduty_schedule.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_incidents" +} +`, username, email, schedule, location, start, rotationVirtualStart, escalationPolicy, service) +} + +func testAccCheckPagerDutyScheduleWithTeamsEscalationPolicyDependantWithUnrelatedOpenIncidentConfig(username, email, schedule, location, start, rotationVirtualStart, team, escalationPolicy1, escalationPolicy2, service1, service2 string) string { + return fmt.Sprintf(` +resource "pagerduty_user" "foo" { + name = "%[1]s" + email = "%[2]s" +} + +resource "pagerduty_team" "foo" { + name = "%[3]s" + description = "fighters" +} + +resource "pagerduty_schedule" "foo" { + name = "%[4]s" + + time_zone = "%[5]s" + description = "foo" + + teams = [pagerduty_team.foo.id] + + layer { + name = "foo" + start = "%[6]s" + rotation_virtual_start = "%[7]s" + rotation_turn_length_seconds = 86400 + users = [pagerduty_user.foo.id] + + restriction { + type = "daily_restriction" + start_time_of_day = "08:00:00" + duration_seconds = 32101 + } + } +} + +resource "pagerduty_escalation_policy" "foo" { + name = "%[8]s" + num_loops = 2 + teams = [pagerduty_team.foo.id] + + rule { + escalation_delay_in_minutes = 10 + target { + type = "user_reference" + id = pagerduty_user.foo.id + } + target { + type = "schedule_reference" + id = pagerduty_schedule.foo.id + } + } + + rule { + escalation_delay_in_minutes = 10 + target { + type = "schedule_reference" + id = pagerduty_schedule.foo.id + } + } +} + +resource "pagerduty_escalation_policy" "bar" { + name = "%[9]s" + num_loops = 2 + teams = [pagerduty_team.foo.id] + + rule { + escalation_delay_in_minutes = 10 + target { + type = "user_reference" + id = pagerduty_user.foo.id + } + } +} + +resource "pagerduty_service" "foo" { + name = "%[10]s" + description = "foo" + auto_resolve_timeout = 1800 + acknowledgement_timeout = 1800 + escalation_policy = pagerduty_escalation_policy.foo.id + alert_creation = "create_incidents" +} +resource "pagerduty_service" "bar" { + name = "%[11]s" + description = "bar" + auto_resolve_timeout = 1800 + acknowledgement_timeout = 1800 + escalation_policy = pagerduty_escalation_policy.bar.id + alert_creation = "create_incidents" +} +`, username, email, team, schedule, location, start, rotationVirtualStart, escalationPolicy1, escalationPolicy2, service1, service2) +} + +func testAccCheckPagerDutyScheduleEscalationPolicyDependantWithUnrelatedOpenIncidentConfig(username, email, schedule1, schedule2, location, start, rotationVirtualStart, escalationPolicy1, escalationPolicy2, service1, service2 string) string { + return fmt.Sprintf(` +resource "pagerduty_user" "foo" { + name = "%[1]s" + email = "%[2]s" +} + +resource "pagerduty_schedule" "foo" { + name = "%[3]s" + + time_zone = "%[5]s" + description = "foo" + + layer { + name = "foo" + start = "%[6]s" + rotation_virtual_start = "%[7]s" + rotation_turn_length_seconds = 86400 + users = [pagerduty_user.foo.id] + + restriction { + type = "daily_restriction" + start_time_of_day = "08:00:00" + duration_seconds = 32101 + } + } +} + +resource "pagerduty_schedule" "bar" { + name = "%[4]s" + + time_zone = "%[5]s" + description = "bar" + + layer { + name = "bar" + start = "%[6]s" + rotation_virtual_start = "%[7]s" + rotation_turn_length_seconds = 86400 + users = [pagerduty_user.foo.id] + + restriction { + type = "daily_restriction" + start_time_of_day = "08:00:00" + duration_seconds = 32101 + } + } +} + +resource "pagerduty_escalation_policy" "foo" { + name = "%[8]s" + num_loops = 2 + + rule { + escalation_delay_in_minutes = 10 + target { + type = "user_reference" + id = pagerduty_user.foo.id + } + target { + type = "schedule_reference" + id = pagerduty_schedule.foo.id + } + } + + rule { + escalation_delay_in_minutes = 10 + target { + type = "schedule_reference" + id = pagerduty_schedule.foo.id + } + } +} + +resource "pagerduty_escalation_policy" "bar" { + name = "%[9]s" + num_loops = 2 + + rule { + escalation_delay_in_minutes = 10 + target { + type = "user_reference" + id = pagerduty_user.foo.id + } + target { + type = "schedule_reference" + id = pagerduty_schedule.bar.id + } + } +} + +resource "pagerduty_service" "foo" { + name = "%[10]s" + description = "foo" + auto_resolve_timeout = 1800 + acknowledgement_timeout = 1800 + escalation_policy = pagerduty_escalation_policy.foo.id + alert_creation = "create_incidents" +} +resource "pagerduty_service" "bar" { + name = "%[11]s" + description = "bar" + auto_resolve_timeout = 1800 + acknowledgement_timeout = 1800 + escalation_policy = pagerduty_escalation_policy.bar.id + alert_creation = "create_incidents" +} +`, username, email, schedule1, schedule2, location, start, rotationVirtualStart, escalationPolicy1, escalationPolicy2, service1, service2) +} + +func testAccCheckPagerDutyScheduleWithTeamsEscalationPolicyDependantWithUnrelatedOpenIncidentConfigUpdated(username, email, schedule, location, start, rotationVirtualStart, team, escalationPolicy1, escalationPolicy2, service1, service2 string) string { + return fmt.Sprintf(` +resource "pagerduty_user" "foo" { + name = "%[1]s" + email = "%[2]s" +} + +resource "pagerduty_team" "foo" { + name = "%[3]s" + description = "fighters" +} + +resource "pagerduty_escalation_policy" "foo" { + name = "%[8]s" + num_loops = 2 + teams = [pagerduty_team.foo.id] + + rule { + escalation_delay_in_minutes = 10 + target { + type = "user_reference" + id = pagerduty_user.foo.id + } + } +} + +resource "pagerduty_escalation_policy" "bar" { + name = "%[9]s" + num_loops = 2 + teams = [pagerduty_team.foo.id] + + rule { + escalation_delay_in_minutes = 10 + target { + type = "user_reference" + id = pagerduty_user.foo.id + } + } +} + +resource "pagerduty_service" "foo" { + name = "%[10]s" + description = "foo" + auto_resolve_timeout = 1800 + acknowledgement_timeout = 1800 + escalation_policy = pagerduty_escalation_policy.foo.id + alert_creation = "create_incidents" +} +resource "pagerduty_service" "bar" { + name = "%[11]s" + description = "bar" + auto_resolve_timeout = 1800 + acknowledgement_timeout = 1800 + escalation_policy = pagerduty_escalation_policy.bar.id + alert_creation = "create_incidents" +} +`, username, email, team, schedule, location, start, rotationVirtualStart, escalationPolicy1, escalationPolicy2, service1, service2) +} + +func testAccCheckPagerDutyScheduleEscalationPolicyDependantWithUnrelatedOpenIncidentConfigUpdated(username, email, schedule1, schedule2, location, start, rotationVirtualStart, escalationPolicy1, escalationPolicy2, service1, service2 string) string { + return fmt.Sprintf(` +resource "pagerduty_user" "foo" { + name = "%[1]s" + email = "%[2]s" +} + +resource "pagerduty_schedule" "bar" { + name = "%[4]s" + + time_zone = "%[5]s" + description = "bar" + + layer { + name = "bar" + start = "%[6]s" + rotation_virtual_start = "%[7]s" + rotation_turn_length_seconds = 86400 + users = [pagerduty_user.foo.id] + + restriction { + type = "daily_restriction" + start_time_of_day = "08:00:00" + duration_seconds = 32101 + } + } +} + +resource "pagerduty_escalation_policy" "foo" { + name = "%[8]s" + num_loops = 2 + + rule { + escalation_delay_in_minutes = 10 + target { + type = "user_reference" + id = pagerduty_user.foo.id + } + } +} + +resource "pagerduty_escalation_policy" "bar" { + name = "%[9]s" + num_loops = 2 + + rule { + escalation_delay_in_minutes = 10 + target { + type = "user_reference" + id = pagerduty_user.foo.id + } + target { + type = "schedule_reference" + id = pagerduty_schedule.bar.id + } + } +} + +resource "pagerduty_service" "foo" { + name = "%[10]s" + description = "foo" + auto_resolve_timeout = 1800 + acknowledgement_timeout = 1800 + escalation_policy = pagerduty_escalation_policy.foo.id + alert_creation = "create_incidents" +} +resource "pagerduty_service" "bar" { + name = "%[11]s" + description = "bar" + auto_resolve_timeout = 1800 + acknowledgement_timeout = 1800 + escalation_policy = pagerduty_escalation_policy.bar.id + alert_creation = "create_incidents" +} +`, username, email, schedule1, schedule2, location, start, rotationVirtualStart, escalationPolicy1, escalationPolicy2, service1, service2) +} + +func testAccCheckPagerDutyScheduleWithTeamsEscalationPolicyDependantWithOpenIncidentConfigUpdated(username, email, team, escalationPolicy, service string) string { + return fmt.Sprintf(` +resource "pagerduty_user" "foo" { + name = "%s" + email = "%s" +} + +resource "pagerduty_team" "foo" { + name = "%s" + description = "bar" +} + +resource "pagerduty_escalation_policy" "foo" { + name = "%s" + num_loops = 2 + teams = [pagerduty_team.foo.id] + + 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_incidents" +} +`, username, email, team, escalationPolicy, service) +} + +func testAccCheckPagerDutyScheduleEscalationPolicyDependantWithOpenIncidentConfigUpdated(username, email, escalationPolicy, service string) string { + return fmt.Sprintf(` +resource "pagerduty_user" "foo" { + name = "%s" + email = "%s" +} + +resource "pagerduty_escalation_policy" "foo" { + name = "%s" + 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_incidents" +} +`, username, email, escalationPolicy, service) +} + +func testAccPreCheckScheduleUsedByEPWithOneLayer(t *testing.T) { + if v := os.Getenv("PAGERDUTY_ACC_SCHEDULE_USED_BY_EP_W_1_LAYER"); v == "" { + t.Skip("PAGERDUTY_ACC_SCHEDULE_USED_BY_EP_W_1_LAYER not set. Skipping Schedule related test") + } +}