From c1490d73caf2f3889c4ad30560278d08146e422e Mon Sep 17 00:00:00 2001 From: Jim Kong Date: Fri, 14 Jul 2023 16:25:12 +1000 Subject: [PATCH 01/22] add ssm contacts rotation resource and data source --- internal/service/ssmcontacts/find.go | 24 + internal/service/ssmcontacts/flex.go | 284 +++++ internal/service/ssmcontacts/helper.go | 7 + internal/service/ssmcontacts/rotation.go | 320 ++++++ .../ssmcontacts/rotation_data_source.go | 173 +++ .../ssmcontacts/rotation_data_source_test.go | 308 +++++ internal/service/ssmcontacts/rotation_test.go | 1013 +++++++++++++++++ .../ssmcontacts/service_package_gen.go | 12 + .../service/ssmcontacts/ssmcontacts_test.go | 13 + .../docs/d/ssmcontacts_rotation.html.markdown | 52 + .../docs/r/ssmcontacts_rotation.html.markdown | 184 +++ 11 files changed, 2390 insertions(+) create mode 100644 internal/service/ssmcontacts/rotation.go create mode 100644 internal/service/ssmcontacts/rotation_data_source.go create mode 100644 internal/service/ssmcontacts/rotation_data_source_test.go create mode 100644 internal/service/ssmcontacts/rotation_test.go create mode 100644 website/docs/d/ssmcontacts_rotation.html.markdown create mode 100644 website/docs/r/ssmcontacts_rotation.html.markdown diff --git a/internal/service/ssmcontacts/find.go b/internal/service/ssmcontacts/find.go index 373b2ea09da8..e266a1f5d685 100644 --- a/internal/service/ssmcontacts/find.go +++ b/internal/service/ssmcontacts/find.go @@ -61,3 +61,27 @@ func findContactChannelByID(ctx context.Context, conn *ssmcontacts.Client, id st return out, nil } + +func FindRotationByID(ctx context.Context, conn *ssmcontacts.Client, id string) (*ssmcontacts.GetRotationOutput, error) { + in := &ssmcontacts.GetRotationInput{ + RotationId: aws.String(id), + } + out, err := conn.GetRotation(ctx, in) + if err != nil { + var nfe *types.ResourceNotFoundException + if errors.As(err, &nfe) { + return nil, &retry.NotFoundError{ + LastError: err, + LastRequest: in, + } + } + + return nil, err + } + + if out == nil { + return nil, tfresource.NewEmptyResultError(in) + } + + return out, nil +} diff --git a/internal/service/ssmcontacts/flex.go b/internal/service/ssmcontacts/flex.go index ad5bfaec5058..65642964d438 100644 --- a/internal/service/ssmcontacts/flex.go +++ b/internal/service/ssmcontacts/flex.go @@ -4,6 +4,11 @@ package ssmcontacts import ( + "context" + "fmt" + "strconv" + "strings" + "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/service/ssmcontacts/types" ) @@ -197,3 +202,282 @@ func flattenContactTargetInfo(contactTargetInfo *types.ContactTargetInfo) []inte return result } + +func expandHandOffTime(handOffTime string) *types.HandOffTime { + split := strings.Split(handOffTime, ":") + hour, _ := strconv.Atoi(split[0]) + minute, _ := strconv.Atoi(split[1]) + + return &types.HandOffTime{ + HourOfDay: int32(hour), + MinuteOfHour: int32(minute), + } +} + +func flattenHandOffTime(handOffTime *types.HandOffTime) string { + return fmt.Sprintf("%02d:%02d", handOffTime.HourOfDay, handOffTime.MinuteOfHour) +} + +func expandRecurrence(recurrence []interface{}, ctx context.Context) *types.RecurrenceSettings { + c := &types.RecurrenceSettings{} + + recurrenceSettings := recurrence[0].(map[string]interface{}) + + if v, ok := recurrenceSettings["daily_settings"].([]interface{}); ok && v != nil { + c.DailySettings = expandDailySettings(v) + } + + if v, ok := recurrenceSettings["monthly_settings"].([]interface{}); ok && v != nil { + c.MonthlySettings = expandMonthlySettings(v) + } + + if v, ok := recurrenceSettings["number_of_on_calls"].(int); ok { + c.NumberOfOnCalls = aws.Int32(int32(v)) + } + + if v, ok := recurrenceSettings["recurrence_multiplier"].(int); ok { + c.RecurrenceMultiplier = aws.Int32(int32(v)) + } + + if v, ok := recurrenceSettings["shift_coverages"].([]interface{}); ok && v != nil { + c.ShiftCoverages = expandShiftCoverages(v) + } + + if v, ok := recurrenceSettings["weekly_settings"].([]interface{}); ok && v != nil { + c.WeeklySettings = expandWeeklySettings(v) + } + + return c +} + +func flattenRecurrence(recurrence *types.RecurrenceSettings, ctx context.Context) []interface{} { + var result []interface{} + + c := make(map[string]interface{}) + + if v := recurrence.DailySettings; v != nil { + c["daily_settings"] = flattenDailySettings(v) + } + + if v := recurrence.MonthlySettings; v != nil { + c["monthly_settings"] = flattenMonthlySettings(v) + } + + if v := recurrence.NumberOfOnCalls; v != nil { + c["number_of_on_calls"] = aws.ToInt32(v) + } + + if v := recurrence.RecurrenceMultiplier; v != nil { + c["recurrence_multiplier"] = aws.ToInt32(v) + } + + if v := recurrence.ShiftCoverages; v != nil { + c["shift_coverages"] = flattenShiftCoverages(v) + } + + if v := recurrence.WeeklySettings; v != nil { + c["weekly_settings"] = flattenWeeklySettings(v) + } + + result = append(result, c) + + return result +} + +func expandDailySettings(dailySettings []interface{}) []types.HandOffTime { + if len(dailySettings) == 0 { + return nil + } + + var result []types.HandOffTime + + for _, dailySetting := range dailySettings { + result = append(result, *expandHandOffTime(dailySetting.(string))) + } + + return result +} + +func flattenDailySettings(dailySettings []types.HandOffTime) []interface{} { + if len(dailySettings) == 0 { + return nil + } + + var result []interface{} + + for _, handOffTime := range dailySettings { + result = append(result, flattenHandOffTime(&handOffTime)) + } + + return result +} + +func expandMonthlySettings(monthlySettings []interface{}) []types.MonthlySetting { + if len(monthlySettings) == 0 { + return nil + } + + var result []types.MonthlySetting + + for _, monthlySetting := range monthlySettings { + monthlySettingData := monthlySetting.(map[string]interface{}) + + c := types.MonthlySetting{ + DayOfMonth: aws.Int32(int32(monthlySettingData["day_of_month"].(int))), + HandOffTime: expandHandOffTime(monthlySettingData["hand_off_time"].(string)), + } + + result = append(result, c) + } + + return result +} + +func flattenMonthlySettings(monthlySettings []types.MonthlySetting) []interface{} { + if len(monthlySettings) == 0 { + return nil + } + + var result []interface{} + + for _, monthlySetting := range monthlySettings { + c := make(map[string]interface{}) + + if v := monthlySetting.DayOfMonth; v != nil { + c["day_of_month"] = aws.ToInt32(v) + } + + if v := monthlySetting.HandOffTime; v != nil { + c["hand_off_time"] = flattenHandOffTime(v) + } + + result = append(result, c) + } + + return result +} + +func expandShiftCoverages(shiftCoverages []interface{}) map[string][]types.CoverageTime { + if len(shiftCoverages) == 0 { + return nil + } + + result := make(map[string][]types.CoverageTime) + + for _, shiftCoverage := range shiftCoverages { + shiftCoverageData := shiftCoverage.(map[string]interface{}) + + dayOfWeek := shiftCoverageData["day_of_week"].(string) + coverageTimes := expandCoverageTimes(shiftCoverageData["coverage_times"].([]interface{})) + + result[dayOfWeek] = coverageTimes + } + + return result +} + +func flattenShiftCoverages(shiftCoverages map[string][]types.CoverageTime) []interface{} { + if len(shiftCoverages) == 0 { + return nil + } + + var result []interface{} + + for coverageDay, coverageTime := range shiftCoverages { + c := make(map[string]interface{}) + + c["coverage_times"] = flattenCoverageTimes(coverageTime) + c["day_of_week"] = coverageDay + + result = append(result, c) + } + + // API doesn't return in any consistent order. This causes flakes during testing, so we sort to always return a consistent order + sortShiftCoverages(result) + + return result +} + +func expandCoverageTimes(coverageTimes []interface{}) []types.CoverageTime { + var result []types.CoverageTime + + for _, coverageTime := range coverageTimes { + coverageTimeData := coverageTime.(map[string]interface{}) + + c := types.CoverageTime{} + + if v, ok := coverageTimeData["end_time"].(string); ok { + c.End = expandHandOffTime(v) + } + + if v, ok := coverageTimeData["start_time"].(string); ok { + c.Start = expandHandOffTime(v) + } + + result = append(result, c) + } + + return result +} + +func flattenCoverageTimes(coverageTimes []types.CoverageTime) []interface{} { + var result []interface{} + + c := make(map[string]interface{}) + + for _, coverageTime := range coverageTimes { + if v := coverageTime.End; v != nil { + c["end_time"] = flattenHandOffTime(v) + } + + if v := coverageTime.Start; v != nil { + c["start_time"] = flattenHandOffTime(v) + } + + result = append(result, c) + } + + return result +} + +func expandWeeklySettings(weeklySettings []interface{}) []types.WeeklySetting { + var result []types.WeeklySetting + + for _, weeklySetting := range weeklySettings { + weeklySettingData := weeklySetting.(map[string]interface{}) + + c := types.WeeklySetting{ + DayOfWeek: types.DayOfWeek(weeklySettingData["day_of_week"].(string)), + HandOffTime: expandHandOffTime(weeklySettingData["hand_off_time"].(string)), + } + + result = append(result, c) + + } + + return result +} + +func flattenWeeklySettings(weeklySettings []types.WeeklySetting) []interface{} { + if len(weeklySettings) == 0 { + return nil + } + + var result []interface{} + + for _, weeklySetting := range weeklySettings { + c := make(map[string]interface{}) + + if v := string(weeklySetting.DayOfWeek); v != "" { + c["day_of_week"] = v + } + + if v := weeklySetting.HandOffTime; v != nil { + c["hand_off_time"] = flattenHandOffTime(v) + } + + result = append(result, c) + } + + return result +} diff --git a/internal/service/ssmcontacts/helper.go b/internal/service/ssmcontacts/helper.go index 3e541c4a4048..f31bd43d4215 100644 --- a/internal/service/ssmcontacts/helper.go +++ b/internal/service/ssmcontacts/helper.go @@ -5,6 +5,7 @@ package ssmcontacts import ( "fmt" + "sort" "github.com/aws/aws-sdk-go-v2/service/ssmcontacts" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" @@ -40,3 +41,9 @@ func setPlanResourceData(d *schema.ResourceData, getContactOutput *ssmcontacts.G return nil } + +func sortShiftCoverages(shiftCoverages []interface{}) { + sort.Slice(shiftCoverages, func(i, j int) bool { + return shiftCoverages[i].(map[string]interface{})["day_of_week"].(string) < shiftCoverages[j].(map[string]interface{})["day_of_week"].(string) + }) +} diff --git a/internal/service/ssmcontacts/rotation.go b/internal/service/ssmcontacts/rotation.go new file mode 100644 index 000000000000..238b19e3e348 --- /dev/null +++ b/internal/service/ssmcontacts/rotation.go @@ -0,0 +1,320 @@ +package ssmcontacts + +import ( + "context" + "errors" + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/customdiff" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" + "github.com/hashicorp/terraform-provider-aws/internal/enum" + "github.com/hashicorp/terraform-provider-aws/internal/flex" + "log" + "regexp" + "time" + + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/ssmcontacts" + "github.com/aws/aws-sdk-go-v2/service/ssmcontacts/types" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/create" + tftags "github.com/hashicorp/terraform-provider-aws/internal/tags" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" + "github.com/hashicorp/terraform-provider-aws/internal/verify" + "github.com/hashicorp/terraform-provider-aws/names" +) + +// @SDKResource("aws_ssmcontacts_rotation") +func ResourceRotation() *schema.Resource { + return &schema.Resource{ + CreateWithoutTimeout: resourceRotationCreate, + ReadWithoutTimeout: resourceRotationRead, + UpdateWithoutTimeout: resourceRotationUpdate, + DeleteWithoutTimeout: resourceRotationDelete, + + Importer: &schema.ResourceImporter{ + StateContext: schema.ImportStatePassthroughContext, + }, + + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(30 * time.Minute), + Update: schema.DefaultTimeout(30 * time.Minute), + Delete: schema.DefaultTimeout(30 * time.Minute), + }, + + Schema: map[string]*schema.Schema{ + "arn": { + Type: schema.TypeString, + Computed: true, + }, + "contact_ids": { + Type: schema.TypeList, + Required: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "name": { + Type: schema.TypeString, + Required: true, + }, + "recurrence": { + Type: schema.TypeList, + Required: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "daily_settings": { + Type: schema.TypeList, + Optional: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + ValidateDiagFunc: validation.ToDiagFunc(handOffTimeValidator), + }, + }, + "monthly_settings": { + Type: schema.TypeList, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "day_of_month": { + Type: schema.TypeInt, + Required: true, + }, + "hand_off_time": { + Type: schema.TypeString, + Required: true, + ValidateDiagFunc: validation.ToDiagFunc(handOffTimeValidator), + }, + }, + }, + }, + "number_of_on_calls": { + Type: schema.TypeInt, + Required: true, + }, + "recurrence_multiplier": { + Type: schema.TypeInt, + Required: true, + }, + "shift_coverages": { + Type: schema.TypeList, + Optional: true, + Computed: true, // This is computed to allow for clearing the diff to handle erroneous diffs + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "coverage_times": { + Type: schema.TypeList, + MaxItems: 1, + Required: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "end_time": { + Type: schema.TypeString, + Optional: true, + ValidateDiagFunc: validation.ToDiagFunc(handOffTimeValidator), + }, + "start_time": { + Type: schema.TypeString, + Optional: true, + ValidateDiagFunc: validation.ToDiagFunc(handOffTimeValidator), + }, + }, + }, + }, + "day_of_week": { + Type: schema.TypeString, + Required: true, + ValidateDiagFunc: enum.Validate[types.DayOfWeek](), + }, + }, + }, + }, + "weekly_settings": { + Type: schema.TypeList, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "day_of_week": { + Type: schema.TypeString, + Required: true, + ValidateDiagFunc: enum.Validate[types.DayOfWeek](), + }, + "hand_off_time": { + Type: schema.TypeString, + Required: true, + ValidateDiagFunc: validation.ToDiagFunc(handOffTimeValidator), + }, + }, + }, + }, + }, + }, + }, + "start_time": { + Type: schema.TypeString, + Optional: true, + ValidateDiagFunc: validation.ToDiagFunc(validation.IsRFC3339Time), + }, + "time_zone_id": { + Type: schema.TypeString, + Required: true, + }, + names.AttrTags: tftags.TagsSchema(), + names.AttrTagsAll: tftags.TagsSchemaComputed(), + }, + + CustomizeDiff: customdiff.Sequence( + func(_ context.Context, diff *schema.ResourceDiff, _ interface{}) error { + if diff.HasChange("recurrence.0.shift_coverages") { + o, n := diff.GetChange("recurrence.0.shift_coverages") + + sortShiftCoverages(o.([]interface{})) + sortShiftCoverages(n.([]interface{})) + + isEqual := cmp.Diff(o, n) == "" + + if isEqual { + return diff.Clear("recurrence.0.shift_coverages") + } + } + + return nil + }, + verify.SetTagsDiff, + ), + } +} + +const ( + ResNameRotation = "Rotation" +) + +var handOffTimeValidator = validation.StringMatch(regexp.MustCompile(`^\d\d:\d\d$`), "Time must be in 24-hour time format, e.g. \"01:00\"") + +func resourceRotationCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.AWSClient).SSMContactsClient(ctx) + + in := &ssmcontacts.CreateRotationInput{ + ContactIds: flex.ExpandStringValueList(d.Get("contact_ids").([]interface{})), + Name: aws.String(d.Get("name").(string)), + Recurrence: expandRecurrence(d.Get("recurrence").([]interface{}), ctx), + Tags: getTagsIn(ctx), + TimeZoneId: aws.String(d.Get("time_zone_id").(string)), + } + + if v, ok := d.GetOk("start_time"); ok { + startTime, _ := time.Parse(time.RFC3339, v.(string)) + in.StartTime = aws.Time(startTime) + } + + out, err := conn.CreateRotation(ctx, in) + if err != nil { + return create.DiagError(names.SSMContacts, create.ErrActionCreating, ResNameRotation, d.Get("name").(string), err) + } + + if out == nil { + return create.DiagError(names.SSMContacts, create.ErrActionCreating, ResNameRotation, d.Get("name").(string), errors.New("empty output")) + } + + d.SetId(aws.ToString(out.RotationArn)) + + return resourceRotationRead(ctx, d, meta) +} + +func resourceRotationRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.AWSClient).SSMContactsClient(ctx) + + out, err := FindRotationByID(ctx, conn, d.Id()) + + if !d.IsNewResource() && tfresource.NotFound(err) { + log.Printf("[WARN] SSMContacts Rotation (%s) not found, removing from state", d.Id()) + d.SetId("") + return nil + } + + if err != nil { + return create.DiagError(names.SSMContacts, create.ErrActionReading, ResNameRotation, d.Id(), err) + } + + d.Set("arn", out.RotationArn) + d.Set("contact_ids", out.ContactIds) + d.Set("name", out.Name) + d.Set("time_zone_id", out.TimeZoneId) + + if out.StartTime != nil { + d.Set("start_time", out.StartTime.Format(time.RFC3339)) + } + + if err := d.Set("recurrence", flattenRecurrence(out.Recurrence, ctx)); err != nil { + return create.DiagError(names.SSMContacts, create.ErrActionSetting, DSNameRotation, d.Id(), err) + } + + return nil +} + +func resourceRotationUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.AWSClient).SSMContactsClient(ctx) + + update := false + + in := &ssmcontacts.UpdateRotationInput{ + RotationId: aws.String(d.Id()), + } + + if d.HasChanges("contact_ids") { + in.ContactIds = flex.ExpandStringValueList(d.Get("contact_ids").([]interface{})) + update = true + } + + // Recurrence is a required field, but we don't want to force an update every time if no changes + in.Recurrence = expandRecurrence(d.Get("recurrence").([]interface{}), ctx) + if d.HasChanges("recurrence") { + update = true + } + + if d.HasChanges("start_time") { + startTime, _ := time.Parse(time.RFC3339, d.Get("start_time").(string)) + in.StartTime = aws.Time(startTime) + update = true + } + + if d.HasChanges("time_zone_id") { + in.TimeZoneId = aws.String(d.Get("time_zone_id").(string)) + update = true + } + + if !update { + return nil + } + + log.Printf("[DEBUG] Updating SSMContacts Rotation (%s): %#v", d.Id(), in) + _, err := conn.UpdateRotation(ctx, in) + if err != nil { + return create.DiagError(names.SSMContacts, create.ErrActionUpdating, ResNameRotation, d.Id(), err) + } + + return resourceRotationRead(ctx, d, meta) +} + +func resourceRotationDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.AWSClient).SSMContactsClient(ctx) + + log.Printf("[INFO] Deleting SSMContacts Rotation %s", d.Id()) + + _, err := conn.DeleteRotation(ctx, &ssmcontacts.DeleteRotationInput{ + RotationId: aws.String(d.Id()), + }) + + if err != nil { + var nfe *types.ResourceNotFoundException + if errors.As(err, &nfe) { + return nil + } + + return create.DiagError(names.SSMContacts, create.ErrActionDeleting, ResNameRotation, d.Id(), err) + } + + return nil +} diff --git a/internal/service/ssmcontacts/rotation_data_source.go b/internal/service/ssmcontacts/rotation_data_source.go new file mode 100644 index 000000000000..282762246b7f --- /dev/null +++ b/internal/service/ssmcontacts/rotation_data_source.go @@ -0,0 +1,173 @@ +package ssmcontacts + +import ( + "context" + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/create" + tftags "github.com/hashicorp/terraform-provider-aws/internal/tags" + "github.com/hashicorp/terraform-provider-aws/names" + "time" +) + +// @SDKDataSource("aws_ssmcontacts_rotation") +func DataSourceRotation() *schema.Resource { + return &schema.Resource{ + ReadWithoutTimeout: dataSourceRotationRead, + + Schema: map[string]*schema.Schema{ + "arn": { + Type: schema.TypeString, + Required: true, + }, + "contact_ids": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "name": { + Type: schema.TypeString, + Computed: true, + }, + "recurrence": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "daily_settings": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "monthly_settings": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "day_of_month": { + Type: schema.TypeInt, + Computed: true, + }, + "hand_off_time": { + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, + "number_of_on_calls": { + Type: schema.TypeInt, + Computed: true, + }, + "recurrence_multiplier": { + Type: schema.TypeInt, + Computed: true, + }, + "shift_coverages": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "coverage_times": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "end_time": { + Type: schema.TypeString, + Computed: true, + }, + "start_time": { + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, + "day_of_week": { + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, + "weekly_settings": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "day_of_week": { + Type: schema.TypeString, + Computed: true, + }, + "hand_off_time": { + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, + }, + }, + }, + "start_time": { + Type: schema.TypeString, + Computed: true, + }, + "time_zone_id": { + Type: schema.TypeString, + Computed: true, + }, + names.AttrTags: tftags.TagsSchemaComputed(), + }, + } +} + +const ( + DSNameRotation = "Rotation Data Source" +) + +func dataSourceRotationRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.AWSClient).SSMContactsClient(ctx) + arn := d.Get("arn").(string) + + out, err := FindRotationByID(ctx, conn, arn) + if err != nil { + return create.DiagError(names.SSMContacts, create.ErrActionReading, DSNameRotation, arn, err) + } + + d.SetId(aws.ToString(out.RotationArn)) + + d.Set("arn", out.RotationArn) + d.Set("contact_ids", out.ContactIds) + d.Set("name", out.Name) + d.Set("time_zone_id", out.TimeZoneId) + + if out.StartTime != nil { + d.Set("start_time", out.StartTime.Format(time.RFC3339)) + } + + if err := d.Set("recurrence", flattenRecurrence(out.Recurrence, ctx)); err != nil { + return create.DiagError(names.SSMContacts, create.ErrActionSetting, DSNameRotation, d.Id(), err) + } + + tags, err := listTags(ctx, conn, d.Id()) + if err != nil { + return create.DiagError(names.SSMIncidents, create.ErrActionReading, DSNameRotation, d.Id(), err) + } + + ignoreTagsConfig := meta.(*conns.AWSClient).IgnoreTagsConfig + + //lintignore:AWSR002 + if err := d.Set("tags", tags.IgnoreAWS().IgnoreConfig(ignoreTagsConfig).Map()); err != nil { + return create.DiagError(names.SSMContacts, create.ErrActionSetting, DSNameRotation, d.Id(), err) + } + + return nil +} diff --git a/internal/service/ssmcontacts/rotation_data_source_test.go b/internal/service/ssmcontacts/rotation_data_source_test.go new file mode 100644 index 000000000000..c22337906b5c --- /dev/null +++ b/internal/service/ssmcontacts/rotation_data_source_test.go @@ -0,0 +1,308 @@ +package ssmcontacts_test + +import ( + "fmt" + sdkacctest "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-provider-aws/internal/acctest" + "github.com/hashicorp/terraform-provider-aws/names" + "regexp" + "testing" + "time" +) + +func TestAccSSMContactsRotationDataSource_basic(t *testing.T) { + ctx := acctest.Context(t) + + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + dataSourceName := "data.aws_ssmcontacts_rotation.test" + resourceName := "aws_ssmcontacts_rotation.test" + startTime := time.Now().UTC().AddDate(0, 0, 2).Format(time.RFC3339) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(ctx, t) + testAccPreCheck(ctx, t) + }, + ErrorCheck: acctest.ErrorCheck(t, names.SSMContactsEndpointID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckRotationDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testRotationDataSourceConfig_basic(rName, startTime), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrPair(resourceName, "name", dataSourceName, "name"), + resource.TestCheckResourceAttrPair(resourceName, "recurrence.0.number_of_on_calls", dataSourceName, "recurrence.0.number_of_on_calls"), + resource.TestCheckResourceAttrPair(resourceName, "recurrence.0.recurrence_multiplier", dataSourceName, "recurrence.0.recurrence_multiplier"), + resource.TestCheckResourceAttrPair(resourceName, "recurrence.0.weekly_settings.#", dataSourceName, "recurrence.0.weekly_settings.#"), + resource.TestCheckResourceAttrPair(resourceName, "recurrence.0.weekly_settings.0.day_of_week", dataSourceName, "recurrence.0.weekly_settings.0.day_of_week"), + resource.TestCheckResourceAttrPair(resourceName, "recurrence.0.weekly_settings.0.hand_off_time", dataSourceName, "recurrence.0.weekly_settings.0.hand_off_time"), + resource.TestCheckResourceAttrPair(resourceName, "recurrence.0.weekly_settings.1.day_of_week", dataSourceName, "recurrence.0.weekly_settings.1.day_of_week"), + resource.TestCheckResourceAttrPair(resourceName, "recurrence.0.weekly_settings.1.hand_off_time", dataSourceName, "recurrence.0.weekly_settings.1.hand_off_time"), + resource.TestCheckResourceAttrPair(resourceName, "recurrence.0.weekly_settings.2.day_of_week", dataSourceName, "recurrence.0.weekly_settings.2.day_of_week"), + resource.TestCheckResourceAttrPair(resourceName, "recurrence.0.weekly_settings.2.hand_off_time", dataSourceName, "recurrence.0.weekly_settings.2.hand_off_time"), + resource.TestCheckResourceAttrPair(resourceName, "recurrence.0.shift_coverages.#", dataSourceName, "recurrence.0.shift_coverages.#"), + resource.TestCheckResourceAttrPair(resourceName, "recurrence.0.shift_coverages.0.day_of_week", dataSourceName, "recurrence.0.shift_coverages.0.day_of_week"), + resource.TestCheckResourceAttrPair(resourceName, "recurrence.0.shift_coverages.0.coverage_times.0.start_time", dataSourceName, "recurrence.0.shift_coverages.0.coverage_times.0.start_time"), + resource.TestCheckResourceAttrPair(resourceName, "recurrence.0.shift_coverages.0.coverage_times.0.end_time", dataSourceName, "recurrence.0.shift_coverages.0.coverage_times.0.end_time"), + resource.TestCheckResourceAttrPair(resourceName, "recurrence.0.shift_coverages.1.day_of_week", dataSourceName, "recurrence.0.shift_coverages.1.day_of_week"), + resource.TestCheckResourceAttrPair(resourceName, "recurrence.0.shift_coverages.1.coverage_times.0.start_time", dataSourceName, "recurrence.0.shift_coverages.1.coverage_times.0.start_time"), + resource.TestCheckResourceAttrPair(resourceName, "recurrence.0.shift_coverages.1.coverage_times.0.end_time", dataSourceName, "recurrence.0.shift_coverages.1.coverage_times.0.end_time"), + resource.TestCheckResourceAttrPair(resourceName, "recurrence.0.shift_coverages.2.day_of_week", dataSourceName, "recurrence.0.shift_coverages.2.day_of_week"), + resource.TestCheckResourceAttrPair(resourceName, "recurrence.0.shift_coverages.2.coverage_times.0.start_time", dataSourceName, "recurrence.0.shift_coverages.2.coverage_times.0.start_time"), + resource.TestCheckResourceAttrPair(resourceName, "recurrence.0.shift_coverages.2.coverage_times.0.end_time", dataSourceName, "recurrence.0.shift_coverages.2.coverage_times.0.end_time"), + resource.TestCheckResourceAttrPair(resourceName, "start_time", dataSourceName, "start_time"), + resource.TestCheckResourceAttrPair(resourceName, "time_zone_id", dataSourceName, "time_zone_id"), + resource.TestCheckResourceAttrPair(resourceName, "contact_ids.#", dataSourceName, "contact_ids.#"), + resource.TestCheckResourceAttrPair(resourceName, "contact_ids.0", dataSourceName, "contact_ids.0"), + resource.TestCheckResourceAttrPair(resourceName, "contact_ids.1", dataSourceName, "contact_ids.1"), + resource.TestCheckResourceAttrPair(resourceName, "contact_ids.2", dataSourceName, "contact_ids.2"), + resource.TestCheckResourceAttrPair(resourceName, "tags.%", dataSourceName, "tags.%"), + resource.TestCheckResourceAttrPair(resourceName, "tags.key1", dataSourceName, "tags.key1"), + resource.TestCheckResourceAttrPair(resourceName, "tags.key2", dataSourceName, "tags.key2"), + acctest.MatchResourceAttrRegionalARN(resourceName, "arn", "ssm-contacts", regexp.MustCompile(`rotation\/+.`)), + ), + }, + }, + }) +} + +func TestAccSSMContactsRotationDataSource_dailySettings(t *testing.T) { + ctx := acctest.Context(t) + + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + dataSourceName := "data.aws_ssmcontacts_rotation.test" + resourceName := "aws_ssmcontacts_rotation.test" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(ctx, t) + testAccPreCheck(ctx, t) + }, + ErrorCheck: acctest.ErrorCheck(t, names.SSMContactsEndpointID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckRotationDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testRotationDataSourceConfig_dailySettings(rName), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrPair(resourceName, "recurrence.0.daily_settings.#", dataSourceName, "recurrence.0.daily_settings.#"), + resource.TestCheckResourceAttrPair(resourceName, "recurrence.0.daily_settings.0", dataSourceName, "recurrence.0.daily_settings.0"), + ), + }, + }, + }) +} + +func TestAccSSMContactsRotationDataSource_monthlySettings(t *testing.T) { + ctx := acctest.Context(t) + + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + dataSourceName := "data.aws_ssmcontacts_rotation.test" + resourceName := "aws_ssmcontacts_rotation.test" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(ctx, t) + testAccPreCheck(ctx, t) + }, + ErrorCheck: acctest.ErrorCheck(t, names.SSMContactsEndpointID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckRotationDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testRotationDataSourceConfig_monthlySettings(rName), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrPair(resourceName, "recurrence.0.monthly_settings.#", dataSourceName, "recurrence.0.monthly_settings.#"), + resource.TestCheckResourceAttrPair(resourceName, "recurrence.0.monthly_settings.0.day_of_month", dataSourceName, "recurrence.0.monthly_settings.0.day_of_month"), + resource.TestCheckResourceAttrPair(resourceName, "recurrence.0.monthly_settings.0.hand_off_time", dataSourceName, "recurrence.0.monthly_settings.0.hand_off_time"), + resource.TestCheckResourceAttrPair(resourceName, "recurrence.0.monthly_settings.1.day_of_month", dataSourceName, "recurrence.0.monthly_settings.1.day_of_month"), + resource.TestCheckResourceAttrPair(resourceName, "recurrence.0.monthly_settings.1.hand_off_time", dataSourceName, "recurrence.0.monthly_settings.1.hand_off_time"), + resource.TestCheckResourceAttrPair(resourceName, "recurrence.0.monthly_settings.2.day_of_month", dataSourceName, "recurrence.0.monthly_settings.2.day_of_month"), + resource.TestCheckResourceAttrPair(resourceName, "recurrence.0.monthly_settings.2.hand_off_time", dataSourceName, "recurrence.0.monthly_settings.2.hand_off_time"), + ), + }, + }, + }) +} + +func testRotationDataSourceConfig_base(alias string) string { + return fmt.Sprintf(` +resource "aws_ssmincidents_replication_set" "test" { + region { + name = %[1]q + } +} + +resource "aws_ssmcontacts_contact" "test_contact_one" { + alias = "test-contact-one-for-%[2]s" + type = "PERSONAL" + + depends_on = [aws_ssmincidents_replication_set.test] +} +`, acctest.Region(), alias) +} + +func testRotationDataSourceConfig_basic(rName, startTime string) string { + return acctest.ConfigCompose( + testRotationDataSourceConfig_base(rName), + fmt.Sprintf(` +resource "aws_ssmcontacts_contact" "test_contact_two" { + alias = "test-contact-two-for-%[1]s" + type = "PERSONAL" + + depends_on = [aws_ssmincidents_replication_set.test] +} + +resource "aws_ssmcontacts_contact" "test_contact_three" { + alias = "test-contact-three-for-%[1]s" + type = "PERSONAL" + + depends_on = [aws_ssmincidents_replication_set.test] +} + +resource "aws_ssmcontacts_rotation" "test" { + contact_ids = [ + aws_ssmcontacts_contact.test_contact_one.arn, + aws_ssmcontacts_contact.test_contact_two.arn, + aws_ssmcontacts_contact.test_contact_three.arn + ] + + name = %[1]q + + recurrence { + number_of_on_calls = 1 + recurrence_multiplier = 1 + weekly_settings { + day_of_week = "MON" + hand_off_time = "04:25" + } + weekly_settings { + day_of_week = "WED" + hand_off_time = "07:34" + } + weekly_settings { + day_of_week = "FRI" + hand_off_time = "15:57" + } + shift_coverages { + day_of_week = "MON" + coverage_times { + start_time = "01:00" + end_time = "23:00" + } + } + shift_coverages { + day_of_week = "WED" + coverage_times { + start_time = "01:00" + end_time = "23:00" + } + } + shift_coverages { + day_of_week = "FRI" + coverage_times { + start_time = "01:00" + end_time = "23:00" + } + } + } + + start_time = %[2]q + + time_zone_id = "Australia/Sydney" + + tags = { + key1 = "tag1" + key2 = "tag2" + } + + depends_on = [aws_ssmincidents_replication_set.test] +} + +data "aws_ssmcontacts_rotation" "test" { + arn = aws_ssmcontacts_rotation.test.arn +} +`, rName, startTime)) +} + +func testRotationDataSourceConfig_dailySettings(rName string) string { + return acctest.ConfigCompose( + testRotationDataSourceConfig_base(rName), + fmt.Sprintf(` +resource "aws_ssmcontacts_rotation" "test" { + contact_ids = [ + aws_ssmcontacts_contact.test_contact_one.arn, + ] + + name = %[1]q + + recurrence { + number_of_on_calls = 1 + recurrence_multiplier = 1 + daily_settings = [ + "18:00" + ] + } + + time_zone_id = "Australia/Sydney" + + depends_on = [aws_ssmincidents_replication_set.test] +} + +data "aws_ssmcontacts_rotation" "test" { + arn = aws_ssmcontacts_rotation.test.arn +} +`, rName)) +} + +func testRotationDataSourceConfig_monthlySettings(rName string) string { + return acctest.ConfigCompose( + testRotationDataSourceConfig_base(rName), + fmt.Sprintf(` +resource "aws_ssmcontacts_rotation" "test" { + contact_ids = [ + aws_ssmcontacts_contact.test_contact_one.arn, + ] + + name = %[1]q + + recurrence { + number_of_on_calls = 1 + recurrence_multiplier = 1 + monthly_settings { + day_of_month = 20 + hand_off_time = "08:00" + } + monthly_settings { + day_of_month = 13 + hand_off_time = "12:34" + } + monthly_settings { + day_of_month = 1 + hand_off_time = "04:58" + } + } + + time_zone_id = "Australia/Sydney" + + depends_on = [aws_ssmincidents_replication_set.test] +} + +data "aws_ssmcontacts_rotation" "test" { + arn = aws_ssmcontacts_rotation.test.arn +} +`, rName)) +} diff --git a/internal/service/ssmcontacts/rotation_test.go b/internal/service/ssmcontacts/rotation_test.go new file mode 100644 index 000000000000..08bfc5201abe --- /dev/null +++ b/internal/service/ssmcontacts/rotation_test.go @@ -0,0 +1,1013 @@ +package ssmcontacts_test + +import ( + "context" + "errors" + "fmt" + "regexp" + "strconv" + "strings" + "testing" + "time" + + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/ssmcontacts" + "github.com/aws/aws-sdk-go-v2/service/ssmcontacts/types" + sdkacctest "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/terraform" + "github.com/hashicorp/terraform-provider-aws/internal/acctest" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/create" + + tfssmcontacts "github.com/hashicorp/terraform-provider-aws/internal/service/ssmcontacts" + "github.com/hashicorp/terraform-provider-aws/names" +) + +func TestAccSSMContactsRotation_basic(t *testing.T) { + ctx := acctest.Context(t) + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_ssmcontacts_rotation.test" + contactResourceName := "aws_ssmcontacts_contact.test_contact_one" + + timeZoneId := "Australia/Sydney" + numberOfOncalls := 1 + recurrenceMultiplier := 1 + + resource.Test(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(ctx, t) + testAccPreCheck(ctx, t) + }, + ErrorCheck: acctest.ErrorCheck(t, names.SSMContactsEndpointID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckRotationDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccRotationConfig_basic(rName, recurrenceMultiplier, timeZoneId), + Check: resource.ComposeTestCheckFunc( + testAccCheckContactExists(ctx, contactResourceName), + testAccCheckRotationExists(ctx, resourceName), + resource.TestCheckResourceAttr(resourceName, "name", rName), + resource.TestCheckResourceAttr(resourceName, "time_zone_id", timeZoneId), + resource.TestCheckResourceAttr(resourceName, "recurrence.0.number_of_on_calls", strconv.Itoa(numberOfOncalls)), + resource.TestCheckResourceAttr(resourceName, "recurrence.0.recurrence_multiplier", strconv.Itoa(recurrenceMultiplier)), + resource.TestCheckResourceAttr(resourceName, "recurrence.0.daily_settings.#", "1"), + resource.TestCheckResourceAttr(resourceName, "recurrence.0.daily_settings.0", "01:00"), + resource.TestCheckResourceAttr(resourceName, "contact_ids.#", "1"), + acctest.CheckResourceAttrRegionalARN(resourceName, "contact_ids.0", "ssm-contacts", "contact/test-contact-one-for-"+rName), + acctest.MatchResourceAttrRegionalARN(resourceName, "arn", "ssm-contacts", regexp.MustCompile(`rotation\/+.`)), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + // We need to explicitly test destroying this resource instead of just using CheckDestroy, + // because CheckDestroy will run after the replication set has been destroyed and destroying + // the replication set will destroy all other resources. + Config: testAccRotationConfig_none(), + Check: testAccCheckRotationDestroy(ctx), + }, + }, + }) +} + +func TestAccSSMContactsRotation_disappears(t *testing.T) { + ctx := acctest.Context(t) + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_ssmcontacts_rotation.test" + timeZoneId := "Australia/Sydney" + recurrenceMultiplier := 1 + + resource.Test(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(ctx, t) + testAccPreCheck(ctx, t) + }, + ErrorCheck: acctest.ErrorCheck(t, names.SSMContactsEndpointID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckRotationDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccRotationConfig_basic(rName, recurrenceMultiplier, timeZoneId), + Check: resource.ComposeTestCheckFunc( + testAccCheckRotationExists(ctx, resourceName), + acctest.CheckResourceDisappears(ctx, acctest.Provider, tfssmcontacts.ResourceRotation(), resourceName), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +func TestAccSSMContactsRotation_updateRequiredFields(t *testing.T) { + ctx := acctest.Context(t) + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_ssmcontacts_rotation.test" + + iniTimeZoneId := "Australia/Sydney" + updTimeZoneId := "America/Los_Angeles" + iniRecurrenceMultiplier := 1 + updRecurrenceMultiplier := 2 + + resource.Test(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(ctx, t) + testAccPreCheck(ctx, t) + }, + ErrorCheck: acctest.ErrorCheck(t, names.SSMContactsEndpointID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckRotationDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccRotationConfig_basic(rName, iniRecurrenceMultiplier, iniTimeZoneId), + Check: resource.ComposeTestCheckFunc( + testAccCheckRotationExists(ctx, resourceName), + resource.TestCheckResourceAttr(resourceName, "time_zone_id", iniTimeZoneId), + resource.TestCheckResourceAttr(resourceName, "recurrence.0.recurrence_multiplier", strconv.Itoa(iniRecurrenceMultiplier)), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccRotationConfig_basic(rName, updRecurrenceMultiplier, updTimeZoneId), + Check: resource.ComposeTestCheckFunc( + testAccCheckRotationExists(ctx, resourceName), + resource.TestCheckResourceAttr(resourceName, "time_zone_id", updTimeZoneId), + resource.TestCheckResourceAttr(resourceName, "recurrence.0.recurrence_multiplier", strconv.Itoa(updRecurrenceMultiplier)), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccSSMContactsRotation_startTime(t *testing.T) { + ctx := acctest.Context(t) + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_ssmcontacts_rotation.test" + + iniStartTime := time.Now().UTC().AddDate(0, 0, 2).Format(time.RFC3339) + updStartTime := time.Now().UTC().AddDate(20, 2, 10).Format(time.RFC3339) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(ctx, t) + testAccPreCheck(ctx, t) + }, + ErrorCheck: acctest.ErrorCheck(t, names.SSMContactsEndpointID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckRotationDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccRotationConfig_startTime(rName, iniStartTime), + Check: resource.ComposeTestCheckFunc( + testAccCheckRotationExists(ctx, resourceName), + resource.TestCheckResourceAttr(resourceName, "start_time", iniStartTime), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccRotationConfig_startTime(rName, updStartTime), + Check: resource.ComposeTestCheckFunc( + testAccCheckRotationExists(ctx, resourceName), + resource.TestCheckResourceAttr(resourceName, "start_time", updStartTime), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccSSMContactsRotation_contactIds(t *testing.T) { + ctx := acctest.Context(t) + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_ssmcontacts_rotation.test" + firstContactResourceName := "aws_ssmcontacts_contact.test_contact_one" + secondContactResourceName := "aws_ssmcontacts_contact.test_contact_two" + thirdContactResourceName := "aws_ssmcontacts_contact.test_contact_three" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(ctx, t) + testAccPreCheck(ctx, t) + }, + ErrorCheck: acctest.ErrorCheck(t, names.SSMContactsEndpointID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckRotationDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccRotationConfig_twoContacts(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckRotationExists(ctx, resourceName), + testAccCheckContactExists(ctx, firstContactResourceName), + testAccCheckContactExists(ctx, secondContactResourceName), + resource.TestCheckResourceAttr(resourceName, "contact_ids.#", "2"), + acctest.CheckResourceAttrRegionalARN(resourceName, "contact_ids.0", "ssm-contacts", "contact/test-contact-one-for-"+rName), + acctest.CheckResourceAttrRegionalARN(resourceName, "contact_ids.1", "ssm-contacts", "contact/test-contact-two-for-"+rName), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccRotationConfig_threeContacts(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckRotationExists(ctx, resourceName), + testAccCheckContactExists(ctx, firstContactResourceName), + testAccCheckContactExists(ctx, secondContactResourceName), + testAccCheckContactExists(ctx, thirdContactResourceName), + resource.TestCheckResourceAttr(resourceName, "contact_ids.#", "3"), + acctest.CheckResourceAttrRegionalARN(resourceName, "contact_ids.0", "ssm-contacts", "contact/test-contact-one-for-"+rName), + acctest.CheckResourceAttrRegionalARN(resourceName, "contact_ids.1", "ssm-contacts", "contact/test-contact-two-for-"+rName), + acctest.CheckResourceAttrRegionalARN(resourceName, "contact_ids.2", "ssm-contacts", "contact/test-contact-three-for-"+rName), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccSSMContactsRotation_recurrence(t *testing.T) { + ctx := acctest.Context(t) + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_ssmcontacts_rotation.test" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(ctx, t) + testAccPreCheck(ctx, t) + }, + ErrorCheck: acctest.ErrorCheck(t, names.SSMContactsEndpointID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckRotationDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccRotationConfig_recurrenceDailySettings(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckRotationExists(ctx, resourceName), + resource.TestCheckResourceAttr(resourceName, "recurrence.0.daily_settings.#", "1"), + resource.TestCheckResourceAttr(resourceName, "recurrence.0.daily_settings.0", "18:00"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccRotationConfig_recurrenceOneMonthlySetting(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckRotationExists(ctx, resourceName), + resource.TestCheckResourceAttr(resourceName, "recurrence.0.monthly_settings.#", "1"), + resource.TestCheckResourceAttr(resourceName, "recurrence.0.monthly_settings.0.day_of_month", "20"), + resource.TestCheckResourceAttr(resourceName, "recurrence.0.monthly_settings.0.hand_off_time", "08:00"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccRotationConfig_recurrenceMultipleMonthlySetting(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckRotationExists(ctx, resourceName), + resource.TestCheckResourceAttr(resourceName, "recurrence.0.monthly_settings.#", "2"), + resource.TestCheckResourceAttr(resourceName, "recurrence.0.monthly_settings.0.day_of_month", "20"), + resource.TestCheckResourceAttr(resourceName, "recurrence.0.monthly_settings.0.hand_off_time", "08:00"), + resource.TestCheckResourceAttr(resourceName, "recurrence.0.monthly_settings.1.day_of_month", "13"), + resource.TestCheckResourceAttr(resourceName, "recurrence.0.monthly_settings.1.hand_off_time", "12:34"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccRotationConfig_recurrenceOneWeeklySettings(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckRotationExists(ctx, resourceName), + resource.TestCheckResourceAttr(resourceName, "recurrence.0.weekly_settings.#", "1"), + resource.TestCheckResourceAttr(resourceName, "recurrence.0.weekly_settings.0.day_of_week", "MON"), + resource.TestCheckResourceAttr(resourceName, "recurrence.0.weekly_settings.0.hand_off_time", "10:30"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccRotationConfig_recurrenceMultipleWeeklySettings(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckRotationExists(ctx, resourceName), + resource.TestCheckResourceAttr(resourceName, "recurrence.0.weekly_settings.#", "2"), + resource.TestCheckResourceAttr(resourceName, "recurrence.0.weekly_settings.0.day_of_week", "WED"), + resource.TestCheckResourceAttr(resourceName, "recurrence.0.weekly_settings.0.hand_off_time", "04:25"), + resource.TestCheckResourceAttr(resourceName, "recurrence.0.weekly_settings.1.day_of_week", "FRI"), + resource.TestCheckResourceAttr(resourceName, "recurrence.0.weekly_settings.1.hand_off_time", "15:57"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccRotationConfig_recurrenceOneShiftCoverages(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckRotationExists(ctx, resourceName), + resource.TestCheckResourceAttr(resourceName, "recurrence.0.shift_coverages.#", "1"), + resource.TestCheckResourceAttr(resourceName, "recurrence.0.shift_coverages.0.day_of_week", "MON"), + resource.TestCheckResourceAttr(resourceName, "recurrence.0.shift_coverages.0.coverage_times.0.start_time", "08:00"), + resource.TestCheckResourceAttr(resourceName, "recurrence.0.shift_coverages.0.coverage_times.0.end_time", "17:00"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccRotationConfig_recurrenceMultipleShiftCoverages(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckRotationExists(ctx, resourceName), + resource.TestCheckResourceAttr(resourceName, "recurrence.0.shift_coverages.#", "3"), + resource.TestCheckResourceAttr(resourceName, "recurrence.0.shift_coverages.0.day_of_week", "FRI"), + resource.TestCheckResourceAttr(resourceName, "recurrence.0.shift_coverages.0.coverage_times.0.start_time", "01:00"), + resource.TestCheckResourceAttr(resourceName, "recurrence.0.shift_coverages.0.coverage_times.0.end_time", "23:00"), + resource.TestCheckResourceAttr(resourceName, "recurrence.0.shift_coverages.1.day_of_week", "MON"), + resource.TestCheckResourceAttr(resourceName, "recurrence.0.shift_coverages.1.coverage_times.0.start_time", "01:00"), + resource.TestCheckResourceAttr(resourceName, "recurrence.0.shift_coverages.1.coverage_times.0.end_time", "23:00"), + resource.TestCheckResourceAttr(resourceName, "recurrence.0.shift_coverages.2.day_of_week", "WED"), + resource.TestCheckResourceAttr(resourceName, "recurrence.0.shift_coverages.2.coverage_times.0.start_time", "01:00"), + resource.TestCheckResourceAttr(resourceName, "recurrence.0.shift_coverages.2.coverage_times.0.end_time", "23:00"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccSSMContactsRotation_tags(t *testing.T) { + ctx := acctest.Context(t) + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_ssmcontacts_rotation.test" + + tagKey1 := sdkacctest.RandString(26) + tagVal1 := sdkacctest.RandString(26) + tagVal1Updated := sdkacctest.RandString(26) + tagKey2 := sdkacctest.RandString(26) + tagVal2 := sdkacctest.RandString(26) + tagVal2Updated := sdkacctest.RandString(26) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(ctx, t) + testAccPreCheck(ctx, t) + }, + ErrorCheck: acctest.ErrorCheck(t, names.SSMContactsEndpointID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckRotationDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccRotationConfig_basic(rName, 1, "Australia/Sydney"), + Check: resource.ComposeTestCheckFunc( + testAccCheckRotationExists(ctx, resourceName), + resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccRotationConfig_oneTag(rName, tagKey1, tagVal1), + Check: resource.ComposeTestCheckFunc( + testAccCheckRotationExists(ctx, resourceName), + resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), + resource.TestCheckResourceAttr(resourceName, "tags."+tagKey1, tagVal1), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccRotationConfig_multipleTags(rName, tagKey1, tagVal1, tagKey2, tagVal2), + Check: resource.ComposeTestCheckFunc( + testAccCheckRotationExists(ctx, resourceName), + resource.TestCheckResourceAttr(resourceName, "tags.%", "2"), + resource.TestCheckResourceAttr(resourceName, "tags."+tagKey1, tagVal1), + resource.TestCheckResourceAttr(resourceName, "tags."+tagKey2, tagVal2), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccRotationConfig_multipleTags(rName, tagKey1, tagVal1Updated, tagKey2, tagVal2Updated), + Check: resource.ComposeTestCheckFunc( + testAccCheckRotationExists(ctx, resourceName), + resource.TestCheckResourceAttr(resourceName, "tags.%", "2"), + resource.TestCheckResourceAttr(resourceName, "tags."+tagKey1, tagVal1Updated), + resource.TestCheckResourceAttr(resourceName, "tags."+tagKey2, tagVal2Updated), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccRotationConfig_oneTag(rName, tagKey1, tagVal1Updated), + Check: resource.ComposeTestCheckFunc( + testAccCheckRotationExists(ctx, resourceName), + resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), + resource.TestCheckResourceAttr(resourceName, "tags."+tagKey1, tagVal1Updated), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccRotationConfig_basic(rName, 1, "Australia/Sydney"), + Check: resource.ComposeTestCheckFunc( + testAccCheckRotationExists(ctx, resourceName), + resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccCheckRotationDestroy(ctx context.Context) resource.TestCheckFunc { + return func(s *terraform.State) error { + conn := acctest.Provider.Meta().(*conns.AWSClient).SSMContactsClient(ctx) + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_ssmcontacts_rotation" { + continue + } + + input := &ssmcontacts.GetRotationInput{ + RotationId: aws.String(rs.Primary.ID), + } + _, err := conn.GetRotation(ctx, input) + if err != nil { + if strings.Contains(err.Error(), "Invalid value provided - Account not found for the request") { + continue + } + + var nfe *types.ResourceNotFoundException + if errors.As(err, &nfe) { + return nil + } + return err + } + + return create.Error(names.SSMContacts, create.ErrActionCheckingDestroyed, tfssmcontacts.ResNameRotation, rs.Primary.ID, errors.New("not destroyed")) + } + + return nil + } +} + +func testAccCheckRotationExists(ctx context.Context, name string) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[name] + if !ok { + return create.Error(names.SSMContacts, create.ErrActionCheckingExistence, tfssmcontacts.ResNameRotation, name, errors.New("not found")) + } + + if rs.Primary.ID == "" { + return create.Error(names.SSMContacts, create.ErrActionCheckingExistence, tfssmcontacts.ResNameRotation, name, errors.New("not set")) + } + + conn := acctest.Provider.Meta().(*conns.AWSClient).SSMContactsClient(ctx) + _, err := tfssmcontacts.FindRotationByID(ctx, conn, rs.Primary.ID) + + if err != nil { + return create.Error(names.SSMContacts, create.ErrActionCheckingExistence, tfssmcontacts.ResNameRotation, rs.Primary.ID, err) + } + + return nil + } +} + +func testAccPreCheck(ctx context.Context, t *testing.T) { + conn := acctest.Provider.Meta().(*conns.AWSClient).SSMContactsClient(ctx) + + input := &ssmcontacts.ListRotationsInput{} + _, err := conn.ListRotations(ctx, input) + + if acctest.PreCheckSkipError(err) { + t.Skipf("skipping acceptance testing: %s", err) + } + + if err != nil { + // ListRotations returns a 400 ValidationException if not onboarded with a replication set, and isn't an awsErr + if strings.Contains(err.Error(), "Invalid value provided - Account not found for the request") { + return + } + + t.Fatalf("unexpected PreCheck error: %s", err) + } +} + +func testAccRotationConfig_none() string { + return fmt.Sprintf(` +resource "aws_ssmincidents_replication_set" "test" { + region { + name = %[1]q + } +} +`, acctest.Region()) +} + +func testAccRotationConfig_base(alias string) string { + return acctest.ConfigCompose( + testAccRotationConfig_none(), + fmt.Sprintf(` +resource "aws_ssmcontacts_contact" "test_contact_one" { + alias = "test-contact-one-for-%[1]s" + type = "PERSONAL" + + depends_on = [aws_ssmincidents_replication_set.test] +} +`, alias)) +} + +func testAccRotationConfig_secondContact(alias string) string { + return fmt.Sprintf(` +resource "aws_ssmcontacts_contact" "test_contact_two" { + alias = "test-contact-two-for-%[1]s" + type = "PERSONAL" + + depends_on = [aws_ssmincidents_replication_set.test] +} +`, alias) +} + +func testAccRotationConfig_thirdContact(alias string) string { + return fmt.Sprintf(` +resource "aws_ssmcontacts_contact" "test_contact_three" { + alias = "test-contact-three-for-%[1]s" + type = "PERSONAL" + + depends_on = [aws_ssmincidents_replication_set.test] +} +`, alias) +} + +func testAccRotationConfig_basic(rName string, recurrenceMultiplier int, timeZoneId string) string { + return acctest.ConfigCompose( + testAccRotationConfig_base(rName), + fmt.Sprintf(` +resource "aws_ssmcontacts_rotation" "test" { + contact_ids = [ + aws_ssmcontacts_contact.test_contact_one.arn + ] + + name = %[1]q + + recurrence { + number_of_on_calls = 1 + recurrence_multiplier = %[2]d + daily_settings = [ + "01:00" + ] + } + + time_zone_id = %[3]q + + depends_on = [aws_ssmincidents_replication_set.test] +}`, rName, recurrenceMultiplier, timeZoneId)) +} + +func testAccRotationConfig_startTime(rName, startTime string) string { + return acctest.ConfigCompose( + testAccRotationConfig_base(rName), + fmt.Sprintf(` +resource "aws_ssmcontacts_rotation" "test" { + contact_ids = [ + aws_ssmcontacts_contact.test_contact_one.arn + ] + + name = %[1]q + + recurrence { + number_of_on_calls = 1 + recurrence_multiplier = 1 + daily_settings = [ + "01:00" + ] + } + + start_time = %[2]q + + time_zone_id = "Australia/Sydney" + + depends_on = [aws_ssmincidents_replication_set.test] +}`, rName, startTime)) +} + +func testAccRotationConfig_twoContacts(rName string) string { + return acctest.ConfigCompose( + testAccRotationConfig_base(rName), + testAccRotationConfig_secondContact(rName), + fmt.Sprintf(` +resource "aws_ssmcontacts_rotation" "test" { + contact_ids = [ + aws_ssmcontacts_contact.test_contact_one.arn, + aws_ssmcontacts_contact.test_contact_two.arn + ] + + name = %[1]q + + recurrence { + number_of_on_calls = 1 + recurrence_multiplier = 1 + daily_settings = [ + "01:00" + ] + } + + time_zone_id = "Australia/Sydney" + + depends_on = [aws_ssmincidents_replication_set.test] +}`, rName)) +} + +func testAccRotationConfig_threeContacts(rName string) string { + return acctest.ConfigCompose( + testAccRotationConfig_base(rName), + testAccRotationConfig_secondContact(rName), + testAccRotationConfig_thirdContact(rName), + fmt.Sprintf(` +resource "aws_ssmcontacts_rotation" "test" { + contact_ids = [ + aws_ssmcontacts_contact.test_contact_one.arn, + aws_ssmcontacts_contact.test_contact_two.arn, + aws_ssmcontacts_contact.test_contact_three.arn + ] + + name = %[1]q + + recurrence { + number_of_on_calls = 1 + recurrence_multiplier = 1 + daily_settings = [ + "01:00" + ] + } + + time_zone_id = "Australia/Sydney" + + depends_on = [aws_ssmincidents_replication_set.test] +}`, rName)) +} + +func testAccRotationConfig_recurrenceDailySettings(rName string) string { + return acctest.ConfigCompose( + testAccRotationConfig_base(rName), + fmt.Sprintf(` +resource "aws_ssmcontacts_rotation" "test" { + contact_ids = [ + aws_ssmcontacts_contact.test_contact_one.arn + ] + + name = %[1]q + + recurrence { + number_of_on_calls = 1 + recurrence_multiplier = 1 + daily_settings = [ + "18:00" + ] + } + + time_zone_id = "Australia/Sydney" + + depends_on = [aws_ssmincidents_replication_set.test] +}`, rName)) +} + +func testAccRotationConfig_recurrenceOneMonthlySetting(rName string) string { + return acctest.ConfigCompose( + testAccRotationConfig_base(rName), + fmt.Sprintf(` +resource "aws_ssmcontacts_rotation" "test" { + contact_ids = [ + aws_ssmcontacts_contact.test_contact_one.arn + ] + + name = %[1]q + + recurrence { + number_of_on_calls = 1 + recurrence_multiplier = 1 + monthly_settings { + day_of_month = 20 + hand_off_time = "08:00" + } + } + + time_zone_id = "Australia/Sydney" + + depends_on = [aws_ssmincidents_replication_set.test] +}`, rName)) +} + +func testAccRotationConfig_recurrenceMultipleMonthlySetting(rName string) string { + return acctest.ConfigCompose( + testAccRotationConfig_base(rName), + fmt.Sprintf(` +resource "aws_ssmcontacts_rotation" "test" { + contact_ids = [ + aws_ssmcontacts_contact.test_contact_one.arn + ] + + name = %[1]q + + recurrence { + number_of_on_calls = 1 + recurrence_multiplier = 1 + monthly_settings { + day_of_month = 20 + hand_off_time = "08:00" + } + monthly_settings { + day_of_month = 13 + hand_off_time = "12:34" + } + } + + time_zone_id = "Australia/Sydney" + + depends_on = [aws_ssmincidents_replication_set.test] +}`, rName)) +} + +func testAccRotationConfig_recurrenceOneWeeklySettings(rName string) string { + return acctest.ConfigCompose( + testAccRotationConfig_base(rName), + fmt.Sprintf(` +resource "aws_ssmcontacts_rotation" "test" { + contact_ids = [ + aws_ssmcontacts_contact.test_contact_one.arn + ] + + name = %[1]q + + recurrence { + number_of_on_calls = 1 + recurrence_multiplier = 1 + weekly_settings { + day_of_week = "MON" + hand_off_time = "10:30" + } + } + + time_zone_id = "Australia/Sydney" + + depends_on = [aws_ssmincidents_replication_set.test] +}`, rName)) +} + +func testAccRotationConfig_recurrenceMultipleWeeklySettings(rName string) string { + return acctest.ConfigCompose( + testAccRotationConfig_base(rName), + fmt.Sprintf(` +resource "aws_ssmcontacts_rotation" "test" { + contact_ids = [ + aws_ssmcontacts_contact.test_contact_one.arn + ] + + name = %[1]q + + recurrence { + number_of_on_calls = 1 + recurrence_multiplier = 1 + weekly_settings { + day_of_week = "WED" + hand_off_time = "04:25" + } + + weekly_settings { + day_of_week = "FRI" + hand_off_time = "15:57" + } + } + + time_zone_id = "Australia/Sydney" + + depends_on = [aws_ssmincidents_replication_set.test] +}`, rName)) +} + +func testAccRotationConfig_recurrenceOneShiftCoverages(rName string) string { + return acctest.ConfigCompose( + testAccRotationConfig_base(rName), + fmt.Sprintf(` +resource "aws_ssmcontacts_rotation" "test" { + contact_ids = [ + aws_ssmcontacts_contact.test_contact_one.arn + ] + + name = %[1]q + + recurrence { + number_of_on_calls = 1 + recurrence_multiplier = 1 + daily_settings = [ + "09:00" + ] + shift_coverages { + day_of_week = "MON" + coverage_times { + start_time = "08:00" + end_time = "17:00" + } + } + } + + time_zone_id = "Australia/Sydney" + + depends_on = [aws_ssmincidents_replication_set.test] +}`, rName)) +} + +func testAccRotationConfig_recurrenceMultipleShiftCoverages(rName string) string { + return acctest.ConfigCompose( + testAccRotationConfig_base(rName), + fmt.Sprintf(` +resource "aws_ssmcontacts_rotation" "test" { + contact_ids = [ + aws_ssmcontacts_contact.test_contact_one.arn + ] + + name = %[1]q + + recurrence { + number_of_on_calls = 1 + recurrence_multiplier = 1 + daily_settings = [ + "09:00" + ] + shift_coverages { + day_of_week = "MON" + coverage_times { + start_time = "01:00" + end_time = "23:00" + } + } + shift_coverages { + day_of_week = "WED" + coverage_times { + start_time = "01:00" + end_time = "23:00" + } + } + shift_coverages { + day_of_week = "FRI" + coverage_times { + start_time = "01:00" + end_time = "23:00" + } + } + } + + time_zone_id = "Australia/Sydney" + + depends_on = [aws_ssmincidents_replication_set.test] +}`, rName)) +} + +func testAccRotationConfig_oneTag(rName, tagKey, tagValue string) string { + return acctest.ConfigCompose( + testAccRotationConfig_base(rName), + fmt.Sprintf(` +resource "aws_ssmcontacts_rotation" "test" { + contact_ids = [ + aws_ssmcontacts_contact.test_contact_one.arn + ] + + name = %[1]q + + recurrence { + number_of_on_calls = 1 + recurrence_multiplier = 1 + daily_settings = [ + "18:00" + ] + } + + tags = { + %[2]q = %[3]q + } + + time_zone_id = "Australia/Sydney" + + depends_on = [aws_ssmincidents_replication_set.test] +}`, rName, tagKey, tagValue)) +} + +func testAccRotationConfig_multipleTags(rName, tagKey1, tagVal1, tagKey2, tagVal2 string) string { + return acctest.ConfigCompose( + testAccRotationConfig_base(rName), + fmt.Sprintf(` +resource "aws_ssmcontacts_rotation" "test" { + contact_ids = [ + aws_ssmcontacts_contact.test_contact_one.arn + ] + + name = %[1]q + + recurrence { + number_of_on_calls = 1 + recurrence_multiplier = 1 + daily_settings = [ + "18:00" + ] + } + + tags = { + %[2]q = %[3]q + %[4]q = %[5]q + } + + time_zone_id = "Australia/Sydney" + + depends_on = [aws_ssmincidents_replication_set.test] +}`, rName, tagKey1, tagVal1, tagKey2, tagVal2)) +} diff --git a/internal/service/ssmcontacts/service_package_gen.go b/internal/service/ssmcontacts/service_package_gen.go index 971fb749addd..a006baa0f058 100644 --- a/internal/service/ssmcontacts/service_package_gen.go +++ b/internal/service/ssmcontacts/service_package_gen.go @@ -36,6 +36,10 @@ func (p *servicePackage) SDKDataSources(ctx context.Context) []*types.ServicePac Factory: DataSourcePlan, TypeName: "aws_ssmcontacts_plan", }, + { + Factory: DataSourceRotation, + TypeName: "aws_ssmcontacts_rotation", + }, } } @@ -59,6 +63,14 @@ func (p *servicePackage) SDKResources(ctx context.Context) []*types.ServicePacka TypeName: "aws_ssmcontacts_plan", Name: "Plan", }, + { + Factory: ResourceRotation, + TypeName: "aws_ssmcontacts_rotation", + Name: "Rotation", + Tags: &types.ServicePackageResourceTags{ + IdentifierAttribute: "id", + }, + }, } } diff --git a/internal/service/ssmcontacts/ssmcontacts_test.go b/internal/service/ssmcontacts/ssmcontacts_test.go index 2c872cd9ee6f..dc7b39b7969b 100644 --- a/internal/service/ssmcontacts/ssmcontacts_test.go +++ b/internal/service/ssmcontacts/ssmcontacts_test.go @@ -51,6 +51,19 @@ func TestAccSSMContacts_serial(t *testing.T) { "basic": testPlanDataSource_basic, "channelTargetInfo": testPlanDataSource_channelTargetInfo, }, + "Rotation Resource Tests": { + "basic": TestAccSSMContactsRotation_basic, + "disappears": TestAccSSMContactsRotation_disappears, + "update": TestAccSSMContactsRotation_updateRequiredFields, + "startTime": TestAccSSMContactsRotation_startTime, + "contactIds": TestAccSSMContactsRotation_contactIds, + "recurrence": TestAccSSMContactsRotation_recurrence, + }, + "Rotation Data Source Tests": { + "basic": TestAccSSMContactsRotationDataSource_basic, + "dailySettings": TestAccSSMContactsRotationDataSource_dailySettings, + "monthlySettings": TestAccSSMContactsRotationDataSource_monthlySettings, + }, } acctest.RunSerialTests2Levels(t, testCases, 0) diff --git a/website/docs/d/ssmcontacts_rotation.html.markdown b/website/docs/d/ssmcontacts_rotation.html.markdown new file mode 100644 index 000000000000..6221c7ce09da --- /dev/null +++ b/website/docs/d/ssmcontacts_rotation.html.markdown @@ -0,0 +1,52 @@ +--- +subcategory: "SSM Contacts" +layout: "aws" +page_title: "AWS: aws_ssmcontacts_rotation" +description: |- + Provides a Terraform data source for managing a Contacts Rotation in AWS Systems Manager Incident Manager +--- + +# Data Source: aws_ssmcontacts_rotation + +Provides a Terraform data source for managing a Contacts Rotation in AWS Systems Manager Incident Manager + +## Example Usage + +### Basic Usage + +```terraform +data "aws_ssmcontacts_rotation" "example" { + arn = "exampleARN" +} +``` + +## Argument Reference + +The following arguments are required: + +* `arn` - (Required) The Amazon Resource Name (ARN) of the rotation. + +## Attributes Reference + +In addition to all arguments above, the following attributes are exported: + +* `contact_ids` - The Amazon Resource Names (ARNs) of the contacts to add to the rotation. The order in which you list the contacts is their shift order in the rotation schedule. +* `name` - The name for the rotation. +* `time_zone_id` - The time zone to base the rotation’s activity on in Internet Assigned Numbers Authority (IANA) format. +* `recurrence` - Information about when an on-call rotation is in effect and how long the rotation period lasts. Exactly one of either `daily_settings`, `monthly_settings`, or `weekly_settings` must be populated. + * `number_of_oncalls` - The number of contacts, or shift team members designated to be on call concurrently during a shift. + * `recurrence_multiplier` - The number of days, weeks, or months a single rotation lasts. + * `daily_settings` - - Information about on-call rotations that recur daily. Composed of a list of times, in 24-hour format, for when daily on-call shift rotation begins. + * `monthly_settings` - Information about on-call rotations that recur monthly. + * `day_of_month` - The day of the month when monthly recurring on-call rotations begin. + * `hand_off_time` The time of day, in 24-hour format, when a monthly recurring on-call shift rotation begins. + * `weekly_settings` - Information about rotations that recur weekly. + * `day_of_week` - The day of the week when weekly recurring on-call shift rotations begins. + * `hand_off_time` - The time of day, in 24-hour format, when a weekly recurring on-call shift rotation begins. + * `shift_coverages` - Information about the days of the week that the on-call rotation coverage includes. + * `coverage_times` - Information about when an on-call shift begins and ends. + * `end_time` - The time, in 24-hour format,, hat the on-call rotation shift ends. + * `start_time` - The time, in 24-hour format, that the on-call rotation shift begins. + * `day_of_week` - The day of the week when the shift coverage occurs. +* `start_time` - The date and time, in RFC 3339 format, that the rotation goes into effect. +* `tags` - A map of tags to assign to the resource. diff --git a/website/docs/r/ssmcontacts_rotation.html.markdown b/website/docs/r/ssmcontacts_rotation.html.markdown new file mode 100644 index 000000000000..182c34ae4f7f --- /dev/null +++ b/website/docs/r/ssmcontacts_rotation.html.markdown @@ -0,0 +1,184 @@ +--- +subcategory: "SSM Contacts" +layout: "aws" +page_title: "AWS: aws_ssmcontacts_rotation" +description: |- + Provides a Terraform resource for managing a Contacts Rotation in AWS Systems Manager Incident Manager. +--- + +# Resource: aws_ssmcontacts_rotation + +Provides a Terraform resource for managing a Contacts Rotation in AWS Systems Manager Incident Manager. + +## Example Usage + +### Basic Usage + +```terraform +resource "aws_ssmcontacts_rotation" "example" { + contact_ids = [ + aws_ssmcontacts_contact.example.arn + ] + + name = "rotation" + + recurrence { + number_of_on_calls = 1 + recurrence_multiplier = 1 + daily_settings = [ + "01:00" + ] + } + + time_zone_id = "Australia/Sydney" + + depends_on = [aws_ssmincidents_replication_set.example] +} +``` + +### Usage with Weekly Settings and Shift Coverages Fields + +```terraform +resource "aws_ssmcontacts_rotation" "example" { + contact_ids = [ + aws_ssmcontacts_contact.example.arn + ] + + name = "rotation" + + recurrence { + number_of_on_calls = 1 + recurrence_multiplier = 1 + weekly_settings { + day_of_week = "MON" + hand_off_time = "04:25" + } + weekly_settings { + day_of_week = "WED" + hand_off_time = "07:34" + } + weekly_settings { + day_of_week = "FRI" + hand_off_time = "15:57" + } + shift_coverages { + day_of_week = "MON" + coverage_times { + start_time = "01:00" + end_time = "23:00" + } + } + shift_coverages { + day_of_week = "WED" + coverage_times { + start_time = "01:00" + end_time = "23:00" + } + } + shift_coverages { + day_of_week = "FRI" + coverage_times { + start_time = "01:00" + end_time = "23:00" + } + } + } + + start_time = "2023-07-20T02:21:49+00:00" + + time_zone_id = "Australia/Sydney" + + tags = { + key1 = "tag1" + key2 = "tag2" + } + + depends_on = [aws_ssmincidents_replication_set.example] +} +``` + +### Usage with Monthly Settings Fields + +```terraform +resource "aws_ssmcontacts_rotation" "example" { + contact_ids = [ + aws_ssmcontacts_contact.example.arn, + ] + + name = "rotation" + + recurrence { + number_of_on_calls = 1 + recurrence_multiplier = 1 + monthly_settings { + day_of_month = 20 + hand_off_time = "08:00" + } + monthly_settings { + day_of_month = 13 + hand_off_time = "12:34" + } + monthly_settings { + day_of_month = 1 + hand_off_time = "04:58" + } + } + + time_zone_id = "Australia/Sydney" + + depends_on = [aws_ssmincidents_replication_set.test] +} +``` + +## Argument Reference + +~> **NOTE:** A rotation implicitly depends on a replication set. If you configured your replication set in Terraform, we recommend you add it to the `depends_on` argument for the Terraform Contact Resource. + +The following arguments are required: + +* `contact_ids` - (Required) Amazon Resource Names (ARNs) of the contacts to add to the rotation. The order in which you list the contacts is their shift order in the rotation schedule. +* `name` - (Required) The name for the rotation. +* `time_zone_id` - (Required) The time zone to base the rotation’s activity on in Internet Assigned Numbers Authority (IANA) format. +* `recurrence` - (Required) Information about when an on-call rotation is in effect and how long the rotation period lasts. Exactly one of either `daily_settings`, `monthly_settings`, or `weekly_settings` must be populated. + * `number_of_oncalls` - (Required) The number of contacts, or shift team members designated to be on call concurrently during a shift. + * `recurrence_multiplier` - (Required) The number of days, weeks, or months a single rotation lasts. + * `daily_settings` - (Optional) Information about on-call rotations that recur daily. Composed of a list of times, in 24-hour format, for when daily on-call shift rotations begin. + * `monthly_settings` - (Optional) Information about on-call rotations that recur monthly. + * `day_of_month` - (Required) The day of the month when monthly recurring on-call rotations begin. + * `hand_off_time` - (Required) The time of day, in 24-hour format, when a monthly recurring on-call shift rotation begins. + * `weekly_settings` - (Optional) Information about rotations that recur weekly. + * `day_of_week` - (Required) The day of the week when weekly recurring on-call shift rotations begins. + * `hand_off_time` - (Required) The time of day, in 24-hour format, when a weekly recurring on-call shift rotation begins. + * `shift_coverages` - (Optional) Information about the days of the week that the on-call rotation coverage includes. + * `coverage_times` - (Required) Information about when an on-call shift begins and ends. + * `end_time` - (Optional) The time, in 24-hour format, that the on-call rotation shift ends. + * `start_time` - (Optional) The time, in 24-hour format, that the on-call rotation shift begins. + * `day_of_week` - (Required) The day of the week when the shift coverage occurs. + +The following arguments are optional: + +* `start_time` - (Optional) The date and time, in RFC 3339 format, that the rotation goes into effect. +* `tags` - (Optional) A map of tags to assign to the resource. + +## Attributes Reference + +In addition to all arguments above, the following attributes are exported: + +* `arn` - The Amazon Resource Name (ARN) of the rotation. +* `tags_all` - A map of tags assigned to the resource, including those inherited from the provider [`default_tags` configuration block](https://registry.terraform.io/providers/hashicorp/aws/latest/docs#default_tags-configuration-block). + +## Timeouts + +[Configuration options](https://developer.hashicorp.com/terraform/language/resources/syntax#operation-timeouts): + +* `create` - (Default `30m`) +* `update` - (Default `30m`) +* `delete` - (Default `30m`) + +## Import + +An Incident Manager Contacts Rotation can be imported using the `ARN`. For example: + +``` +$ terraform import aws_ssmcontacts_rotation.example {ARNValue} +``` From 86dd6c9120b9d35453bedd54622b18527d8a944f Mon Sep 17 00:00:00 2001 From: Adrian Johnson Date: Thu, 21 Dec 2023 21:04:58 -0600 Subject: [PATCH 02/22] aws_ssmcontacts_rotation: convert to framework --- internal/framework/flex/int.go | 10 + internal/service/ssmcontacts/find.go | 16 +- internal/service/ssmcontacts/flex.go | 98 ++-- internal/service/ssmcontacts/planmodifiers.go | 62 +++ internal/service/ssmcontacts/rotation.go | 18 +- internal/service/ssmcontacts/rotation_fw.go | 428 ++++++++++++++++++ .../ssmcontacts/service_package_gen.go | 18 +- internal/service/ssmcontacts/sweep.go | 64 +++ internal/sweep/register_gen_test.go | 2 + 9 files changed, 642 insertions(+), 74 deletions(-) create mode 100644 internal/service/ssmcontacts/planmodifiers.go create mode 100644 internal/service/ssmcontacts/rotation_fw.go create mode 100644 internal/service/ssmcontacts/sweep.go diff --git a/internal/framework/flex/int.go b/internal/framework/flex/int.go index ed8bca2be9e1..c2ff8fcf119a 100644 --- a/internal/framework/flex/int.go +++ b/internal/framework/flex/int.go @@ -67,3 +67,13 @@ func Int32FromFramework(ctx context.Context, v types.Int64) *int32 { return output } + +// Int32ValueFromFramework coverts a Framework Int64 value to an int32 pointer. +// A null Int64 is converted to a nil int32 pointer. +func Int32ValueFromFramework(ctx context.Context, v types.Int64) int32 { + var output int32 + + panicOnError(Expand(ctx, v, &output)) + + return output +} diff --git a/internal/service/ssmcontacts/find.go b/internal/service/ssmcontacts/find.go index e266a1f5d685..a460e4754181 100644 --- a/internal/service/ssmcontacts/find.go +++ b/internal/service/ssmcontacts/find.go @@ -10,7 +10,9 @@ import ( "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/service/ssmcontacts" "github.com/aws/aws-sdk-go-v2/service/ssmcontacts/types" + awstypes "github.com/aws/aws-sdk-go-v2/service/ssmcontacts/types" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry" + "github.com/hashicorp/terraform-provider-aws/internal/errs" "github.com/hashicorp/terraform-provider-aws/internal/tfresource" ) @@ -67,15 +69,15 @@ func FindRotationByID(ctx context.Context, conn *ssmcontacts.Client, id string) RotationId: aws.String(id), } out, err := conn.GetRotation(ctx, in) - if err != nil { - var nfe *types.ResourceNotFoundException - if errors.As(err, &nfe) { - return nil, &retry.NotFoundError{ - LastError: err, - LastRequest: in, - } + + if errs.IsA[*awstypes.ResourceNotFoundException](err) { + return nil, &retry.NotFoundError{ + LastError: err, + LastRequest: in, } + } + if err != nil { return nil, err } diff --git a/internal/service/ssmcontacts/flex.go b/internal/service/ssmcontacts/flex.go index 65642964d438..e0f8b9202abc 100644 --- a/internal/service/ssmcontacts/flex.go +++ b/internal/service/ssmcontacts/flex.go @@ -218,37 +218,37 @@ func flattenHandOffTime(handOffTime *types.HandOffTime) string { return fmt.Sprintf("%02d:%02d", handOffTime.HourOfDay, handOffTime.MinuteOfHour) } -func expandRecurrence(recurrence []interface{}, ctx context.Context) *types.RecurrenceSettings { - c := &types.RecurrenceSettings{} - - recurrenceSettings := recurrence[0].(map[string]interface{}) - - if v, ok := recurrenceSettings["daily_settings"].([]interface{}); ok && v != nil { - c.DailySettings = expandDailySettings(v) - } - - if v, ok := recurrenceSettings["monthly_settings"].([]interface{}); ok && v != nil { - c.MonthlySettings = expandMonthlySettings(v) - } - - if v, ok := recurrenceSettings["number_of_on_calls"].(int); ok { - c.NumberOfOnCalls = aws.Int32(int32(v)) - } - - if v, ok := recurrenceSettings["recurrence_multiplier"].(int); ok { - c.RecurrenceMultiplier = aws.Int32(int32(v)) - } - - if v, ok := recurrenceSettings["shift_coverages"].([]interface{}); ok && v != nil { - c.ShiftCoverages = expandShiftCoverages(v) - } - - if v, ok := recurrenceSettings["weekly_settings"].([]interface{}); ok && v != nil { - c.WeeklySettings = expandWeeklySettings(v) - } - - return c -} +//func expandRecurrence(recurrence []interface{}, ctx context.Context) *types.RecurrenceSettings { +// c := &types.RecurrenceSettings{} +// +// recurrenceSettings := recurrence[0].(map[string]interface{}) +// +// if v, ok := recurrenceSettings["daily_settings"].([]interface{}); ok && v != nil { +// c.DailySettings = expandDailySettings(v) +// } +// +// if v, ok := recurrenceSettings["monthly_settings"].([]interface{}); ok && v != nil { +// c.MonthlySettings = expandMonthlySettings(v) +// } +// +// if v, ok := recurrenceSettings["number_of_on_calls"].(int); ok { +// c.NumberOfOnCalls = aws.Int32(int32(v)) +// } +// +// if v, ok := recurrenceSettings["recurrence_multiplier"].(int); ok { +// c.RecurrenceMultiplier = aws.Int32(int32(v)) +// } +// +// if v, ok := recurrenceSettings["shift_coverages"].([]interface{}); ok && v != nil { +// c.ShiftCoverages = expandShiftCoverages(v) +// } +// +// if v, ok := recurrenceSettings["weekly_settings"].([]interface{}); ok && v != nil { +// c.WeeklySettings = expandWeeklySettings(v) +// } +// +// return c +//} func flattenRecurrence(recurrence *types.RecurrenceSettings, ctx context.Context) []interface{} { var result []interface{} @@ -357,24 +357,24 @@ func flattenMonthlySettings(monthlySettings []types.MonthlySetting) []interface{ return result } -func expandShiftCoverages(shiftCoverages []interface{}) map[string][]types.CoverageTime { - if len(shiftCoverages) == 0 { - return nil - } - - result := make(map[string][]types.CoverageTime) - - for _, shiftCoverage := range shiftCoverages { - shiftCoverageData := shiftCoverage.(map[string]interface{}) - - dayOfWeek := shiftCoverageData["day_of_week"].(string) - coverageTimes := expandCoverageTimes(shiftCoverageData["coverage_times"].([]interface{})) - - result[dayOfWeek] = coverageTimes - } - - return result -} +//func expandShiftCoverages(shiftCoverages []interface{}) map[string][]types.CoverageTime { +// if len(shiftCoverages) == 0 { +// return nil +// } +// +// result := make(map[string][]types.CoverageTime) +// +// for _, shiftCoverage := range shiftCoverages { +// shiftCoverageData := shiftCoverage.(map[string]interface{}) +// +// dayOfWeek := shiftCoverageData["day_of_week"].(string) +// coverageTimes := expandCoverageTimes(shiftCoverageData["coverage_times"].([]interface{})) +// +// result[dayOfWeek] = coverageTimes +// } +// +// return result +//} func flattenShiftCoverages(shiftCoverages map[string][]types.CoverageTime) []interface{} { if len(shiftCoverages) == 0 { diff --git a/internal/service/ssmcontacts/planmodifiers.go b/internal/service/ssmcontacts/planmodifiers.go new file mode 100644 index 000000000000..6617819a2f39 --- /dev/null +++ b/internal/service/ssmcontacts/planmodifiers.go @@ -0,0 +1,62 @@ +package ssmcontacts + +import ( + "context" + "sort" + + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" +) + +var _ planmodifier.List = shiftCoveragesPlanModifier{} + +func ShiftCoveragesPlanModifier() planmodifier.List { + return &shiftCoveragesPlanModifier{} +} + +type shiftCoveragesPlanModifier struct{} + +func (s shiftCoveragesPlanModifier) PlanModifyList(ctx context.Context, req planmodifier.ListRequest, resp *planmodifier.ListResponse) { + if req.PlanValue.IsNull() { + return + } + + if req.PlanValue.IsUnknown() { + return + } + + if req.ConfigValue.IsUnknown() { + return + } + + var plan, state []shiftCoveragesData + resp.Diagnostics.Append(req.PlanValue.ElementsAs(ctx, &plan, false)...) + resp.Diagnostics.Append(req.StateValue.ElementsAs(ctx, &state, false)...) + + req.PlanValue.ElementsAs(ctx, &plan, false) + if resp.Diagnostics.HasError() { + return + } + + sort.Slice(plan, func(i, j int) bool { + return plan[i].MapBlockKey.ValueString() < plan[j].MapBlockKey.ValueString() + }) + + sort.Slice(state, func(i, j int) bool { + return state[i].MapBlockKey.ValueString() < state[j].MapBlockKey.ValueString() + }) + + isEqual := cmp.Diff(plan, state) == "" + + if isEqual { + resp.PlanValue = req.StateValue + } +} + +func (s shiftCoveragesPlanModifier) Description(_ context.Context) string { + return "Suppress diff for shift_coverages" +} + +func (s shiftCoveragesPlanModifier) MarkdownDescription(ctx context.Context) string { + return s.Description(ctx) +} diff --git a/internal/service/ssmcontacts/rotation.go b/internal/service/ssmcontacts/rotation.go index 238b19e3e348..c11aa2840d09 100644 --- a/internal/service/ssmcontacts/rotation.go +++ b/internal/service/ssmcontacts/rotation.go @@ -3,14 +3,15 @@ package ssmcontacts import ( "context" "errors" + "log" + "regexp" + "time" + "github.com/google/go-cmp/cmp" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/customdiff" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" "github.com/hashicorp/terraform-provider-aws/internal/enum" "github.com/hashicorp/terraform-provider-aws/internal/flex" - "log" - "regexp" - "time" "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/service/ssmcontacts" @@ -25,7 +26,6 @@ import ( "github.com/hashicorp/terraform-provider-aws/names" ) -// @SDKResource("aws_ssmcontacts_rotation") func ResourceRotation() *schema.Resource { return &schema.Resource{ CreateWithoutTimeout: resourceRotationCreate, @@ -187,9 +187,9 @@ func ResourceRotation() *schema.Resource { } } -const ( - ResNameRotation = "Rotation" -) +//const ( +// ResNameRotation = "Rotation" +//) var handOffTimeValidator = validation.StringMatch(regexp.MustCompile(`^\d\d:\d\d$`), "Time must be in 24-hour time format, e.g. \"01:00\"") @@ -199,7 +199,7 @@ func resourceRotationCreate(ctx context.Context, d *schema.ResourceData, meta in in := &ssmcontacts.CreateRotationInput{ ContactIds: flex.ExpandStringValueList(d.Get("contact_ids").([]interface{})), Name: aws.String(d.Get("name").(string)), - Recurrence: expandRecurrence(d.Get("recurrence").([]interface{}), ctx), + // Recurrence: expandRecurrence(d.Get("recurrence").([]interface{}), ctx), Tags: getTagsIn(ctx), TimeZoneId: aws.String(d.Get("time_zone_id").(string)), } @@ -269,7 +269,7 @@ func resourceRotationUpdate(ctx context.Context, d *schema.ResourceData, meta in } // Recurrence is a required field, but we don't want to force an update every time if no changes - in.Recurrence = expandRecurrence(d.Get("recurrence").([]interface{}), ctx) + //in.Recurrence = expandRecurrence(d.Get("recurrence").([]interface{}), ctx) if d.HasChanges("recurrence") { update = true } diff --git a/internal/service/ssmcontacts/rotation_fw.go b/internal/service/ssmcontacts/rotation_fw.go new file mode 100644 index 000000000000..4b460ec299ce --- /dev/null +++ b/internal/service/ssmcontacts/rotation_fw.go @@ -0,0 +1,428 @@ +// Code generated by tools/tfsdk2fw/main.go. Manual editing is required. + +package ssmcontacts + +import ( + "context" + + "github.com/aws/aws-sdk-go-v2/service/ssmcontacts" + awstypes "github.com/aws/aws-sdk-go-v2/service/ssmcontacts/types" + "github.com/aws/aws-sdk-go/aws" + "github.com/hashicorp/terraform-plugin-framework-validators/listvalidator" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/listplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-log/tflog" + "github.com/hashicorp/terraform-provider-aws/internal/create" + "github.com/hashicorp/terraform-provider-aws/internal/errs" + "github.com/hashicorp/terraform-provider-aws/internal/framework" + "github.com/hashicorp/terraform-provider-aws/internal/framework/flex" + fwtypes "github.com/hashicorp/terraform-provider-aws/internal/framework/types" + tftags "github.com/hashicorp/terraform-provider-aws/internal/tags" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" + "github.com/hashicorp/terraform-provider-aws/names" +) + +const ( + ResNameRotation = "Rotation" +) + +// @FrameworkResource(name="Rotation") +// @Tags(identifierAttribute="arn") +func newResourceRotation(context.Context) (resource.ResourceWithConfigure, error) { + r := &resourceRotation{} + + return r, nil +} + +type resourceRotation struct { + framework.ResourceWithConfigure +} + +func (r *resourceRotation) Metadata(_ context.Context, request resource.MetadataRequest, response *resource.MetadataResponse) { + response.TypeName = "aws_ssmcontacts_rotation" +} + +// Schema returns the schema for this resource. +func (r *resourceRotation) Schema(ctx context.Context, request resource.SchemaRequest, response *resource.SchemaResponse) { + s := schema.Schema{ + Attributes: map[string]schema.Attribute{ + "arn": framework.ARNAttributeComputedOnly(), + "contact_ids": schema.ListAttribute{ + CustomType: fwtypes.ListOfStringType, + ElementType: types.StringType, + Required: true, + }, + "id": framework.IDAttribute(), + "name": schema.StringAttribute{ + Required: true, + }, + "start_time": schema.StringAttribute{ + CustomType: fwtypes.TimestampType, + Optional: true, + }, + names.AttrTags: tftags.TagsAttribute(), + names.AttrTagsAll: tftags.TagsAttributeComputedOnly(), + "time_zone_id": schema.StringAttribute{ + Required: true, + }, + }, + Blocks: map[string]schema.Block{ + "recurrence": schema.ListNestedBlock{ + CustomType: fwtypes.NewListNestedObjectTypeOf[recurrenceData](ctx), + Validators: []validator.List{ + listvalidator.IsRequired(), + listvalidator.SizeAtMost(1), + }, + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "number_of_on_calls": schema.Int64Attribute{ + Required: true, + }, + "recurrence_multiplier": schema.Int64Attribute{ + Required: true, + }, + }, + Blocks: map[string]schema.Block{ + "daily_settings": handOffTimeSchema(ctx, nil), + "monthly_settings": schema.ListNestedBlock{ + CustomType: fwtypes.NewListNestedObjectTypeOf[monthlySettingsData](ctx), + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "day_of_month": schema.Int64Attribute{ + Required: true, + }, + }, + Blocks: map[string]schema.Block{ + "hand_off_time": handOffTimeSchema(ctx, aws.Int(1)), + }, + }, + }, + "shift_coverages": schema.ListNestedBlock{ + CustomType: fwtypes.NewListNestedObjectTypeOf[shiftCoveragesData](ctx), + PlanModifiers: []planmodifier.List{ + ShiftCoveragesPlanModifier(), + listplanmodifier.UseStateForUnknown(), + }, + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "map_block_key": schema.StringAttribute{ + CustomType: fwtypes.StringEnumType[awstypes.DayOfWeek](), + Required: true, + }, + }, + Blocks: map[string]schema.Block{ + "coverage_times": schema.ListNestedBlock{ + CustomType: fwtypes.NewListNestedObjectTypeOf[coverageTimesData](ctx), + NestedObject: schema.NestedBlockObject{ + Blocks: map[string]schema.Block{ + "end": handOffTimeSchema(ctx, aws.Int(1)), + "start": handOffTimeSchema(ctx, aws.Int(1)), + }, + }, + Validators: []validator.List{ + listvalidator.IsRequired(), + listvalidator.SizeAtLeast(1), + }, + }, + }, + }, + }, + "weekly_settings": schema.ListNestedBlock{ + CustomType: fwtypes.NewListNestedObjectTypeOf[weeklySettingsData](ctx), + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "day_of_week": schema.StringAttribute{ + CustomType: fwtypes.StringEnumType[awstypes.DayOfWeek](), + Required: true, + }, + }, + Blocks: map[string]schema.Block{ + "hand_off_time": handOffTimeSchema(ctx, aws.Int(1)), + }, + }, + }, + }, + }, + }, + }, + } + + if s.Blocks == nil { + s.Blocks = make(map[string]schema.Block) + } + + response.Schema = s +} + +func handOffTimeSchema(ctx context.Context, size *int) schema.ListNestedBlock { + listSchema := schema.ListNestedBlock{ + CustomType: fwtypes.NewListNestedObjectTypeOf[handOffTime](ctx), + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "hour_of_day": schema.Int64Attribute{ + Required: true, + }, + "minute_of_hour": schema.Int64Attribute{ + Required: true, + }, + }, + }, + } + + if size != nil { + listSchema.Validators = []validator.List{ + listvalidator.SizeAtMost(*size), + } + } + + return listSchema +} + +func (r *resourceRotation) Create(ctx context.Context, request resource.CreateRequest, response *resource.CreateResponse) { + conn := r.Meta().SSMContactsClient(ctx) + var plan resourceRotationData + + response.Diagnostics.Append(request.Plan.Get(ctx, &plan)...) + + if response.Diagnostics.HasError() { + return + } + input := &ssmcontacts.CreateRotationInput{} + response.Diagnostics.Append(flex.Expand(ctx, plan, input)...) + + if response.Diagnostics.HasError() { + return + } + + sc := newShiftCoverages() + response.Diagnostics.Append(sc.expand(ctx, &plan)...) + + if response.Diagnostics.HasError() { + return + } + + input.Recurrence.ShiftCoverages = sc + input.Tags = getTagsIn(ctx) + + output, err := conn.CreateRotation(ctx, input) + + if err != nil { + response.Diagnostics.AddError( + create.ProblemStandardMessage(names.SSMContacts, create.ErrActionCreating, ResNameRotation, plan.Name.ValueString(), err), + err.Error(), + ) + return + } + + state := plan + + response.Diagnostics.Append(flex.Flatten(ctx, output, &state)...) + + state.ID = flex.StringToFramework(ctx, output.RotationArn) + state.ARN = flex.StringToFramework(ctx, output.RotationArn) + + response.Diagnostics.Append(response.State.Set(ctx, &state)...) +} + +// Read is called when the provider must read resource values in order to update state. +// Planned state values should be read from the ReadRequest and new state values set on the ReadResponse. +func (r *resourceRotation) Read(ctx context.Context, request resource.ReadRequest, response *resource.ReadResponse) { + conn := r.Meta().SSMContactsClient(ctx) + var state resourceRotationData + + response.Diagnostics.Append(request.State.Get(ctx, &state)...) + + if response.Diagnostics.HasError() { + return + } + + output, err := FindRotationByID(ctx, conn, state.ID.ValueString()) + + if tfresource.NotFound(err) { + response.State.RemoveResource(ctx) + return + } + + if err != nil { + response.Diagnostics.AddError( + create.ProblemStandardMessage(names.SSMContacts, create.ErrActionSetting, ResNameRotation, state.ID.ValueString(), err), + err.Error(), + ) + return + } + + response.Diagnostics.Append(flex.Flatten(ctx, output, &state)...) + + if response.Diagnostics.HasError() { + return + } + + response.Diagnostics.Append(response.State.Set(ctx, &state)...) +} + +func (r *resourceRotation) Update(ctx context.Context, request resource.UpdateRequest, response *resource.UpdateResponse) { + var plan resourceRotationData + + //response.Diagnostics.Append(request.State.Get(ctx, &old)...) + // + //if response.Diagnostics.HasError() { + // return + //} + // + response.Diagnostics.Append(request.Plan.Get(ctx, &plan)...) + + if response.Diagnostics.HasError() { + return + } + //updateTimeout := r.UpdateTimeout(ctx, new.Timeouts) + + response.Diagnostics.Append(response.State.Set(ctx, &plan)...) +} + +func (r *resourceRotation) Delete(ctx context.Context, request resource.DeleteRequest, response *resource.DeleteResponse) { + conn := r.Meta().SSMContactsClient(ctx) + var state resourceRotationData + + response.Diagnostics.Append(request.State.Get(ctx, &state)...) + + if response.Diagnostics.HasError() { + return + } + + tflog.Debug(ctx, "deleting TODO", map[string]interface{}{ + "id": state.ID.ValueString(), + }) + + _, err := conn.DeleteRotation(ctx, &ssmcontacts.DeleteRotationInput{ + RotationId: flex.StringFromFramework(ctx, state.ID), + }) + + if errs.IsA[*awstypes.ResourceNotFoundException](err) { + return + } + + if err != nil { + response.Diagnostics.AddError( + create.ProblemStandardMessage(names.SSMContacts, create.ErrActionDeleting, ResNameRotation, state.ID.ValueString(), err), + err.Error(), + ) + return + } +} + +func (r *resourceRotation) ImportState(ctx context.Context, request resource.ImportStateRequest, response *resource.ImportStateResponse) { + resource.ImportStatePassthroughID(ctx, path.Root("id"), request, response) +} + +func (r *resourceRotation) ModifyPlan(ctx context.Context, request resource.ModifyPlanRequest, response *resource.ModifyPlanResponse) { + r.SetTagsAll(ctx, request, response) +} + +type resourceRotationData struct { + ARN types.String `tfsdk:"arn"` + ContactIds fwtypes.ListValueOf[types.String] `tfsdk:"contact_ids"` + ID types.String `tfsdk:"id"` + Recurrence fwtypes.ListNestedObjectValueOf[recurrenceData] `tfsdk:"recurrence"` + Name types.String `tfsdk:"name"` + StartTime fwtypes.Timestamp `tfsdk:"start_time"` + Tags types.Map `tfsdk:"tags"` + TagsAll types.Map `tfsdk:"tags_all"` + TimeZoneID types.String `tfsdk:"time_zone_id"` +} + +type recurrenceData struct { + DailySettings fwtypes.ListNestedObjectValueOf[handOffTime] `tfsdk:"daily_settings"` + MonthlySettings fwtypes.ListNestedObjectValueOf[monthlySettingsData] `tfsdk:"monthly_settings"` + NumberOfOnCalls types.Int64 `tfsdk:"number_of_on_calls"` + RecurrenceMultiplier types.Int64 `tfsdk:"recurrence_multiplier"` + ShiftCoverages fwtypes.ListNestedObjectValueOf[shiftCoveragesData] `tfsdk:"shift_coverages"` + WeeklySettings fwtypes.ListNestedObjectValueOf[weeklySettingsData] `tfsdk:"weekly_settings"` +} + +type monthlySettingsData struct { + DayOfMonth types.Int64 `tfsdk:"day_of_month"` + HandOffTime fwtypes.ListNestedObjectValueOf[handOffTime] `tfsdk:"hand_off_time"` +} + +type shiftCoveragesData struct { + CoverageTimes fwtypes.ListNestedObjectValueOf[coverageTimesData] `tfsdk:"coverage_times"` + MapBlockKey fwtypes.StringEnum[awstypes.DayOfWeek] `tfsdk:"map_block_key"` +} + +type coverageTimesData struct { + End fwtypes.ListNestedObjectValueOf[handOffTime] `tfsdk:"end"` + Start fwtypes.ListNestedObjectValueOf[handOffTime] `tfsdk:"start"` +} + +type handOffTime struct { + HourOfDay types.Int64 `tfsdk:"hour_of_day"` + MinuteOfHour types.Int64 `tfsdk:"minute_of_hour"` +} + +type weeklySettingsData struct { + DayOfWeek fwtypes.StringEnum[awstypes.DayOfWeek] `tfsdk:"day_of_week"` + HandOffTime types.String `tfsdk:"hand_off_time"` +} + +type shiftCoverages map[string][]awstypes.CoverageTime + +func newShiftCoverages() shiftCoverages { + sc := make(shiftCoverages) + return sc +} + +func (s shiftCoverages) expand(ctx context.Context, data *resourceRotationData) diag.Diagnostics { + var diags diag.Diagnostics + + var recurrence []recurrenceData + diags.Append(data.Recurrence.ElementsAs(ctx, &recurrence, false)...) + + if diags.HasError() { + return diags + } + + var sc []shiftCoveragesData + diags.Append(recurrence[0].ShiftCoverages.ElementsAs(ctx, &sc, false)...) + + for _, v := range sc { + var ct coverageTimes + diags.Append(v.CoverageTimes.ElementsAs(ctx, &ct, false)...) + + if diags.HasError() { + return diags + } + + s[v.MapBlockKey.ValueString()] = ct.expand(ctx, diags) + + } + + return diags +} + +type coverageTimes []coverageTimesData + +func (c coverageTimes) expand(ctx context.Context, diags diag.Diagnostics) (result []awstypes.CoverageTime) { + for _, v := range c { + var start, end []handOffTime + diags.Append(v.End.ElementsAs(ctx, &end, false)...) + diags.Append(v.End.ElementsAs(ctx, &start, false)...) + + result = append(result, awstypes.CoverageTime{ + End: &awstypes.HandOffTime{ + HourOfDay: flex.Int32ValueFromFramework(ctx, end[0].HourOfDay), + MinuteOfHour: flex.Int32ValueFromFramework(ctx, end[0].MinuteOfHour), + }, + Start: &awstypes.HandOffTime{ + HourOfDay: flex.Int32ValueFromFramework(ctx, start[0].HourOfDay), + MinuteOfHour: flex.Int32ValueFromFramework(ctx, start[0].MinuteOfHour), + }, + }) + } + return +} diff --git a/internal/service/ssmcontacts/service_package_gen.go b/internal/service/ssmcontacts/service_package_gen.go index 42c71873ea2b..f0e27e58ec1a 100644 --- a/internal/service/ssmcontacts/service_package_gen.go +++ b/internal/service/ssmcontacts/service_package_gen.go @@ -19,7 +19,15 @@ func (p *servicePackage) FrameworkDataSources(ctx context.Context) []*types.Serv } func (p *servicePackage) FrameworkResources(ctx context.Context) []*types.ServicePackageFrameworkResource { - return []*types.ServicePackageFrameworkResource{} + return []*types.ServicePackageFrameworkResource{ + { + Factory: newResourceRotation, + Name: "Rotation", + Tags: &types.ServicePackageResourceTags{ + IdentifierAttribute: "arn", + }, + }, + } } func (p *servicePackage) SDKDataSources(ctx context.Context) []*types.ServicePackageSDKDataSource { @@ -63,14 +71,6 @@ func (p *servicePackage) SDKResources(ctx context.Context) []*types.ServicePacka TypeName: "aws_ssmcontacts_plan", Name: "Plan", }, - { - Factory: ResourceRotation, - TypeName: "aws_ssmcontacts_rotation", - Name: "Rotation", - Tags: &types.ServicePackageResourceTags{ - IdentifierAttribute: "id", - }, - }, } } diff --git a/internal/service/ssmcontacts/sweep.go b/internal/service/ssmcontacts/sweep.go new file mode 100644 index 000000000000..c573dfdd7678 --- /dev/null +++ b/internal/service/ssmcontacts/sweep.go @@ -0,0 +1,64 @@ +package ssmcontacts + +import ( + "fmt" + "log" + + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/ssmcontacts" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-provider-aws/internal/sweep" + "github.com/hashicorp/terraform-provider-aws/internal/sweep/awsv2" + "github.com/hashicorp/terraform-provider-aws/internal/sweep/framework" + "github.com/hashicorp/terraform-provider-aws/names" +) + +func RegisterSweepers() { + resource.AddTestSweepers("aws_ssmcontacts_rotation", &resource.Sweeper{ + Name: "aws_ssmcontacts_rotation", + F: sweepRotations, + }) +} + +func sweepRotations(region string) error { + ctx := sweep.Context(region) + if region == names.USWest1RegionID { + log.Printf("[WARN] Skipping SSMContacts Rotations sweep for region: %s", region) + return nil + } + client, err := sweep.SharedRegionalSweepClient(ctx, region) + if err != nil { + return fmt.Errorf("error getting client: %w", err) + } + conn := client.SSMContactsClient(ctx) + input := &ssmcontacts.ListRotationsInput{} + sweepResources := make([]sweep.Sweepable, 0) + + pages := ssmcontacts.NewListRotationsPaginator(conn, input) + + for pages.HasMorePages() { + page, err := pages.NextPage(ctx) + if awsv2.SkipSweepError(err) { + log.Printf("[WARN] Skipping SSMContacts Rotations sweep for %s: %s", region, err) + return nil + } + if err != nil { + return fmt.Errorf("error retrieving SSMContacts Rotations: %w", err) + } + + for _, rotation := range page.Rotations { + id := aws.ToString(rotation.RotationArn) + + log.Printf("[INFO] Deleting SSMContacts Rotation: %s", id) + sweepResources = append(sweepResources, framework.NewSweepResource(newResourceRotation, client, + framework.NewAttribute("id", id), + )) + } + } + + if err := sweep.SweepOrchestrator(ctx, sweepResources); err != nil { + return fmt.Errorf("error sweeping SSMContacts Rotations for %s: %w", region, err) + } + + return nil +} diff --git a/internal/sweep/register_gen_test.go b/internal/sweep/register_gen_test.go index b5c1ed6acc18..fe66ea29f691 100644 --- a/internal/sweep/register_gen_test.go +++ b/internal/sweep/register_gen_test.go @@ -137,6 +137,7 @@ import ( "github.com/hashicorp/terraform-provider-aws/internal/service/sns" "github.com/hashicorp/terraform-provider-aws/internal/service/sqs" "github.com/hashicorp/terraform-provider-aws/internal/service/ssm" + "github.com/hashicorp/terraform-provider-aws/internal/service/ssmcontacts" "github.com/hashicorp/terraform-provider-aws/internal/service/ssoadmin" "github.com/hashicorp/terraform-provider-aws/internal/service/storagegateway" "github.com/hashicorp/terraform-provider-aws/internal/service/swf" @@ -286,6 +287,7 @@ func registerSweepers() { sns.RegisterSweepers() sqs.RegisterSweepers() ssm.RegisterSweepers() + ssmcontacts.RegisterSweepers() ssoadmin.RegisterSweepers() storagegateway.RegisterSweepers() swf.RegisterSweepers() From f6e10b4dad272c6f6a68dc278314fa31afe8e5c5 Mon Sep 17 00:00:00 2001 From: Adrian Johnson Date: Wed, 24 Jan 2024 19:18:23 -0600 Subject: [PATCH 03/22] aws_ssmcontacts_rotation: getting autoflex working --- internal/framework/flex/int.go | 8 + .../framework/types/list_nested_objectof.go | 60 ++ internal/service/ssmcontacts/exports_test.go | 10 + internal/service/ssmcontacts/flex.go | 271 ++++---- internal/service/ssmcontacts/rotation.go | 610 +++++++++--------- internal/service/ssmcontacts/rotation_fw.go | 300 +++++++-- internal/service/ssmcontacts/rotation_test.go | 16 +- 7 files changed, 743 insertions(+), 532 deletions(-) create mode 100644 internal/service/ssmcontacts/exports_test.go diff --git a/internal/framework/flex/int.go b/internal/framework/flex/int.go index c2ff8fcf119a..96351de3f80e 100644 --- a/internal/framework/flex/int.go +++ b/internal/framework/flex/int.go @@ -58,6 +58,14 @@ func Int32ToFramework(ctx context.Context, v *int32) types.Int64 { return output } +func Int32ValueToFramework(ctx context.Context, v int32) types.Int64 { + var output types.Int64 + + panicOnError(Flatten(ctx, v, &output)) + + return output +} + // Int32FromFramework coverts a Framework Int64 value to an int32 pointer. // A null Int64 is converted to a nil int32 pointer. func Int32FromFramework(ctx context.Context, v types.Int64) *int32 { diff --git a/internal/framework/types/list_nested_objectof.go b/internal/framework/types/list_nested_objectof.go index 9a60f67f93e4..0f2234a9c12c 100644 --- a/internal/framework/types/list_nested_objectof.go +++ b/internal/framework/types/list_nested_objectof.go @@ -169,6 +169,10 @@ func (v ListNestedObjectValueOf[T]) ToObjectSlice(ctx context.Context) (any, dia return v.ToSlice(ctx) } +func (v ListNestedObjectValueOf[T]) ToObjectValueSlice(ctx context.Context) (any, diag.Diagnostics) { + return v.ToValueSlice(ctx) +} + // ToPtr returns a pointer to the single element of a ListNestedObject. func (v ListNestedObjectValueOf[T]) ToPtr(ctx context.Context) (*T, diag.Diagnostics) { return nestedObjectValueObjectPtr[T](ctx, v.ListValue) @@ -179,6 +183,10 @@ func (v ListNestedObjectValueOf[T]) ToSlice(ctx context.Context) ([]*T, diag.Dia return nestedObjectValueObjectSlice[T](ctx, v.ListValue) } +func (v ListNestedObjectValueOf[T]) ToValueSlice(ctx context.Context) ([]T, diag.Diagnostics) { + return nestedObjectValueObjectValueSlice[T](ctx, v.ListValue) +} + func nestedObjectValueObjectPtr[T any](ctx context.Context, val valueWithElements) (*T, diag.Diagnostics) { var diags diag.Diagnostics @@ -199,6 +207,27 @@ func nestedObjectValueObjectPtr[T any](ctx context.Context, val valueWithElement } } +func nestedObjectValueObject[T any](ctx context.Context, val valueWithElements) (T, diag.Diagnostics) { + var diags diag.Diagnostics + var zero T + + elements := val.Elements() + switch n := len(elements); n { + case 0: + return zero, diags + case 1: + value, d := nestedObjectValueObjectFromElement[T](ctx, elements[0]) + diags.Append(d...) + if diags.HasError() { + return zero, diags + } + return value, diags + default: + diags.Append(diag.NewErrorDiagnostic("Invalid list/set", fmt.Sprintf("too many elements: want 1, got %d", n))) + return zero, diags + } +} + func nestedObjectValueObjectSlice[T any](ctx context.Context, val valueWithElements) ([]*T, diag.Diagnostics) { var diags diag.Diagnostics @@ -218,6 +247,25 @@ func nestedObjectValueObjectSlice[T any](ctx context.Context, val valueWithEleme return slice, diags } +func nestedObjectValueObjectValueSlice[T any](ctx context.Context, val valueWithElements) ([]T, diag.Diagnostics) { + var diags diag.Diagnostics + + elements := val.Elements() + n := len(elements) + slice := make([]T, n) + for i := 0; i < n; i++ { + ptr, d := nestedObjectValueObjectFromElement[T](ctx, elements[i]) + diags.Append(d...) + if diags.HasError() { + return nil, diags + } + + slice[i] = ptr + } + + return slice, diags +} + func nestedObjectValueObjectPtrFromElement[T any](ctx context.Context, val attr.Value) (*T, diag.Diagnostics) { var diags diag.Diagnostics @@ -230,6 +278,18 @@ func nestedObjectValueObjectPtrFromElement[T any](ctx context.Context, val attr. return ptr, diags } +func nestedObjectValueObjectFromElement[T any](ctx context.Context, val attr.Value) (T, diag.Diagnostics) { + var diags diag.Diagnostics + + var zero T + diags.Append(val.(ObjectValueOf[T]).ObjectValue.As(ctx, zero, basetypes.ObjectAsOptions{})...) + if diags.HasError() { + return zero, diags + } + + return zero, diags +} + func NewListNestedObjectValueOfNull[T any](ctx context.Context) ListNestedObjectValueOf[T] { return ListNestedObjectValueOf[T]{ListValue: basetypes.NewListNull(NewObjectTypeOf[T](ctx))} } diff --git a/internal/service/ssmcontacts/exports_test.go b/internal/service/ssmcontacts/exports_test.go new file mode 100644 index 000000000000..6a7b0de6115b --- /dev/null +++ b/internal/service/ssmcontacts/exports_test.go @@ -0,0 +1,10 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package ssmcontacts + +// Exports for use in tests only. + +var ( + ResourceRotation = newResourceRotation +) diff --git a/internal/service/ssmcontacts/flex.go b/internal/service/ssmcontacts/flex.go index e0f8b9202abc..13f43aba7d11 100644 --- a/internal/service/ssmcontacts/flex.go +++ b/internal/service/ssmcontacts/flex.go @@ -6,8 +6,6 @@ package ssmcontacts import ( "context" "fmt" - "strconv" - "strings" "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/service/ssmcontacts/types" @@ -203,53 +201,51 @@ func flattenContactTargetInfo(contactTargetInfo *types.ContactTargetInfo) []inte return result } -func expandHandOffTime(handOffTime string) *types.HandOffTime { - split := strings.Split(handOffTime, ":") - hour, _ := strconv.Atoi(split[0]) - minute, _ := strconv.Atoi(split[1]) - - return &types.HandOffTime{ - HourOfDay: int32(hour), - MinuteOfHour: int32(minute), - } -} - +// func expandHandOffTime(handOffTime string) *types.HandOffTime { +// split := strings.Split(handOffTime, ":") +// hour, _ := strconv.Atoi(split[0]) +// minute, _ := strconv.Atoi(split[1]) +// +// return &types.HandOffTime{ +// HourOfDay: int32(hour), +// MinuteOfHour: int32(minute), +// } +// } func flattenHandOffTime(handOffTime *types.HandOffTime) string { return fmt.Sprintf("%02d:%02d", handOffTime.HourOfDay, handOffTime.MinuteOfHour) } -//func expandRecurrence(recurrence []interface{}, ctx context.Context) *types.RecurrenceSettings { -// c := &types.RecurrenceSettings{} -// -// recurrenceSettings := recurrence[0].(map[string]interface{}) -// -// if v, ok := recurrenceSettings["daily_settings"].([]interface{}); ok && v != nil { -// c.DailySettings = expandDailySettings(v) -// } -// -// if v, ok := recurrenceSettings["monthly_settings"].([]interface{}); ok && v != nil { -// c.MonthlySettings = expandMonthlySettings(v) -// } -// -// if v, ok := recurrenceSettings["number_of_on_calls"].(int); ok { -// c.NumberOfOnCalls = aws.Int32(int32(v)) -// } -// -// if v, ok := recurrenceSettings["recurrence_multiplier"].(int); ok { -// c.RecurrenceMultiplier = aws.Int32(int32(v)) -// } -// -// if v, ok := recurrenceSettings["shift_coverages"].([]interface{}); ok && v != nil { -// c.ShiftCoverages = expandShiftCoverages(v) -// } -// -// if v, ok := recurrenceSettings["weekly_settings"].([]interface{}); ok && v != nil { -// c.WeeklySettings = expandWeeklySettings(v) -// } -// -// return c -//} - +// //func expandRecurrence(recurrence []interface{}, ctx context.Context) *types.RecurrenceSettings { +// // c := &types.RecurrenceSettings{} +// // +// // recurrenceSettings := recurrence[0].(map[string]interface{}) +// // +// // if v, ok := recurrenceSettings["daily_settings"].([]interface{}); ok && v != nil { +// // c.DailySettings = expandDailySettings(v) +// // } +// // +// // if v, ok := recurrenceSettings["monthly_settings"].([]interface{}); ok && v != nil { +// // c.MonthlySettings = expandMonthlySettings(v) +// // } +// // +// // if v, ok := recurrenceSettings["number_of_on_calls"].(int); ok { +// // c.NumberOfOnCalls = aws.Int32(int32(v)) +// // } +// // +// // if v, ok := recurrenceSettings["recurrence_multiplier"].(int); ok { +// // c.RecurrenceMultiplier = aws.Int32(int32(v)) +// // } +// // +// // if v, ok := recurrenceSettings["shift_coverages"].([]interface{}); ok && v != nil { +// // c.ShiftCoverages = expandShiftCoverages(v) +// // } +// // +// // if v, ok := recurrenceSettings["weekly_settings"].([]interface{}); ok && v != nil { +// // c.WeeklySettings = expandWeeklySettings(v) +// // } +// // +// // return c +// //} func flattenRecurrence(recurrence *types.RecurrenceSettings, ctx context.Context) []interface{} { var result []interface{} @@ -284,20 +280,21 @@ func flattenRecurrence(recurrence *types.RecurrenceSettings, ctx context.Context return result } -func expandDailySettings(dailySettings []interface{}) []types.HandOffTime { - if len(dailySettings) == 0 { - return nil - } - - var result []types.HandOffTime - - for _, dailySetting := range dailySettings { - result = append(result, *expandHandOffTime(dailySetting.(string))) - } - - return result -} - +// // +// //func expandDailySettings(dailySettings []interface{}) []types.HandOffTime { +// // if len(dailySettings) == 0 { +// // return nil +// // } +// // +// // var result []types.HandOffTime +// // +// // +// // for _, dailySetting := range dailySettings { +// // result = append(result, *expandHandOffTime(dailySetting.(string))) +// // } +// // +// // return result +// //} func flattenDailySettings(dailySettings []types.HandOffTime) []interface{} { if len(dailySettings) == 0 { return nil @@ -312,27 +309,26 @@ func flattenDailySettings(dailySettings []types.HandOffTime) []interface{} { return result } -func expandMonthlySettings(monthlySettings []interface{}) []types.MonthlySetting { - if len(monthlySettings) == 0 { - return nil - } - - var result []types.MonthlySetting - - for _, monthlySetting := range monthlySettings { - monthlySettingData := monthlySetting.(map[string]interface{}) - - c := types.MonthlySetting{ - DayOfMonth: aws.Int32(int32(monthlySettingData["day_of_month"].(int))), - HandOffTime: expandHandOffTime(monthlySettingData["hand_off_time"].(string)), - } - - result = append(result, c) - } - - return result -} - +// func expandMonthlySettings(monthlySettings []interface{}) []types.MonthlySetting { +// if len(monthlySettings) == 0 { +// return nil +// } +// +// var result []types.MonthlySetting +// +// for _, monthlySetting := range monthlySettings { +// monthlySettingData := monthlySetting.(map[string]interface{}) +// +// c := types.MonthlySetting{ +// DayOfMonth: aws.Int32(int32(monthlySettingData["day_of_month"].(int))), +// HandOffTime: expandHandOffTime(monthlySettingData["hand_off_time"].(string)), +// } +// +// result = append(result, c) +// } +// +// return result +// } func flattenMonthlySettings(monthlySettings []types.MonthlySetting) []interface{} { if len(monthlySettings) == 0 { return nil @@ -357,25 +353,24 @@ func flattenMonthlySettings(monthlySettings []types.MonthlySetting) []interface{ return result } -//func expandShiftCoverages(shiftCoverages []interface{}) map[string][]types.CoverageTime { -// if len(shiftCoverages) == 0 { -// return nil -// } -// -// result := make(map[string][]types.CoverageTime) -// -// for _, shiftCoverage := range shiftCoverages { -// shiftCoverageData := shiftCoverage.(map[string]interface{}) -// -// dayOfWeek := shiftCoverageData["day_of_week"].(string) -// coverageTimes := expandCoverageTimes(shiftCoverageData["coverage_times"].([]interface{})) -// -// result[dayOfWeek] = coverageTimes -// } -// -// return result -//} - +// //func expandShiftCoverages(shiftCoverages []interface{}) map[string][]types.CoverageTime { +// // if len(shiftCoverages) == 0 { +// // return nil +// // } +// // +// // result := make(map[string][]types.CoverageTime) +// // +// // for _, shiftCoverage := range shiftCoverages { +// // shiftCoverageData := shiftCoverage.(map[string]interface{}) +// // +// // dayOfWeek := shiftCoverageData["day_of_week"].(string) +// // coverageTimes := expandCoverageTimes(shiftCoverageData["coverage_times"].([]interface{})) +// // +// // result[dayOfWeek] = coverageTimes +// // } +// // +// // return result +// //} func flattenShiftCoverages(shiftCoverages map[string][]types.CoverageTime) []interface{} { if len(shiftCoverages) == 0 { return nil @@ -398,28 +393,27 @@ func flattenShiftCoverages(shiftCoverages map[string][]types.CoverageTime) []int return result } -func expandCoverageTimes(coverageTimes []interface{}) []types.CoverageTime { - var result []types.CoverageTime - - for _, coverageTime := range coverageTimes { - coverageTimeData := coverageTime.(map[string]interface{}) - - c := types.CoverageTime{} - - if v, ok := coverageTimeData["end_time"].(string); ok { - c.End = expandHandOffTime(v) - } - - if v, ok := coverageTimeData["start_time"].(string); ok { - c.Start = expandHandOffTime(v) - } - - result = append(result, c) - } - - return result -} - +// func expandCoverageTimes(coverageTimes []interface{}) []types.CoverageTime { +// var result []types.CoverageTime +// +// for _, coverageTime := range coverageTimes { +// coverageTimeData := coverageTime.(map[string]interface{}) +// +// c := types.CoverageTime{} +// +// if v, ok := coverageTimeData["end_time"].(string); ok { +// c.End = expandHandOffTime(v) +// } +// +// if v, ok := coverageTimeData["start_time"].(string); ok { +// c.Start = expandHandOffTime(v) +// } +// +// result = append(result, c) +// } +// +// return result +// } func flattenCoverageTimes(coverageTimes []types.CoverageTime) []interface{} { var result []interface{} @@ -440,24 +434,23 @@ func flattenCoverageTimes(coverageTimes []types.CoverageTime) []interface{} { return result } -func expandWeeklySettings(weeklySettings []interface{}) []types.WeeklySetting { - var result []types.WeeklySetting - - for _, weeklySetting := range weeklySettings { - weeklySettingData := weeklySetting.(map[string]interface{}) - - c := types.WeeklySetting{ - DayOfWeek: types.DayOfWeek(weeklySettingData["day_of_week"].(string)), - HandOffTime: expandHandOffTime(weeklySettingData["hand_off_time"].(string)), - } - - result = append(result, c) - - } - - return result -} - +// func expandWeeklySettings(weeklySettings []interface{}) []types.WeeklySetting { +// var result []types.WeeklySetting +// +// for _, weeklySetting := range weeklySettings { +// weeklySettingData := weeklySetting.(map[string]interface{}) +// +// c := types.WeeklySetting{ +// DayOfWeek: types.DayOfWeek(weeklySettingData["day_of_week"].(string)), +// HandOffTime: expandHandOffTime(weeklySettingData["hand_off_time"].(string)), +// } +// +// result = append(result, c) +// +// } +// +// return result +// } func flattenWeeklySettings(weeklySettings []types.WeeklySetting) []interface{} { if len(weeklySettings) == 0 { return nil diff --git a/internal/service/ssmcontacts/rotation.go b/internal/service/ssmcontacts/rotation.go index c11aa2840d09..2a3d87277bcb 100644 --- a/internal/service/ssmcontacts/rotation.go +++ b/internal/service/ssmcontacts/rotation.go @@ -1,320 +1,294 @@ package ssmcontacts -import ( - "context" - "errors" - "log" - "regexp" - "time" - - "github.com/google/go-cmp/cmp" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/customdiff" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" - "github.com/hashicorp/terraform-provider-aws/internal/enum" - "github.com/hashicorp/terraform-provider-aws/internal/flex" - - "github.com/aws/aws-sdk-go-v2/aws" - "github.com/aws/aws-sdk-go-v2/service/ssmcontacts" - "github.com/aws/aws-sdk-go-v2/service/ssmcontacts/types" - "github.com/hashicorp/terraform-plugin-sdk/v2/diag" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/hashicorp/terraform-provider-aws/internal/conns" - "github.com/hashicorp/terraform-provider-aws/internal/create" - tftags "github.com/hashicorp/terraform-provider-aws/internal/tags" - "github.com/hashicorp/terraform-provider-aws/internal/tfresource" - "github.com/hashicorp/terraform-provider-aws/internal/verify" - "github.com/hashicorp/terraform-provider-aws/names" -) - -func ResourceRotation() *schema.Resource { - return &schema.Resource{ - CreateWithoutTimeout: resourceRotationCreate, - ReadWithoutTimeout: resourceRotationRead, - UpdateWithoutTimeout: resourceRotationUpdate, - DeleteWithoutTimeout: resourceRotationDelete, - - Importer: &schema.ResourceImporter{ - StateContext: schema.ImportStatePassthroughContext, - }, - - Timeouts: &schema.ResourceTimeout{ - Create: schema.DefaultTimeout(30 * time.Minute), - Update: schema.DefaultTimeout(30 * time.Minute), - Delete: schema.DefaultTimeout(30 * time.Minute), - }, - - Schema: map[string]*schema.Schema{ - "arn": { - Type: schema.TypeString, - Computed: true, - }, - "contact_ids": { - Type: schema.TypeList, - Required: true, - Elem: &schema.Schema{ - Type: schema.TypeString, - }, - }, - "name": { - Type: schema.TypeString, - Required: true, - }, - "recurrence": { - Type: schema.TypeList, - Required: true, - MaxItems: 1, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "daily_settings": { - Type: schema.TypeList, - Optional: true, - Elem: &schema.Schema{ - Type: schema.TypeString, - ValidateDiagFunc: validation.ToDiagFunc(handOffTimeValidator), - }, - }, - "monthly_settings": { - Type: schema.TypeList, - Optional: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "day_of_month": { - Type: schema.TypeInt, - Required: true, - }, - "hand_off_time": { - Type: schema.TypeString, - Required: true, - ValidateDiagFunc: validation.ToDiagFunc(handOffTimeValidator), - }, - }, - }, - }, - "number_of_on_calls": { - Type: schema.TypeInt, - Required: true, - }, - "recurrence_multiplier": { - Type: schema.TypeInt, - Required: true, - }, - "shift_coverages": { - Type: schema.TypeList, - Optional: true, - Computed: true, // This is computed to allow for clearing the diff to handle erroneous diffs - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "coverage_times": { - Type: schema.TypeList, - MaxItems: 1, - Required: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "end_time": { - Type: schema.TypeString, - Optional: true, - ValidateDiagFunc: validation.ToDiagFunc(handOffTimeValidator), - }, - "start_time": { - Type: schema.TypeString, - Optional: true, - ValidateDiagFunc: validation.ToDiagFunc(handOffTimeValidator), - }, - }, - }, - }, - "day_of_week": { - Type: schema.TypeString, - Required: true, - ValidateDiagFunc: enum.Validate[types.DayOfWeek](), - }, - }, - }, - }, - "weekly_settings": { - Type: schema.TypeList, - Optional: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "day_of_week": { - Type: schema.TypeString, - Required: true, - ValidateDiagFunc: enum.Validate[types.DayOfWeek](), - }, - "hand_off_time": { - Type: schema.TypeString, - Required: true, - ValidateDiagFunc: validation.ToDiagFunc(handOffTimeValidator), - }, - }, - }, - }, - }, - }, - }, - "start_time": { - Type: schema.TypeString, - Optional: true, - ValidateDiagFunc: validation.ToDiagFunc(validation.IsRFC3339Time), - }, - "time_zone_id": { - Type: schema.TypeString, - Required: true, - }, - names.AttrTags: tftags.TagsSchema(), - names.AttrTagsAll: tftags.TagsSchemaComputed(), - }, - - CustomizeDiff: customdiff.Sequence( - func(_ context.Context, diff *schema.ResourceDiff, _ interface{}) error { - if diff.HasChange("recurrence.0.shift_coverages") { - o, n := diff.GetChange("recurrence.0.shift_coverages") - - sortShiftCoverages(o.([]interface{})) - sortShiftCoverages(n.([]interface{})) - - isEqual := cmp.Diff(o, n) == "" - - if isEqual { - return diff.Clear("recurrence.0.shift_coverages") - } - } - - return nil - }, - verify.SetTagsDiff, - ), - } -} - -//const ( -// ResNameRotation = "Rotation" -//) - -var handOffTimeValidator = validation.StringMatch(regexp.MustCompile(`^\d\d:\d\d$`), "Time must be in 24-hour time format, e.g. \"01:00\"") - -func resourceRotationCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - conn := meta.(*conns.AWSClient).SSMContactsClient(ctx) - - in := &ssmcontacts.CreateRotationInput{ - ContactIds: flex.ExpandStringValueList(d.Get("contact_ids").([]interface{})), - Name: aws.String(d.Get("name").(string)), - // Recurrence: expandRecurrence(d.Get("recurrence").([]interface{}), ctx), - Tags: getTagsIn(ctx), - TimeZoneId: aws.String(d.Get("time_zone_id").(string)), - } - - if v, ok := d.GetOk("start_time"); ok { - startTime, _ := time.Parse(time.RFC3339, v.(string)) - in.StartTime = aws.Time(startTime) - } - - out, err := conn.CreateRotation(ctx, in) - if err != nil { - return create.DiagError(names.SSMContacts, create.ErrActionCreating, ResNameRotation, d.Get("name").(string), err) - } - - if out == nil { - return create.DiagError(names.SSMContacts, create.ErrActionCreating, ResNameRotation, d.Get("name").(string), errors.New("empty output")) - } - - d.SetId(aws.ToString(out.RotationArn)) - - return resourceRotationRead(ctx, d, meta) -} - -func resourceRotationRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - conn := meta.(*conns.AWSClient).SSMContactsClient(ctx) - - out, err := FindRotationByID(ctx, conn, d.Id()) - - if !d.IsNewResource() && tfresource.NotFound(err) { - log.Printf("[WARN] SSMContacts Rotation (%s) not found, removing from state", d.Id()) - d.SetId("") - return nil - } - - if err != nil { - return create.DiagError(names.SSMContacts, create.ErrActionReading, ResNameRotation, d.Id(), err) - } - - d.Set("arn", out.RotationArn) - d.Set("contact_ids", out.ContactIds) - d.Set("name", out.Name) - d.Set("time_zone_id", out.TimeZoneId) - - if out.StartTime != nil { - d.Set("start_time", out.StartTime.Format(time.RFC3339)) - } - - if err := d.Set("recurrence", flattenRecurrence(out.Recurrence, ctx)); err != nil { - return create.DiagError(names.SSMContacts, create.ErrActionSetting, DSNameRotation, d.Id(), err) - } - - return nil -} - -func resourceRotationUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - conn := meta.(*conns.AWSClient).SSMContactsClient(ctx) - - update := false - - in := &ssmcontacts.UpdateRotationInput{ - RotationId: aws.String(d.Id()), - } - - if d.HasChanges("contact_ids") { - in.ContactIds = flex.ExpandStringValueList(d.Get("contact_ids").([]interface{})) - update = true - } - - // Recurrence is a required field, but we don't want to force an update every time if no changes - //in.Recurrence = expandRecurrence(d.Get("recurrence").([]interface{}), ctx) - if d.HasChanges("recurrence") { - update = true - } - - if d.HasChanges("start_time") { - startTime, _ := time.Parse(time.RFC3339, d.Get("start_time").(string)) - in.StartTime = aws.Time(startTime) - update = true - } - - if d.HasChanges("time_zone_id") { - in.TimeZoneId = aws.String(d.Get("time_zone_id").(string)) - update = true - } - - if !update { - return nil - } - - log.Printf("[DEBUG] Updating SSMContacts Rotation (%s): %#v", d.Id(), in) - _, err := conn.UpdateRotation(ctx, in) - if err != nil { - return create.DiagError(names.SSMContacts, create.ErrActionUpdating, ResNameRotation, d.Id(), err) - } - - return resourceRotationRead(ctx, d, meta) -} - -func resourceRotationDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - conn := meta.(*conns.AWSClient).SSMContactsClient(ctx) - - log.Printf("[INFO] Deleting SSMContacts Rotation %s", d.Id()) - - _, err := conn.DeleteRotation(ctx, &ssmcontacts.DeleteRotationInput{ - RotationId: aws.String(d.Id()), - }) - - if err != nil { - var nfe *types.ResourceNotFoundException - if errors.As(err, &nfe) { - return nil - } - - return create.DiagError(names.SSMContacts, create.ErrActionDeleting, ResNameRotation, d.Id(), err) - } - - return nil -} +//func ResourceRotation() *schema.Resource { +// return &schema.Resource{ +// CreateWithoutTimeout: resourceRotationCreate, +// ReadWithoutTimeout: resourceRotationRead, +// UpdateWithoutTimeout: resourceRotationUpdate, +// DeleteWithoutTimeout: resourceRotationDelete, +// +// Importer: &schema.ResourceImporter{ +// StateContext: schema.ImportStatePassthroughContext, +// }, +// +// Timeouts: &schema.ResourceTimeout{ +// Create: schema.DefaultTimeout(30 * time.Minute), +// Update: schema.DefaultTimeout(30 * time.Minute), +// Delete: schema.DefaultTimeout(30 * time.Minute), +// }, +// +// Schema: map[string]*schema.Schema{ +// "arn": { +// Type: schema.TypeString, +// Computed: true, +// }, +// "contact_ids": { +// Type: schema.TypeList, +// Required: true, +// Elem: &schema.Schema{ +// Type: schema.TypeString, +// }, +// }, +// "name": { +// Type: schema.TypeString, +// Required: true, +// }, +// "recurrence": { +// Type: schema.TypeList, +// Required: true, +// MaxItems: 1, +// Elem: &schema.Resource{ +// Schema: map[string]*schema.Schema{ +// "daily_settings": { +// Type: schema.TypeList, +// Optional: true, +// Elem: &schema.Schema{ +// Type: schema.TypeString, +// ValidateDiagFunc: validation.ToDiagFunc(handOffTimeValidator), +// }, +// }, +// "monthly_settings": { +// Type: schema.TypeList, +// Optional: true, +// Elem: &schema.Resource{ +// Schema: map[string]*schema.Schema{ +// "day_of_month": { +// Type: schema.TypeInt, +// Required: true, +// }, +// "hand_off_time": { +// Type: schema.TypeString, +// Required: true, +// ValidateDiagFunc: validation.ToDiagFunc(handOffTimeValidator), +// }, +// }, +// }, +// }, +// "number_of_on_calls": { +// Type: schema.TypeInt, +// Required: true, +// }, +// "recurrence_multiplier": { +// Type: schema.TypeInt, +// Required: true, +// }, +// "shift_coverages": { +// Type: schema.TypeList, +// Optional: true, +// Computed: true, // This is computed to allow for clearing the diff to handle erroneous diffs +// Elem: &schema.Resource{ +// Schema: map[string]*schema.Schema{ +// "coverage_times": { +// Type: schema.TypeList, +// MaxItems: 1, +// Required: true, +// Elem: &schema.Resource{ +// Schema: map[string]*schema.Schema{ +// "end_time": { +// Type: schema.TypeString, +// Optional: true, +// ValidateDiagFunc: validation.ToDiagFunc(handOffTimeValidator), +// }, +// "start_time": { +// Type: schema.TypeString, +// Optional: true, +// ValidateDiagFunc: validation.ToDiagFunc(handOffTimeValidator), +// }, +// }, +// }, +// }, +// "day_of_week": { +// Type: schema.TypeString, +// Required: true, +// ValidateDiagFunc: enum.Validate[types.DayOfWeek](), +// }, +// }, +// }, +// }, +// "weekly_settings": { +// Type: schema.TypeList, +// Optional: true, +// Elem: &schema.Resource{ +// Schema: map[string]*schema.Schema{ +// "day_of_week": { +// Type: schema.TypeString, +// Required: true, +// ValidateDiagFunc: enum.Validate[types.DayOfWeek](), +// }, +// "hand_off_time": { +// Type: schema.TypeString, +// Required: true, +// ValidateDiagFunc: validation.ToDiagFunc(handOffTimeValidator), +// }, +// }, +// }, +// }, +// }, +// }, +// }, +// "start_time": { +// Type: schema.TypeString, +// Optional: true, +// ValidateDiagFunc: validation.ToDiagFunc(validation.IsRFC3339Time), +// }, +// "time_zone_id": { +// Type: schema.TypeString, +// Required: true, +// }, +// names.AttrTags: tftags.TagsSchema(), +// names.AttrTagsAll: tftags.TagsSchemaComputed(), +// }, +// +// CustomizeDiff: customdiff.Sequence( +// func(_ context.Context, diff *schema.ResourceDiff, _ interface{}) error { +// if diff.HasChange("recurrence.0.shift_coverages") { +// o, n := diff.GetChange("recurrence.0.shift_coverages") +// +// sortShiftCoverages(o.([]interface{})) +// sortShiftCoverages(n.([]interface{})) +// +// isEqual := cmp.Diff(o, n) == "" +// +// if isEqual { +// return diff.Clear("recurrence.0.shift_coverages") +// } +// } +// +// return nil +// }, +// verify.SetTagsDiff, +// ), +// } +//} +// +////const ( +//// ResNameRotation = "Rotation" +////) +// +//var handOffTimeValidator = validation.StringMatch(regexp.MustCompile(`^\d\d:\d\d$`), "Time must be in 24-hour time format, e.g. \"01:00\"") +// +//func resourceRotationCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { +// conn := meta.(*conns.AWSClient).SSMContactsClient(ctx) +// +// in := &ssmcontacts.CreateRotationInput{ +// ContactIds: flex.ExpandStringValueList(d.Get("contact_ids").([]interface{})), +// Name: aws.String(d.Get("name").(string)), +// // Recurrence: expandRecurrence(d.Get("recurrence").([]interface{}), ctx), +// Tags: getTagsIn(ctx), +// TimeZoneId: aws.String(d.Get("time_zone_id").(string)), +// } +// +// if v, ok := d.GetOk("start_time"); ok { +// startTime, _ := time.Parse(time.RFC3339, v.(string)) +// in.StartTime = aws.Time(startTime) +// } +// +// out, err := conn.CreateRotation(ctx, in) +// if err != nil { +// return create.DiagError(names.SSMContacts, create.ErrActionCreating, ResNameRotation, d.Get("name").(string), err) +// } +// +// if out == nil { +// return create.DiagError(names.SSMContacts, create.ErrActionCreating, ResNameRotation, d.Get("name").(string), errors.New("empty output")) +// } +// +// d.SetId(aws.ToString(out.RotationArn)) +// +// return resourceRotationRead(ctx, d, meta) +//} +// +//func resourceRotationRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { +// conn := meta.(*conns.AWSClient).SSMContactsClient(ctx) +// +// out, err := FindRotationByID(ctx, conn, d.Id()) +// +// if !d.IsNewResource() && tfresource.NotFound(err) { +// log.Printf("[WARN] SSMContacts Rotation (%s) not found, removing from state", d.Id()) +// d.SetId("") +// return nil +// } +// +// if err != nil { +// return create.DiagError(names.SSMContacts, create.ErrActionReading, ResNameRotation, d.Id(), err) +// } +// +// d.Set("arn", out.RotationArn) +// d.Set("contact_ids", out.ContactIds) +// d.Set("name", out.Name) +// d.Set("time_zone_id", out.TimeZoneId) +// +// if out.StartTime != nil { +// d.Set("start_time", out.StartTime.Format(time.RFC3339)) +// } +// +// if err := d.Set("recurrence", flattenRecurrence(out.Recurrence, ctx)); err != nil { +// return create.DiagError(names.SSMContacts, create.ErrActionSetting, DSNameRotation, d.Id(), err) +// } +// +// return nil +//} +// +//func resourceRotationUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { +// conn := meta.(*conns.AWSClient).SSMContactsClient(ctx) +// +// update := false +// +// in := &ssmcontacts.UpdateRotationInput{ +// RotationId: aws.String(d.Id()), +// } +// +// if d.HasChanges("contact_ids") { +// in.ContactIds = flex.ExpandStringValueList(d.Get("contact_ids").([]interface{})) +// update = true +// } +// +// // Recurrence is a required field, but we don't want to force an update every time if no changes +// //in.Recurrence = expandRecurrence(d.Get("recurrence").([]interface{}), ctx) +// if d.HasChanges("recurrence") { +// update = true +// } +// +// if d.HasChanges("start_time") { +// startTime, _ := time.Parse(time.RFC3339, d.Get("start_time").(string)) +// in.StartTime = aws.Time(startTime) +// update = true +// } +// +// if d.HasChanges("time_zone_id") { +// in.TimeZoneId = aws.String(d.Get("time_zone_id").(string)) +// update = true +// } +// +// if !update { +// return nil +// } +// +// log.Printf("[DEBUG] Updating SSMContacts Rotation (%s): %#v", d.Id(), in) +// _, err := conn.UpdateRotation(ctx, in) +// if err != nil { +// return create.DiagError(names.SSMContacts, create.ErrActionUpdating, ResNameRotation, d.Id(), err) +// } +// +// return resourceRotationRead(ctx, d, meta) +//} +// +//func resourceRotationDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { +// conn := meta.(*conns.AWSClient).SSMContactsClient(ctx) +// +// log.Printf("[INFO] Deleting SSMContacts Rotation %s", d.Id()) +// +// _, err := conn.DeleteRotation(ctx, &ssmcontacts.DeleteRotationInput{ +// RotationId: aws.String(d.Id()), +// }) +// +// if err != nil { +// var nfe *types.ResourceNotFoundException +// if errors.As(err, &nfe) { +// return nil +// } +// +// return create.DiagError(names.SSMContacts, create.ErrActionDeleting, ResNameRotation, d.Id(), err) +// } +// +// return nil +//} diff --git a/internal/service/ssmcontacts/rotation_fw.go b/internal/service/ssmcontacts/rotation_fw.go index 4b460ec299ce..1da26c56f6bf 100644 --- a/internal/service/ssmcontacts/rotation_fw.go +++ b/internal/service/ssmcontacts/rotation_fw.go @@ -1,4 +1,5 @@ -// Code generated by tools/tfsdk2fw/main.go. Manual editing is required. +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 package ssmcontacts @@ -10,7 +11,6 @@ import ( "github.com/aws/aws-sdk-go/aws" "github.com/hashicorp/terraform-plugin-framework-validators/listvalidator" "github.com/hashicorp/terraform-plugin-framework/diag" - "github.com/hashicorp/terraform-plugin-framework/path" "github.com/hashicorp/terraform-plugin-framework/resource" "github.com/hashicorp/terraform-plugin-framework/resource/schema" "github.com/hashicorp/terraform-plugin-framework/resource/schema/listplanmodifier" @@ -18,6 +18,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/schema/validator" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-log/tflog" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/id" "github.com/hashicorp/terraform-provider-aws/internal/create" "github.com/hashicorp/terraform-provider-aws/internal/errs" "github.com/hashicorp/terraform-provider-aws/internal/framework" @@ -42,13 +43,13 @@ func newResourceRotation(context.Context) (resource.ResourceWithConfigure, error type resourceRotation struct { framework.ResourceWithConfigure + framework.WithImportByID } func (r *resourceRotation) Metadata(_ context.Context, request resource.MetadataRequest, response *resource.MetadataResponse) { response.TypeName = "aws_ssmcontacts_rotation" } -// Schema returns the schema for this resource. func (r *resourceRotation) Schema(ctx context.Context, request resource.SchemaRequest, response *resource.SchemaResponse) { s := schema.Schema{ Attributes: map[string]schema.Attribute{ @@ -193,22 +194,67 @@ func (r *resourceRotation) Create(ctx context.Context, request resource.CreateRe if response.Diagnostics.HasError() { return } - input := &ssmcontacts.CreateRotationInput{} - response.Diagnostics.Append(flex.Expand(ctx, plan, input)...) + recurrenceData, diags := plan.Recurrence.ToPtr(ctx) + response.Diagnostics.Append(diags...) if response.Diagnostics.HasError() { return } - sc := newShiftCoverages() - response.Diagnostics.Append(sc.expand(ctx, &plan)...) + shiftCoveragesData, diags := recurrenceData.ShiftCoverages.ToSlice(ctx) + response.Diagnostics.Append(diags...) + if response.Diagnostics.HasError() { + return + } + + shiftCoverages := expandShiftCoverages(ctx, shiftCoveragesData, &response.Diagnostics) + if response.Diagnostics.HasError() { + return + } + dailySettingsInput := objectForInput[handOffTime]{ + Data: recurrenceData.DailySettings, + } + dailySettingsOutput := objectForOutput[awstypes.HandOffTime]{} + response.Diagnostics.Append(flex.Expand(ctx, dailySettingsInput, &dailySettingsOutput)...) if response.Diagnostics.HasError() { return } - input.Recurrence.ShiftCoverages = sc - input.Tags = getTagsIn(ctx) + monthlySettingsInput := objectForInput[monthlySettingsData]{ + Data: recurrenceData.MonthlySettings, + } + monthlySettingsOutput := objectForOutput[awstypes.MonthlySetting]{} + response.Diagnostics.Append(flex.Expand(ctx, monthlySettingsInput, &monthlySettingsOutput)...) + if response.Diagnostics.HasError() { + return + } + + weeklySettingsInput := objectForInput[weeklySettingsData]{ + Data: recurrenceData.WeeklySettings, + } + weeklySettingsOutput := objectForOutput[awstypes.WeeklySetting]{} + response.Diagnostics.Append(flex.Expand(ctx, weeklySettingsInput, &weeklySettingsOutput)...) + if response.Diagnostics.HasError() { + return + } + + input := &ssmcontacts.CreateRotationInput{ + IdempotencyToken: aws.String(id.UniqueId()), + ContactIds: flex.ExpandFrameworkStringValueList(ctx, plan.ContactIds), + Name: flex.StringFromFramework(ctx, plan.Name), + Recurrence: &awstypes.RecurrenceSettings{ + NumberOfOnCalls: flex.Int32FromFramework(ctx, recurrenceData.NumberOfOnCalls), + RecurrenceMultiplier: flex.Int32FromFramework(ctx, recurrenceData.RecurrenceMultiplier), + DailySettings: dailySettingsOutput.Data, + MonthlySettings: monthlySettingsOutput.Data, + ShiftCoverages: shiftCoverages, + WeeklySettings: weeklySettingsOutput.Data, + }, + TimeZoneId: flex.StringFromFramework(ctx, plan.TimeZoneID), + StartTime: plan.StartTime.ValueTimestampPointer(), + Tags: getTagsIn(ctx), + } output, err := conn.CreateRotation(ctx, input) @@ -222,16 +268,12 @@ func (r *resourceRotation) Create(ctx context.Context, request resource.CreateRe state := plan - response.Diagnostics.Append(flex.Flatten(ctx, output, &state)...) - state.ID = flex.StringToFramework(ctx, output.RotationArn) state.ARN = flex.StringToFramework(ctx, output.RotationArn) response.Diagnostics.Append(response.State.Set(ctx, &state)...) } -// Read is called when the provider must read resource values in order to update state. -// Planned state values should be read from the ReadRequest and new state values set on the ReadResponse. func (r *resourceRotation) Read(ctx context.Context, request resource.ReadRequest, response *resource.ReadResponse) { conn := r.Meta().SSMContactsClient(ctx) var state resourceRotationData @@ -257,30 +299,128 @@ func (r *resourceRotation) Read(ctx context.Context, request resource.ReadReques return } - response.Diagnostics.Append(flex.Flatten(ctx, output, &state)...) + rc := &recurrenceData{} + rc.RecurrenceMultiplier = flex.Int32ToFramework(ctx, output.Recurrence.RecurrenceMultiplier) + rc.NumberOfOnCalls = flex.Int32ToFramework(ctx, output.Recurrence.NumberOfOnCalls) + response.Diagnostics.Append(flex.Flatten(ctx, output.Recurrence.DailySettings, &rc.DailySettings)...) if response.Diagnostics.HasError() { return } + response.Diagnostics.Append(flex.Flatten(ctx, output.Recurrence.MonthlySettings, &rc.MonthlySettings)...) + if response.Diagnostics.HasError() { + return + } + + response.Diagnostics.Append(flex.Flatten(ctx, output.Recurrence.WeeklySettings, &rc.WeeklySettings)...) + if response.Diagnostics.HasError() { + return + } + + response.Diagnostics.Append(flex.Flatten(ctx, output.ContactIds, &state.ContactIds)...) + if response.Diagnostics.HasError() { + return + } + + rc.ShiftCoverages = flattenShiftCoveragesFW(ctx, output.Recurrence.ShiftCoverages) + + state.ARN = flex.StringToFramework(ctx, output.RotationArn) + state.Name = flex.StringToFramework(ctx, output.Name) + state.Recurrence = fwtypes.NewListNestedObjectValueOfPtr(ctx, rc) + state.TimeZoneID = flex.StringToFramework(ctx, output.TimeZoneId) + response.Diagnostics.Append(response.State.Set(ctx, &state)...) } func (r *resourceRotation) Update(ctx context.Context, request resource.UpdateRequest, response *resource.UpdateResponse) { - var plan resourceRotationData + conn := r.Meta().SSMContactsClient(ctx) + var state, plan resourceRotationData + + response.Diagnostics.Append(request.State.Get(ctx, &state)...) + + if response.Diagnostics.HasError() { + return + } - //response.Diagnostics.Append(request.State.Get(ctx, &old)...) - // - //if response.Diagnostics.HasError() { - // return - //} - // response.Diagnostics.Append(request.Plan.Get(ctx, &plan)...) if response.Diagnostics.HasError() { return } - //updateTimeout := r.UpdateTimeout(ctx, new.Timeouts) + + if !plan.Recurrence.Equal(state.Recurrence) || !plan.ContactIds.Equal(state.ContactIds) || + !plan.StartTime.Equal(state.StartTime) || !plan.TimeZoneID.Equal(state.TimeZoneID) { + + recurrenceData, diags := plan.Recurrence.ToPtr(ctx) + response.Diagnostics.Append(diags...) + if response.Diagnostics.HasError() { + return + } + + shiftCoveragesData, diags := recurrenceData.ShiftCoverages.ToSlice(ctx) + response.Diagnostics.Append(diags...) + if response.Diagnostics.HasError() { + return + } + + shiftCoverages := expandShiftCoverages(ctx, shiftCoveragesData, &response.Diagnostics) + if response.Diagnostics.HasError() { + return + } + + dailySettingsInput := objectForInput[handOffTime]{ + Data: recurrenceData.DailySettings, + } + dailySettingsOutput := objectForOutput[awstypes.HandOffTime]{} + response.Diagnostics.Append(flex.Expand(ctx, dailySettingsInput, &dailySettingsOutput)...) + if response.Diagnostics.HasError() { + return + } + + monthlySettingsInput := objectForInput[monthlySettingsData]{ + Data: recurrenceData.MonthlySettings, + } + monthlySettingsOutput := objectForOutput[awstypes.MonthlySetting]{} + response.Diagnostics.Append(flex.Expand(ctx, monthlySettingsInput, &monthlySettingsOutput)...) + if response.Diagnostics.HasError() { + return + } + + weeklySettingsInput := objectForInput[weeklySettingsData]{ + Data: recurrenceData.WeeklySettings, + } + weeklySettingsOutput := objectForOutput[awstypes.WeeklySetting]{} + response.Diagnostics.Append(flex.Expand(ctx, weeklySettingsInput, &weeklySettingsOutput)...) + if response.Diagnostics.HasError() { + return + } + + input := &ssmcontacts.UpdateRotationInput{ + RotationId: flex.StringFromFramework(ctx, state.ID), + Recurrence: &awstypes.RecurrenceSettings{ + NumberOfOnCalls: flex.Int32FromFramework(ctx, recurrenceData.NumberOfOnCalls), + RecurrenceMultiplier: flex.Int32FromFramework(ctx, recurrenceData.RecurrenceMultiplier), + DailySettings: dailySettingsOutput.Data, + MonthlySettings: monthlySettingsOutput.Data, + ShiftCoverages: shiftCoverages, + WeeklySettings: weeklySettingsOutput.Data, + }, + ContactIds: flex.ExpandFrameworkStringValueList(ctx, plan.ContactIds), + TimeZoneId: flex.StringFromFramework(ctx, plan.TimeZoneID), + StartTime: plan.StartTime.ValueTimestampPointer(), + } + + _, err := conn.UpdateRotation(ctx, input) + + if err != nil { + response.Diagnostics.AddError( + create.ProblemStandardMessage(names.SSMContacts, create.ErrActionUpdating, ResNameRotation, state.ID.ValueString(), err), + err.Error(), + ) + return + } + } response.Diagnostics.Append(response.State.Set(ctx, &plan)...) } @@ -316,10 +456,6 @@ func (r *resourceRotation) Delete(ctx context.Context, request resource.DeleteRe } } -func (r *resourceRotation) ImportState(ctx context.Context, request resource.ImportStateRequest, response *resource.ImportStateResponse) { - resource.ImportStatePassthroughID(ctx, path.Root("id"), request, response) -} - func (r *resourceRotation) ModifyPlan(ctx context.Context, request resource.ModifyPlanRequest, response *resource.ModifyPlanResponse) { r.SetTagsAll(ctx, request, response) } @@ -366,63 +502,91 @@ type handOffTime struct { } type weeklySettingsData struct { - DayOfWeek fwtypes.StringEnum[awstypes.DayOfWeek] `tfsdk:"day_of_week"` - HandOffTime types.String `tfsdk:"hand_off_time"` + DayOfWeek fwtypes.StringEnum[awstypes.DayOfWeek] `tfsdk:"day_of_week"` + HandOffTime fwtypes.ListNestedObjectValueOf[handOffTime] `tfsdk:"hand_off_time"` } -type shiftCoverages map[string][]awstypes.CoverageTime - -func newShiftCoverages() shiftCoverages { - sc := make(shiftCoverages) - return sc -} +func expandShiftCoverages(ctx context.Context, object []*shiftCoveragesData, diags *diag.Diagnostics) (result map[string][]awstypes.CoverageTime) { + if len(object) == 0 { + return + } -func (s shiftCoverages) expand(ctx context.Context, data *resourceRotationData) diag.Diagnostics { - var diags diag.Diagnostics + result = make(map[string][]awstypes.CoverageTime) + for _, v := range object { + covTimes, diagErr := v.CoverageTimes.ToSlice(ctx) + diags.Append(diagErr...) + if diags.HasError() { + return + } - var recurrence []recurrenceData - diags.Append(data.Recurrence.ElementsAs(ctx, &recurrence, false)...) + var cTimes []awstypes.CoverageTime + for _, val := range covTimes { + end, diagErr := val.End.ToPtr(ctx) + diags.Append(diagErr...) + if diags.HasError() { + return + } + start, diagErr := val.Start.ToPtr(ctx) + diags.Append(diagErr...) + if diags.HasError() { + return + } + + cTimes = append(cTimes, awstypes.CoverageTime{ + End: &awstypes.HandOffTime{ + HourOfDay: flex.Int32ValueFromFramework(ctx, end.HourOfDay), + MinuteOfHour: flex.Int32ValueFromFramework(ctx, end.MinuteOfHour), + }, + Start: &awstypes.HandOffTime{ + HourOfDay: flex.Int32ValueFromFramework(ctx, start.HourOfDay), + MinuteOfHour: flex.Int32ValueFromFramework(ctx, start.MinuteOfHour), + }, + }) + } - if diags.HasError() { - return diags + result[v.MapBlockKey.ValueString()] = cTimes } - var sc []shiftCoveragesData - diags.Append(recurrence[0].ShiftCoverages.ElementsAs(ctx, &sc, false)...) + return +} - for _, v := range sc { - var ct coverageTimes - diags.Append(v.CoverageTimes.ElementsAs(ctx, &ct, false)...) +func flattenShiftCoveragesFW(ctx context.Context, object map[string][]awstypes.CoverageTime) fwtypes.ListNestedObjectValueOf[shiftCoveragesData] { + if len(object) == 0 { + return fwtypes.NewListNestedObjectValueOfNull[shiftCoveragesData](ctx) + } - if diags.HasError() { - return diags + var output []shiftCoveragesData + for key, value := range object { + sc := shiftCoveragesData{ + MapBlockKey: fwtypes.StringEnumValue[awstypes.DayOfWeek](awstypes.DayOfWeek(key)), } - s[v.MapBlockKey.ValueString()] = ct.expand(ctx, diags) + var coverageTimes []coverageTimesData + for _, v := range value { + ct := coverageTimesData{ + End: fwtypes.NewListNestedObjectValueOfPtr(ctx, &handOffTime{ + HourOfDay: flex.Int32ValueToFramework(ctx, v.End.HourOfDay), + MinuteOfHour: flex.Int32ValueToFramework(ctx, v.End.MinuteOfHour), + }), + Start: fwtypes.NewListNestedObjectValueOfPtr(ctx, &handOffTime{ + HourOfDay: flex.Int32ValueToFramework(ctx, v.Start.HourOfDay), + MinuteOfHour: flex.Int32ValueToFramework(ctx, v.End.MinuteOfHour), + }), + } + coverageTimes = append(coverageTimes, ct) + } + sc.CoverageTimes = fwtypes.NewListNestedObjectValueOfValueSlice(ctx, coverageTimes) + output = append(output, sc) } - return diags + return fwtypes.NewListNestedObjectValueOfValueSlice[shiftCoveragesData](ctx, output) } -type coverageTimes []coverageTimesData - -func (c coverageTimes) expand(ctx context.Context, diags diag.Diagnostics) (result []awstypes.CoverageTime) { - for _, v := range c { - var start, end []handOffTime - diags.Append(v.End.ElementsAs(ctx, &end, false)...) - diags.Append(v.End.ElementsAs(ctx, &start, false)...) +type objectForOutput[T any] struct { + Data []T +} - result = append(result, awstypes.CoverageTime{ - End: &awstypes.HandOffTime{ - HourOfDay: flex.Int32ValueFromFramework(ctx, end[0].HourOfDay), - MinuteOfHour: flex.Int32ValueFromFramework(ctx, end[0].MinuteOfHour), - }, - Start: &awstypes.HandOffTime{ - HourOfDay: flex.Int32ValueFromFramework(ctx, start[0].HourOfDay), - MinuteOfHour: flex.Int32ValueFromFramework(ctx, start[0].MinuteOfHour), - }, - }) - } - return +type objectForInput[T any] struct { + Data fwtypes.ListNestedObjectValueOf[T] } diff --git a/internal/service/ssmcontacts/rotation_test.go b/internal/service/ssmcontacts/rotation_test.go index 08bfc5201abe..1223562026c6 100644 --- a/internal/service/ssmcontacts/rotation_test.go +++ b/internal/service/ssmcontacts/rotation_test.go @@ -103,7 +103,7 @@ func TestAccSSMContactsRotation_disappears(t *testing.T) { Config: testAccRotationConfig_basic(rName, recurrenceMultiplier, timeZoneId), Check: resource.ComposeTestCheckFunc( testAccCheckRotationExists(ctx, resourceName), - acctest.CheckResourceDisappears(ctx, acctest.Provider, tfssmcontacts.ResourceRotation(), resourceName), + acctest.CheckFrameworkResourceDisappears(ctx, acctest.Provider, tfssmcontacts.ResourceRotation, resourceName), ), ExpectNonEmptyPlan: true, }, @@ -643,9 +643,10 @@ resource "aws_ssmcontacts_rotation" "test" { recurrence { number_of_on_calls = 1 recurrence_multiplier = %[2]d - daily_settings = [ - "01:00" - ] + daily_settings { + hour_of_day = 1 + minute_of_hour = 00 + } } time_zone_id = %[3]q @@ -668,9 +669,10 @@ resource "aws_ssmcontacts_rotation" "test" { recurrence { number_of_on_calls = 1 recurrence_multiplier = 1 - daily_settings = [ - "01:00" - ] + daily_settings { + hour_of_day = 9 + minute_of_hour = 00 + } } start_time = %[2]q From 222b18e163a3e179f967936d4a77c89fd053e551 Mon Sep 17 00:00:00 2001 From: Adrian Johnson Date: Wed, 24 Jan 2024 20:37:23 -0600 Subject: [PATCH 04/22] aws_ssmcontacts_rotation: cleanup --- internal/service/ssmcontacts/exports_test.go | 4 + internal/service/ssmcontacts/find.go | 26 - .../ssmcontacts/rotation_data_source.go | 5 +- .../ssmcontacts/rotation_data_source_test.go | 138 ++--- internal/service/ssmcontacts/rotation_fw.go | 71 ++- internal/service/ssmcontacts/rotation_test.go | 562 +++++++----------- 6 files changed, 347 insertions(+), 459 deletions(-) diff --git a/internal/service/ssmcontacts/exports_test.go b/internal/service/ssmcontacts/exports_test.go index 6a7b0de6115b..6aacb41a7d0b 100644 --- a/internal/service/ssmcontacts/exports_test.go +++ b/internal/service/ssmcontacts/exports_test.go @@ -8,3 +8,7 @@ package ssmcontacts var ( ResourceRotation = newResourceRotation ) + +var ( + FindRotationByID = findRotationByID +) diff --git a/internal/service/ssmcontacts/find.go b/internal/service/ssmcontacts/find.go index a460e4754181..373b2ea09da8 100644 --- a/internal/service/ssmcontacts/find.go +++ b/internal/service/ssmcontacts/find.go @@ -10,9 +10,7 @@ import ( "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/service/ssmcontacts" "github.com/aws/aws-sdk-go-v2/service/ssmcontacts/types" - awstypes "github.com/aws/aws-sdk-go-v2/service/ssmcontacts/types" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry" - "github.com/hashicorp/terraform-provider-aws/internal/errs" "github.com/hashicorp/terraform-provider-aws/internal/tfresource" ) @@ -63,27 +61,3 @@ func findContactChannelByID(ctx context.Context, conn *ssmcontacts.Client, id st return out, nil } - -func FindRotationByID(ctx context.Context, conn *ssmcontacts.Client, id string) (*ssmcontacts.GetRotationOutput, error) { - in := &ssmcontacts.GetRotationInput{ - RotationId: aws.String(id), - } - out, err := conn.GetRotation(ctx, in) - - if errs.IsA[*awstypes.ResourceNotFoundException](err) { - return nil, &retry.NotFoundError{ - LastError: err, - LastRequest: in, - } - } - - if err != nil { - return nil, err - } - - if out == nil { - return nil, tfresource.NewEmptyResultError(in) - } - - return out, nil -} diff --git a/internal/service/ssmcontacts/rotation_data_source.go b/internal/service/ssmcontacts/rotation_data_source.go index 282762246b7f..f87c6cccae1a 100644 --- a/internal/service/ssmcontacts/rotation_data_source.go +++ b/internal/service/ssmcontacts/rotation_data_source.go @@ -2,6 +2,8 @@ package ssmcontacts import ( "context" + "time" + "github.com/aws/aws-sdk-go-v2/aws" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" @@ -9,7 +11,6 @@ import ( "github.com/hashicorp/terraform-provider-aws/internal/create" tftags "github.com/hashicorp/terraform-provider-aws/internal/tags" "github.com/hashicorp/terraform-provider-aws/names" - "time" ) // @SDKDataSource("aws_ssmcontacts_rotation") @@ -137,7 +138,7 @@ func dataSourceRotationRead(ctx context.Context, d *schema.ResourceData, meta in conn := meta.(*conns.AWSClient).SSMContactsClient(ctx) arn := d.Get("arn").(string) - out, err := FindRotationByID(ctx, conn, arn) + out, err := findRotationByID(ctx, conn, arn) if err != nil { return create.DiagError(names.SSMContacts, create.ErrActionReading, DSNameRotation, arn, err) } diff --git a/internal/service/ssmcontacts/rotation_data_source_test.go b/internal/service/ssmcontacts/rotation_data_source_test.go index c22337906b5c..1c72089d8317 100644 --- a/internal/service/ssmcontacts/rotation_data_source_test.go +++ b/internal/service/ssmcontacts/rotation_data_source_test.go @@ -169,66 +169,66 @@ resource "aws_ssmcontacts_contact" "test_contact_two" { resource "aws_ssmcontacts_contact" "test_contact_three" { alias = "test-contact-three-for-%[1]s" type = "PERSONAL" - + depends_on = [aws_ssmincidents_replication_set.test] } resource "aws_ssmcontacts_rotation" "test" { contact_ids = [ - aws_ssmcontacts_contact.test_contact_one.arn, - aws_ssmcontacts_contact.test_contact_two.arn, - aws_ssmcontacts_contact.test_contact_three.arn + aws_ssmcontacts_contact.test_contact_one.arn, + aws_ssmcontacts_contact.test_contact_two.arn, + aws_ssmcontacts_contact.test_contact_three.arn ] name = %[1]q recurrence { - number_of_on_calls = 1 - recurrence_multiplier = 1 - weekly_settings { - day_of_week = "MON" - hand_off_time = "04:25" - } - weekly_settings { - day_of_week = "WED" - hand_off_time = "07:34" - } - weekly_settings { - day_of_week = "FRI" - hand_off_time = "15:57" - } + number_of_on_calls = 1 + recurrence_multiplier = 1 + weekly_settings { + day_of_week = "MON" + hand_off_time = "04:25" + } + weekly_settings { + day_of_week = "WED" + hand_off_time = "07:34" + } + weekly_settings { + day_of_week = "FRI" + hand_off_time = "15:57" + } shift_coverages { - day_of_week = "MON" - coverage_times { - start_time = "01:00" - end_time = "23:00" - } - } - shift_coverages { - day_of_week = "WED" - coverage_times { - start_time = "01:00" - end_time = "23:00" - } - } - shift_coverages { - day_of_week = "FRI" - coverage_times { - start_time = "01:00" - end_time = "23:00" - } - } + day_of_week = "MON" + coverage_times { + start_time = "01:00" + end_time = "23:00" + } + } + shift_coverages { + day_of_week = "WED" + coverage_times { + start_time = "01:00" + end_time = "23:00" + } + } + shift_coverages { + day_of_week = "FRI" + coverage_times { + start_time = "01:00" + end_time = "23:00" + } + } } start_time = %[2]q - time_zone_id = "Australia/Sydney" + time_zone_id = "Australia/Sydney" tags = { - key1 = "tag1" - key2 = "tag2" + key1 = "tag1" + key2 = "tag2" } - + depends_on = [aws_ssmincidents_replication_set.test] } @@ -244,22 +244,22 @@ func testRotationDataSourceConfig_dailySettings(rName string) string { fmt.Sprintf(` resource "aws_ssmcontacts_rotation" "test" { contact_ids = [ - aws_ssmcontacts_contact.test_contact_one.arn, + aws_ssmcontacts_contact.test_contact_one.arn, ] name = %[1]q recurrence { - number_of_on_calls = 1 - recurrence_multiplier = 1 - daily_settings = [ - "18:00" - ] + number_of_on_calls = 1 + recurrence_multiplier = 1 + daily_settings = [ + "18:00" + ] } - - time_zone_id = "Australia/Sydney" - depends_on = [aws_ssmincidents_replication_set.test] + time_zone_id = "Australia/Sydney" + + depends_on = [aws_ssmincidents_replication_set.test] } data "aws_ssmcontacts_rotation" "test" { @@ -274,31 +274,31 @@ func testRotationDataSourceConfig_monthlySettings(rName string) string { fmt.Sprintf(` resource "aws_ssmcontacts_rotation" "test" { contact_ids = [ - aws_ssmcontacts_contact.test_contact_one.arn, + aws_ssmcontacts_contact.test_contact_one.arn, ] name = %[1]q recurrence { - number_of_on_calls = 1 - recurrence_multiplier = 1 - monthly_settings { - day_of_month = 20 - hand_off_time = "08:00" - } - monthly_settings { - day_of_month = 13 - hand_off_time = "12:34" - } - monthly_settings { - day_of_month = 1 - hand_off_time = "04:58" - } + number_of_on_calls = 1 + recurrence_multiplier = 1 + monthly_settings { + day_of_month = 20 + hand_off_time = "08:00" + } + monthly_settings { + day_of_month = 13 + hand_off_time = "12:34" + } + monthly_settings { + day_of_month = 1 + hand_off_time = "04:58" + } } - - time_zone_id = "Australia/Sydney" - depends_on = [aws_ssmincidents_replication_set.test] + time_zone_id = "Australia/Sydney" + + depends_on = [aws_ssmincidents_replication_set.test] } data "aws_ssmcontacts_rotation" "test" { diff --git a/internal/service/ssmcontacts/rotation_fw.go b/internal/service/ssmcontacts/rotation_fw.go index 1da26c56f6bf..a1839f20b8ac 100644 --- a/internal/service/ssmcontacts/rotation_fw.go +++ b/internal/service/ssmcontacts/rotation_fw.go @@ -19,6 +19,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-log/tflog" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/id" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry" "github.com/hashicorp/terraform-provider-aws/internal/create" "github.com/hashicorp/terraform-provider-aws/internal/errs" "github.com/hashicorp/terraform-provider-aws/internal/framework" @@ -211,29 +212,20 @@ func (r *resourceRotation) Create(ctx context.Context, request resource.CreateRe if response.Diagnostics.HasError() { return } - - dailySettingsInput := objectForInput[handOffTime]{ - Data: recurrenceData.DailySettings, - } - dailySettingsOutput := objectForOutput[awstypes.HandOffTime]{} + + dailySettingsInput, dailySettingsOutput := setupSerializationObjects[handOffTime, awstypes.HandOffTime](recurrenceData.DailySettings) response.Diagnostics.Append(flex.Expand(ctx, dailySettingsInput, &dailySettingsOutput)...) if response.Diagnostics.HasError() { return } - monthlySettingsInput := objectForInput[monthlySettingsData]{ - Data: recurrenceData.MonthlySettings, - } - monthlySettingsOutput := objectForOutput[awstypes.MonthlySetting]{} + monthlySettingsInput, monthlySettingsOutput := setupSerializationObjects[monthlySettingsData, awstypes.MonthlySetting](recurrenceData.MonthlySettings) response.Diagnostics.Append(flex.Expand(ctx, monthlySettingsInput, &monthlySettingsOutput)...) if response.Diagnostics.HasError() { return } - weeklySettingsInput := objectForInput[weeklySettingsData]{ - Data: recurrenceData.WeeklySettings, - } - weeklySettingsOutput := objectForOutput[awstypes.WeeklySetting]{} + weeklySettingsInput, weeklySettingsOutput := setupSerializationObjects[weeklySettingsData, awstypes.WeeklySetting](recurrenceData.WeeklySettings) response.Diagnostics.Append(flex.Expand(ctx, weeklySettingsInput, &weeklySettingsOutput)...) if response.Diagnostics.HasError() { return @@ -284,7 +276,7 @@ func (r *resourceRotation) Read(ctx context.Context, request resource.ReadReques return } - output, err := FindRotationByID(ctx, conn, state.ID.ValueString()) + output, err := findRotationByID(ctx, conn, state.ID.ValueString()) if tfresource.NotFound(err) { response.State.RemoveResource(ctx) @@ -369,28 +361,19 @@ func (r *resourceRotation) Update(ctx context.Context, request resource.UpdateRe return } - dailySettingsInput := objectForInput[handOffTime]{ - Data: recurrenceData.DailySettings, - } - dailySettingsOutput := objectForOutput[awstypes.HandOffTime]{} + dailySettingsInput, dailySettingsOutput := setupSerializationObjects[handOffTime, awstypes.HandOffTime](recurrenceData.DailySettings) response.Diagnostics.Append(flex.Expand(ctx, dailySettingsInput, &dailySettingsOutput)...) if response.Diagnostics.HasError() { return } - monthlySettingsInput := objectForInput[monthlySettingsData]{ - Data: recurrenceData.MonthlySettings, - } - monthlySettingsOutput := objectForOutput[awstypes.MonthlySetting]{} + monthlySettingsInput, monthlySettingsOutput := setupSerializationObjects[monthlySettingsData, awstypes.MonthlySetting](recurrenceData.MonthlySettings) response.Diagnostics.Append(flex.Expand(ctx, monthlySettingsInput, &monthlySettingsOutput)...) if response.Diagnostics.HasError() { return } - weeklySettingsInput := objectForInput[weeklySettingsData]{ - Data: recurrenceData.WeeklySettings, - } - weeklySettingsOutput := objectForOutput[awstypes.WeeklySetting]{} + weeklySettingsInput, weeklySettingsOutput := setupSerializationObjects[weeklySettingsData, awstypes.WeeklySetting](recurrenceData.WeeklySettings) response.Diagnostics.Append(flex.Expand(ctx, weeklySettingsInput, &weeklySettingsOutput)...) if response.Diagnostics.HasError() { return @@ -583,10 +566,42 @@ func flattenShiftCoveragesFW(ctx context.Context, object map[string][]awstypes.C return fwtypes.NewListNestedObjectValueOfValueSlice[shiftCoveragesData](ctx, output) } -type objectForOutput[T any] struct { - Data []T +func findRotationByID(ctx context.Context, conn *ssmcontacts.Client, id string) (*ssmcontacts.GetRotationOutput, error) { + in := &ssmcontacts.GetRotationInput{ + RotationId: aws.String(id), + } + out, err := conn.GetRotation(ctx, in) + + if errs.IsA[*awstypes.ResourceNotFoundException](err) { + return nil, &retry.NotFoundError{ + LastError: err, + LastRequest: in, + } + } + + if err != nil { + return nil, err + } + + if out == nil { + return nil, tfresource.NewEmptyResultError(in) + } + + return out, nil } type objectForInput[T any] struct { Data fwtypes.ListNestedObjectValueOf[T] } + +type objectForOutput[T any] struct { + Data []T +} + +func setupSerializationObjects[T any, V any](input fwtypes.ListNestedObjectValueOf[T]) (objectForInput[T], objectForOutput[V]) { + in := objectForInput[T]{ + Data: input, + } + + return in, objectForOutput[V]{} +} diff --git a/internal/service/ssmcontacts/rotation_test.go b/internal/service/ssmcontacts/rotation_test.go index 1223562026c6..bce6e09a9df9 100644 --- a/internal/service/ssmcontacts/rotation_test.go +++ b/internal/service/ssmcontacts/rotation_test.go @@ -5,22 +5,19 @@ import ( "errors" "fmt" "regexp" - "strconv" "strings" "testing" "time" - "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/service/ssmcontacts" - "github.com/aws/aws-sdk-go-v2/service/ssmcontacts/types" sdkacctest "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" "github.com/hashicorp/terraform-plugin-testing/helper/resource" "github.com/hashicorp/terraform-plugin-testing/terraform" "github.com/hashicorp/terraform-provider-aws/internal/acctest" "github.com/hashicorp/terraform-provider-aws/internal/conns" "github.com/hashicorp/terraform-provider-aws/internal/create" - tfssmcontacts "github.com/hashicorp/terraform-provider-aws/internal/service/ssmcontacts" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" "github.com/hashicorp/terraform-provider-aws/names" ) @@ -32,10 +29,8 @@ func TestAccSSMContactsRotation_basic(t *testing.T) { rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_ssmcontacts_rotation.test" - contactResourceName := "aws_ssmcontacts_contact.test_contact_one" timeZoneId := "Australia/Sydney" - numberOfOncalls := 1 recurrenceMultiplier := 1 resource.Test(t, resource.TestCase{ @@ -50,16 +45,15 @@ func TestAccSSMContactsRotation_basic(t *testing.T) { { Config: testAccRotationConfig_basic(rName, recurrenceMultiplier, timeZoneId), Check: resource.ComposeTestCheckFunc( - testAccCheckContactExists(ctx, contactResourceName), testAccCheckRotationExists(ctx, resourceName), resource.TestCheckResourceAttr(resourceName, "name", rName), resource.TestCheckResourceAttr(resourceName, "time_zone_id", timeZoneId), - resource.TestCheckResourceAttr(resourceName, "recurrence.0.number_of_on_calls", strconv.Itoa(numberOfOncalls)), - resource.TestCheckResourceAttr(resourceName, "recurrence.0.recurrence_multiplier", strconv.Itoa(recurrenceMultiplier)), + resource.TestCheckResourceAttr(resourceName, "recurrence.0.number_of_on_calls", "1"), + resource.TestCheckResourceAttr(resourceName, "recurrence.0.recurrence_multiplier", "1"), resource.TestCheckResourceAttr(resourceName, "recurrence.0.daily_settings.#", "1"), - resource.TestCheckResourceAttr(resourceName, "recurrence.0.daily_settings.0", "01:00"), + resource.TestCheckResourceAttr(resourceName, "recurrence.0.daily_settings.0.hour_of_day", "1"), + resource.TestCheckResourceAttr(resourceName, "recurrence.0.daily_settings.0.minute_of_hour", "0"), resource.TestCheckResourceAttr(resourceName, "contact_ids.#", "1"), - acctest.CheckResourceAttrRegionalARN(resourceName, "contact_ids.0", "ssm-contacts", "contact/test-contact-one-for-"+rName), acctest.MatchResourceAttrRegionalARN(resourceName, "arn", "ssm-contacts", regexp.MustCompile(`rotation\/+.`)), ), }, @@ -139,7 +133,7 @@ func TestAccSSMContactsRotation_updateRequiredFields(t *testing.T) { Check: resource.ComposeTestCheckFunc( testAccCheckRotationExists(ctx, resourceName), resource.TestCheckResourceAttr(resourceName, "time_zone_id", iniTimeZoneId), - resource.TestCheckResourceAttr(resourceName, "recurrence.0.recurrence_multiplier", strconv.Itoa(iniRecurrenceMultiplier)), + resource.TestCheckResourceAttr(resourceName, "recurrence.0.recurrence_multiplier", "1"), ), }, { @@ -152,14 +146,9 @@ func TestAccSSMContactsRotation_updateRequiredFields(t *testing.T) { Check: resource.ComposeTestCheckFunc( testAccCheckRotationExists(ctx, resourceName), resource.TestCheckResourceAttr(resourceName, "time_zone_id", updTimeZoneId), - resource.TestCheckResourceAttr(resourceName, "recurrence.0.recurrence_multiplier", strconv.Itoa(updRecurrenceMultiplier)), + resource.TestCheckResourceAttr(resourceName, "recurrence.0.recurrence_multiplier", "2"), ), }, - { - ResourceName: resourceName, - ImportState: true, - ImportStateVerify: true, - }, }, }) } @@ -298,11 +287,6 @@ func TestAccSSMContactsRotation_recurrence(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "recurrence.0.daily_settings.0", "18:00"), ), }, - { - ResourceName: resourceName, - ImportState: true, - ImportStateVerify: true, - }, { Config: testAccRotationConfig_recurrenceOneMonthlySetting(rName), Check: resource.ComposeTestCheckFunc( @@ -312,11 +296,6 @@ func TestAccSSMContactsRotation_recurrence(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "recurrence.0.monthly_settings.0.hand_off_time", "08:00"), ), }, - { - ResourceName: resourceName, - ImportState: true, - ImportStateVerify: true, - }, { Config: testAccRotationConfig_recurrenceMultipleMonthlySetting(rName), Check: resource.ComposeTestCheckFunc( @@ -328,11 +307,6 @@ func TestAccSSMContactsRotation_recurrence(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "recurrence.0.monthly_settings.1.hand_off_time", "12:34"), ), }, - { - ResourceName: resourceName, - ImportState: true, - ImportStateVerify: true, - }, { Config: testAccRotationConfig_recurrenceOneWeeklySettings(rName), Check: resource.ComposeTestCheckFunc( @@ -342,11 +316,6 @@ func TestAccSSMContactsRotation_recurrence(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "recurrence.0.weekly_settings.0.hand_off_time", "10:30"), ), }, - { - ResourceName: resourceName, - ImportState: true, - ImportStateVerify: true, - }, { Config: testAccRotationConfig_recurrenceMultipleWeeklySettings(rName), Check: resource.ComposeTestCheckFunc( @@ -358,11 +327,6 @@ func TestAccSSMContactsRotation_recurrence(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "recurrence.0.weekly_settings.1.hand_off_time", "15:57"), ), }, - { - ResourceName: resourceName, - ImportState: true, - ImportStateVerify: true, - }, { Config: testAccRotationConfig_recurrenceOneShiftCoverages(rName), Check: resource.ComposeTestCheckFunc( @@ -373,11 +337,6 @@ func TestAccSSMContactsRotation_recurrence(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "recurrence.0.shift_coverages.0.coverage_times.0.end_time", "17:00"), ), }, - { - ResourceName: resourceName, - ImportState: true, - ImportStateVerify: true, - }, { Config: testAccRotationConfig_recurrenceMultipleShiftCoverages(rName), Check: resource.ComposeTestCheckFunc( @@ -394,11 +353,6 @@ func TestAccSSMContactsRotation_recurrence(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "recurrence.0.shift_coverages.2.coverage_times.0.end_time", "23:00"), ), }, - { - ResourceName: resourceName, - ImportState: true, - ImportStateVerify: true, - }, }, }) } @@ -417,7 +371,6 @@ func TestAccSSMContactsRotation_tags(t *testing.T) { tagVal1Updated := sdkacctest.RandString(26) tagKey2 := sdkacctest.RandString(26) tagVal2 := sdkacctest.RandString(26) - tagVal2Updated := sdkacctest.RandString(26) resource.Test(t, resource.TestCase{ PreCheck: func() { @@ -428,18 +381,6 @@ func TestAccSSMContactsRotation_tags(t *testing.T) { ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, CheckDestroy: testAccCheckRotationDestroy(ctx), Steps: []resource.TestStep{ - { - Config: testAccRotationConfig_basic(rName, 1, "Australia/Sydney"), - Check: resource.ComposeTestCheckFunc( - testAccCheckRotationExists(ctx, resourceName), - resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), - ), - }, - { - ResourceName: resourceName, - ImportState: true, - ImportStateVerify: true, - }, { Config: testAccRotationConfig_oneTag(rName, tagKey1, tagVal1), Check: resource.ComposeTestCheckFunc( @@ -448,11 +389,6 @@ func TestAccSSMContactsRotation_tags(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "tags."+tagKey1, tagVal1), ), }, - { - ResourceName: resourceName, - ImportState: true, - ImportStateVerify: true, - }, { Config: testAccRotationConfig_multipleTags(rName, tagKey1, tagVal1, tagKey2, tagVal2), Check: resource.ComposeTestCheckFunc( @@ -462,25 +398,6 @@ func TestAccSSMContactsRotation_tags(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "tags."+tagKey2, tagVal2), ), }, - { - ResourceName: resourceName, - ImportState: true, - ImportStateVerify: true, - }, - { - Config: testAccRotationConfig_multipleTags(rName, tagKey1, tagVal1Updated, tagKey2, tagVal2Updated), - Check: resource.ComposeTestCheckFunc( - testAccCheckRotationExists(ctx, resourceName), - resource.TestCheckResourceAttr(resourceName, "tags.%", "2"), - resource.TestCheckResourceAttr(resourceName, "tags."+tagKey1, tagVal1Updated), - resource.TestCheckResourceAttr(resourceName, "tags."+tagKey2, tagVal2Updated), - ), - }, - { - ResourceName: resourceName, - ImportState: true, - ImportStateVerify: true, - }, { Config: testAccRotationConfig_oneTag(rName, tagKey1, tagVal1Updated), Check: resource.ComposeTestCheckFunc( @@ -489,23 +406,6 @@ func TestAccSSMContactsRotation_tags(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "tags."+tagKey1, tagVal1Updated), ), }, - { - ResourceName: resourceName, - ImportState: true, - ImportStateVerify: true, - }, - { - Config: testAccRotationConfig_basic(rName, 1, "Australia/Sydney"), - Check: resource.ComposeTestCheckFunc( - testAccCheckRotationExists(ctx, resourceName), - resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), - ), - }, - { - ResourceName: resourceName, - ImportState: true, - ImportStateVerify: true, - }, }, }) } @@ -519,19 +419,17 @@ func testAccCheckRotationDestroy(ctx context.Context) resource.TestCheckFunc { continue } - input := &ssmcontacts.GetRotationInput{ - RotationId: aws.String(rs.Primary.ID), + _, err := tfssmcontacts.FindRotationByID(ctx, conn, rs.Primary.ID) + + if tfresource.NotFound(err) { + continue } - _, err := conn.GetRotation(ctx, input) + if err != nil { if strings.Contains(err.Error(), "Invalid value provided - Account not found for the request") { continue } - var nfe *types.ResourceNotFoundException - if errors.As(err, &nfe) { - return nil - } return err } @@ -594,83 +492,58 @@ resource "aws_ssmincidents_replication_set" "test" { `, acctest.Region()) } -func testAccRotationConfig_base(alias string) string { +func testAccRotationConfig_base(alias string, contactCount int) string { return acctest.ConfigCompose( testAccRotationConfig_none(), fmt.Sprintf(` -resource "aws_ssmcontacts_contact" "test_contact_one" { - alias = "test-contact-one-for-%[1]s" +resource "aws_ssmcontacts_contact" "test" { + count = %[2]d + alias = %[1]q type = "PERSONAL" depends_on = [aws_ssmincidents_replication_set.test] } -`, alias)) -} - -func testAccRotationConfig_secondContact(alias string) string { - return fmt.Sprintf(` -resource "aws_ssmcontacts_contact" "test_contact_two" { - alias = "test-contact-two-for-%[1]s" - type = "PERSONAL" - - depends_on = [aws_ssmincidents_replication_set.test] -} -`, alias) -} - -func testAccRotationConfig_thirdContact(alias string) string { - return fmt.Sprintf(` -resource "aws_ssmcontacts_contact" "test_contact_three" { - alias = "test-contact-three-for-%[1]s" - type = "PERSONAL" - - depends_on = [aws_ssmincidents_replication_set.test] -} -`, alias) +`, alias, contactCount)) } func testAccRotationConfig_basic(rName string, recurrenceMultiplier int, timeZoneId string) string { return acctest.ConfigCompose( - testAccRotationConfig_base(rName), + testAccRotationConfig_base(rName, 1), fmt.Sprintf(` resource "aws_ssmcontacts_rotation" "test" { - contact_ids = [ - aws_ssmcontacts_contact.test_contact_one.arn - ] + contact_ids = aws_ssmcontacts_contact.test[*].arn name = %[1]q recurrence { - number_of_on_calls = 1 - recurrence_multiplier = %[2]d + number_of_on_calls = 1 + recurrence_multiplier = %[2]d daily_settings { hour_of_day = 1 minute_of_hour = 00 } } - - time_zone_id = %[3]q - depends_on = [aws_ssmincidents_replication_set.test] + time_zone_id = %[3]q + + depends_on = [aws_ssmincidents_replication_set.test] }`, rName, recurrenceMultiplier, timeZoneId)) } func testAccRotationConfig_startTime(rName, startTime string) string { return acctest.ConfigCompose( - testAccRotationConfig_base(rName), + testAccRotationConfig_base(rName, 1), fmt.Sprintf(` resource "aws_ssmcontacts_rotation" "test" { - contact_ids = [ - aws_ssmcontacts_contact.test_contact_one.arn - ] + contact_ids = aws_ssmcontacts_contact.test[*].arn name = %[1]q recurrence { - number_of_on_calls = 1 - recurrence_multiplier = 1 + number_of_on_calls = 1 + recurrence_multiplier = 1 daily_settings { - hour_of_day = 9 + hour_of_day = 1 minute_of_hour = 00 } } @@ -685,331 +558,352 @@ resource "aws_ssmcontacts_rotation" "test" { func testAccRotationConfig_twoContacts(rName string) string { return acctest.ConfigCompose( - testAccRotationConfig_base(rName), - testAccRotationConfig_secondContact(rName), + testAccRotationConfig_base(rName, 2), fmt.Sprintf(` resource "aws_ssmcontacts_rotation" "test" { - contact_ids = [ - aws_ssmcontacts_contact.test_contact_one.arn, - aws_ssmcontacts_contact.test_contact_two.arn - ] + contact_ids = aws_ssmcontacts_contact.test[*].arn name = %[1]q recurrence { - number_of_on_calls = 1 - recurrence_multiplier = 1 - daily_settings = [ - "01:00" - ] + number_of_on_calls = 1 + recurrence_multiplier = 1 + daily_settings { + hour_of_day = 1 + minute_of_hour = 00 + } } - time_zone_id = "Australia/Sydney" + time_zone_id = "Australia/Sydney" - depends_on = [aws_ssmincidents_replication_set.test] + depends_on = [aws_ssmincidents_replication_set.test] }`, rName)) } func testAccRotationConfig_threeContacts(rName string) string { return acctest.ConfigCompose( - testAccRotationConfig_base(rName), - testAccRotationConfig_secondContact(rName), - testAccRotationConfig_thirdContact(rName), + testAccRotationConfig_base(rName, 3), fmt.Sprintf(` resource "aws_ssmcontacts_rotation" "test" { - contact_ids = [ - aws_ssmcontacts_contact.test_contact_one.arn, - aws_ssmcontacts_contact.test_contact_two.arn, - aws_ssmcontacts_contact.test_contact_three.arn - ] + contact_ids = aws_ssmcontacts_contact.test[*].arn name = %[1]q recurrence { - number_of_on_calls = 1 - recurrence_multiplier = 1 - daily_settings = [ - "01:00" - ] + number_of_on_calls = 1 + recurrence_multiplier = 1 + daily_settings { + hour_of_day = 1 + minute_of_hour = 00 + } } - time_zone_id = "Australia/Sydney" + time_zone_id = "Australia/Sydney" - depends_on = [aws_ssmincidents_replication_set.test] + depends_on = [aws_ssmincidents_replication_set.test] }`, rName)) } func testAccRotationConfig_recurrenceDailySettings(rName string) string { return acctest.ConfigCompose( - testAccRotationConfig_base(rName), + testAccRotationConfig_base(rName, 1), fmt.Sprintf(` resource "aws_ssmcontacts_rotation" "test" { - contact_ids = [ - aws_ssmcontacts_contact.test_contact_one.arn - ] + contact_ids = aws_ssmcontacts_contact.test[*].arn name = %[1]q recurrence { - number_of_on_calls = 1 - recurrence_multiplier = 1 - daily_settings = [ - "18:00" - ] + number_of_on_calls = 1 + recurrence_multiplier = 1 + daily_settings { + hour_of_day = 1 + minute_of_hour = 00 + } } - - time_zone_id = "Australia/Sydney" - depends_on = [aws_ssmincidents_replication_set.test] + time_zone_id = "Australia/Sydney" + + depends_on = [aws_ssmincidents_replication_set.test] }`, rName)) } func testAccRotationConfig_recurrenceOneMonthlySetting(rName string) string { return acctest.ConfigCompose( - testAccRotationConfig_base(rName), + testAccRotationConfig_base(rName, 1), fmt.Sprintf(` resource "aws_ssmcontacts_rotation" "test" { - contact_ids = [ - aws_ssmcontacts_contact.test_contact_one.arn - ] + contact_ids = aws_ssmcontacts_contact.test[*].arn name = %[1]q recurrence { - number_of_on_calls = 1 - recurrence_multiplier = 1 - monthly_settings { - day_of_month = 20 - hand_off_time = "08:00" - } + number_of_on_calls = 1 + recurrence_multiplier = 1 + monthly_settings { + day_of_month = 20 + hand_off_time { + hour_of_day = 8 + minute_of_hour = 00 + } + } } - - time_zone_id = "Australia/Sydney" - depends_on = [aws_ssmincidents_replication_set.test] + time_zone_id = "Australia/Sydney" + + depends_on = [aws_ssmincidents_replication_set.test] }`, rName)) } func testAccRotationConfig_recurrenceMultipleMonthlySetting(rName string) string { return acctest.ConfigCompose( - testAccRotationConfig_base(rName), + testAccRotationConfig_base(rName, 1), fmt.Sprintf(` resource "aws_ssmcontacts_rotation" "test" { - contact_ids = [ - aws_ssmcontacts_contact.test_contact_one.arn - ] + contact_ids = aws_ssmcontacts_contact.test[*].arn name = %[1]q recurrence { - number_of_on_calls = 1 - recurrence_multiplier = 1 - monthly_settings { - day_of_month = 20 - hand_off_time = "08:00" - } - monthly_settings { - day_of_month = 13 - hand_off_time = "12:34" - } + number_of_on_calls = 1 + recurrence_multiplier = 1 + monthly_settings { + day_of_month = 20 + hand_off_time { + hour_of_day = 8 + minute_of_hour = 00 + } + } + monthly_settings { + day_of_month = 13 + hand_off_time { + hour_of_day = 12 + minute_of_hour = 34 + } + } } - - time_zone_id = "Australia/Sydney" - depends_on = [aws_ssmincidents_replication_set.test] + time_zone_id = "Australia/Sydney" + + depends_on = [aws_ssmincidents_replication_set.test] }`, rName)) } func testAccRotationConfig_recurrenceOneWeeklySettings(rName string) string { return acctest.ConfigCompose( - testAccRotationConfig_base(rName), + testAccRotationConfig_base(rName, 1), fmt.Sprintf(` resource "aws_ssmcontacts_rotation" "test" { - contact_ids = [ - aws_ssmcontacts_contact.test_contact_one.arn - ] + contact_ids = aws_ssmcontacts_contact.test[*].arn name = %[1]q recurrence { - number_of_on_calls = 1 - recurrence_multiplier = 1 - weekly_settings { - day_of_week = "MON" - hand_off_time = "10:30" - } + number_of_on_calls = 1 + recurrence_multiplier = 1 + weekly_settings { + day_of_week = "MON" + hand_off_time { + hour_of_day = 10 + minute_of_hour = 30 + } + } } - - time_zone_id = "Australia/Sydney" - depends_on = [aws_ssmincidents_replication_set.test] + time_zone_id = "Australia/Sydney" + + depends_on = [aws_ssmincidents_replication_set.test] }`, rName)) } func testAccRotationConfig_recurrenceMultipleWeeklySettings(rName string) string { return acctest.ConfigCompose( - testAccRotationConfig_base(rName), + testAccRotationConfig_base(rName, 1), fmt.Sprintf(` resource "aws_ssmcontacts_rotation" "test" { - contact_ids = [ - aws_ssmcontacts_contact.test_contact_one.arn - ] - + contact_ids = aws_ssmcontacts_contact.test[*].arn name = %[1]q recurrence { - number_of_on_calls = 1 - recurrence_multiplier = 1 - weekly_settings { - day_of_week = "WED" - hand_off_time = "04:25" - } + number_of_on_calls = 1 + recurrence_multiplier = 1 + weekly_settings { + day_of_week = "WED" + hand_off_time { + hour_of_day = 04 + minute_of_hour = 25 + } + } - weekly_settings { - day_of_week = "FRI" - hand_off_time = "15:57" - } + weekly_settings { + day_of_week = "FRI" + hand_off_time { + hour_of_day = 15 + minute_of_hour = 57 + } + } } - - time_zone_id = "Australia/Sydney" - depends_on = [aws_ssmincidents_replication_set.test] + time_zone_id = "Australia/Sydney" + + depends_on = [aws_ssmincidents_replication_set.test] }`, rName)) } func testAccRotationConfig_recurrenceOneShiftCoverages(rName string) string { return acctest.ConfigCompose( - testAccRotationConfig_base(rName), + testAccRotationConfig_base(rName, 1), fmt.Sprintf(` resource "aws_ssmcontacts_rotation" "test" { - contact_ids = [ - aws_ssmcontacts_contact.test_contact_one.arn - ] + contact_ids = aws_ssmcontacts_contact.test[*].arn name = %[1]q recurrence { - number_of_on_calls = 1 - recurrence_multiplier = 1 - daily_settings = [ - "09:00" - ] + number_of_on_calls = 1 + recurrence_multiplier = 1 + daily_settings { + hour_of_day = 9 + minute_of_hour = 00 + } shift_coverages { - day_of_week = "MON" - coverage_times { - start_time = "08:00" - end_time = "17:00" - } - } + map_block_key = "MON" + coverage_times { + start { + hour_of_day = 08 + minute_of_hour = 00 + } + end { + hour_of_day = 17 + minute_of_hour = 00 + } + } + } } - time_zone_id = "Australia/Sydney" + time_zone_id = "Australia/Sydney" - depends_on = [aws_ssmincidents_replication_set.test] + depends_on = [aws_ssmincidents_replication_set.test] }`, rName)) } func testAccRotationConfig_recurrenceMultipleShiftCoverages(rName string) string { return acctest.ConfigCompose( - testAccRotationConfig_base(rName), + testAccRotationConfig_base(rName, 1), fmt.Sprintf(` resource "aws_ssmcontacts_rotation" "test" { - contact_ids = [ - aws_ssmcontacts_contact.test_contact_one.arn - ] + contact_ids = aws_ssmcontacts_contact.test[*].arn name = %[1]q recurrence { - number_of_on_calls = 1 - recurrence_multiplier = 1 - daily_settings = [ - "09:00" - ] + number_of_on_calls = 1 + recurrence_multiplier = 1 + daily_settings { + hour_of_day = 9 + minute_of_hour = 00 + } + shift_coverages { - day_of_week = "MON" - coverage_times { - start_time = "01:00" - end_time = "23:00" - } - } - shift_coverages { - day_of_week = "WED" - coverage_times { - start_time = "01:00" - end_time = "23:00" - } - } - shift_coverages { - day_of_week = "FRI" - coverage_times { - start_time = "01:00" - end_time = "23:00" - } - } + day_of_week = "MON" + coverage_times { + start { + hour_of_day = 01 + minute_of_hour = 00 + } + end { + hour_of_day = 23 + minute_of_hour = 00 + } + } + } + shift_coverages { + day_of_week = "WED" + coverage_times { + start { + hour_of_day = 01 + minute_of_hour = 00 + } + end { + hour_of_day = 23 + minute_of_hour = 00 + } + } + } + shift_coverages { + day_of_week = "FRI" + coverage_times { + start { + hour_of_day = 01 + minute_of_hour = 00 + } + end { + hour_of_day = 23 + minute_of_hour = 00 + } + } + } } - time_zone_id = "Australia/Sydney" + time_zone_id = "Australia/Sydney" - depends_on = [aws_ssmincidents_replication_set.test] + depends_on = [aws_ssmincidents_replication_set.test] }`, rName)) } func testAccRotationConfig_oneTag(rName, tagKey, tagValue string) string { return acctest.ConfigCompose( - testAccRotationConfig_base(rName), + testAccRotationConfig_base(rName, 1), fmt.Sprintf(` resource "aws_ssmcontacts_rotation" "test" { - contact_ids = [ - aws_ssmcontacts_contact.test_contact_one.arn - ] + contact_ids = aws_ssmcontacts_contact.test[*].arn name = %[1]q recurrence { - number_of_on_calls = 1 - recurrence_multiplier = 1 - daily_settings = [ - "18:00" - ] + number_of_on_calls = 1 + recurrence_multiplier = 1 + daily_settings { + hour_of_day = 18 + minute_of_hour = 00 + } } - tags = { - %[2]q = %[3]q - } - - time_zone_id = "Australia/Sydney" + tags = { + %[2]q = %[3]q + } + + time_zone_id = "Australia/Sydney" - depends_on = [aws_ssmincidents_replication_set.test] + depends_on = [aws_ssmincidents_replication_set.test] }`, rName, tagKey, tagValue)) } func testAccRotationConfig_multipleTags(rName, tagKey1, tagVal1, tagKey2, tagVal2 string) string { return acctest.ConfigCompose( - testAccRotationConfig_base(rName), + testAccRotationConfig_base(rName, 1), fmt.Sprintf(` resource "aws_ssmcontacts_rotation" "test" { - contact_ids = [ - aws_ssmcontacts_contact.test_contact_one.arn - ] + contact_ids = aws_ssmcontacts_contact.test[*].arn name = %[1]q recurrence { - number_of_on_calls = 1 - recurrence_multiplier = 1 - daily_settings = [ - "18:00" - ] + number_of_on_calls = 1 + recurrence_multiplier = 1 + daily_settings { + hour_of_day = 18 + minute_of_hour = 00 + } } - tags = { + tags = { %[2]q = %[3]q %[4]q = %[5]q - } - - time_zone_id = "Australia/Sydney" + } - depends_on = [aws_ssmincidents_replication_set.test] + time_zone_id = "Australia/Sydney" + + depends_on = [aws_ssmincidents_replication_set.test] }`, rName, tagKey1, tagVal1, tagKey2, tagVal2)) } From 6a9637f4a3bd151d15db8cf9babf4bcd8dfac19c Mon Sep 17 00:00:00 2001 From: Adrian Johnson Date: Wed, 24 Jan 2024 20:38:04 -0600 Subject: [PATCH 05/22] rename file --- internal/service/ssmcontacts/rotation.go | 897 +++++++++++++------- internal/service/ssmcontacts/rotation_fw.go | 607 ------------- 2 files changed, 605 insertions(+), 899 deletions(-) delete mode 100644 internal/service/ssmcontacts/rotation_fw.go diff --git a/internal/service/ssmcontacts/rotation.go b/internal/service/ssmcontacts/rotation.go index 2a3d87277bcb..60069825625b 100644 --- a/internal/service/ssmcontacts/rotation.go +++ b/internal/service/ssmcontacts/rotation.go @@ -1,294 +1,607 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package ssmcontacts -//func ResourceRotation() *schema.Resource { -// return &schema.Resource{ -// CreateWithoutTimeout: resourceRotationCreate, -// ReadWithoutTimeout: resourceRotationRead, -// UpdateWithoutTimeout: resourceRotationUpdate, -// DeleteWithoutTimeout: resourceRotationDelete, -// -// Importer: &schema.ResourceImporter{ -// StateContext: schema.ImportStatePassthroughContext, -// }, -// -// Timeouts: &schema.ResourceTimeout{ -// Create: schema.DefaultTimeout(30 * time.Minute), -// Update: schema.DefaultTimeout(30 * time.Minute), -// Delete: schema.DefaultTimeout(30 * time.Minute), -// }, -// -// Schema: map[string]*schema.Schema{ -// "arn": { -// Type: schema.TypeString, -// Computed: true, -// }, -// "contact_ids": { -// Type: schema.TypeList, -// Required: true, -// Elem: &schema.Schema{ -// Type: schema.TypeString, -// }, -// }, -// "name": { -// Type: schema.TypeString, -// Required: true, -// }, -// "recurrence": { -// Type: schema.TypeList, -// Required: true, -// MaxItems: 1, -// Elem: &schema.Resource{ -// Schema: map[string]*schema.Schema{ -// "daily_settings": { -// Type: schema.TypeList, -// Optional: true, -// Elem: &schema.Schema{ -// Type: schema.TypeString, -// ValidateDiagFunc: validation.ToDiagFunc(handOffTimeValidator), -// }, -// }, -// "monthly_settings": { -// Type: schema.TypeList, -// Optional: true, -// Elem: &schema.Resource{ -// Schema: map[string]*schema.Schema{ -// "day_of_month": { -// Type: schema.TypeInt, -// Required: true, -// }, -// "hand_off_time": { -// Type: schema.TypeString, -// Required: true, -// ValidateDiagFunc: validation.ToDiagFunc(handOffTimeValidator), -// }, -// }, -// }, -// }, -// "number_of_on_calls": { -// Type: schema.TypeInt, -// Required: true, -// }, -// "recurrence_multiplier": { -// Type: schema.TypeInt, -// Required: true, -// }, -// "shift_coverages": { -// Type: schema.TypeList, -// Optional: true, -// Computed: true, // This is computed to allow for clearing the diff to handle erroneous diffs -// Elem: &schema.Resource{ -// Schema: map[string]*schema.Schema{ -// "coverage_times": { -// Type: schema.TypeList, -// MaxItems: 1, -// Required: true, -// Elem: &schema.Resource{ -// Schema: map[string]*schema.Schema{ -// "end_time": { -// Type: schema.TypeString, -// Optional: true, -// ValidateDiagFunc: validation.ToDiagFunc(handOffTimeValidator), -// }, -// "start_time": { -// Type: schema.TypeString, -// Optional: true, -// ValidateDiagFunc: validation.ToDiagFunc(handOffTimeValidator), -// }, -// }, -// }, -// }, -// "day_of_week": { -// Type: schema.TypeString, -// Required: true, -// ValidateDiagFunc: enum.Validate[types.DayOfWeek](), -// }, -// }, -// }, -// }, -// "weekly_settings": { -// Type: schema.TypeList, -// Optional: true, -// Elem: &schema.Resource{ -// Schema: map[string]*schema.Schema{ -// "day_of_week": { -// Type: schema.TypeString, -// Required: true, -// ValidateDiagFunc: enum.Validate[types.DayOfWeek](), -// }, -// "hand_off_time": { -// Type: schema.TypeString, -// Required: true, -// ValidateDiagFunc: validation.ToDiagFunc(handOffTimeValidator), -// }, -// }, -// }, -// }, -// }, -// }, -// }, -// "start_time": { -// Type: schema.TypeString, -// Optional: true, -// ValidateDiagFunc: validation.ToDiagFunc(validation.IsRFC3339Time), -// }, -// "time_zone_id": { -// Type: schema.TypeString, -// Required: true, -// }, -// names.AttrTags: tftags.TagsSchema(), -// names.AttrTagsAll: tftags.TagsSchemaComputed(), -// }, -// -// CustomizeDiff: customdiff.Sequence( -// func(_ context.Context, diff *schema.ResourceDiff, _ interface{}) error { -// if diff.HasChange("recurrence.0.shift_coverages") { -// o, n := diff.GetChange("recurrence.0.shift_coverages") -// -// sortShiftCoverages(o.([]interface{})) -// sortShiftCoverages(n.([]interface{})) -// -// isEqual := cmp.Diff(o, n) == "" -// -// if isEqual { -// return diff.Clear("recurrence.0.shift_coverages") -// } -// } -// -// return nil -// }, -// verify.SetTagsDiff, -// ), -// } -//} -// -////const ( -//// ResNameRotation = "Rotation" -////) -// -//var handOffTimeValidator = validation.StringMatch(regexp.MustCompile(`^\d\d:\d\d$`), "Time must be in 24-hour time format, e.g. \"01:00\"") -// -//func resourceRotationCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { -// conn := meta.(*conns.AWSClient).SSMContactsClient(ctx) -// -// in := &ssmcontacts.CreateRotationInput{ -// ContactIds: flex.ExpandStringValueList(d.Get("contact_ids").([]interface{})), -// Name: aws.String(d.Get("name").(string)), -// // Recurrence: expandRecurrence(d.Get("recurrence").([]interface{}), ctx), -// Tags: getTagsIn(ctx), -// TimeZoneId: aws.String(d.Get("time_zone_id").(string)), -// } -// -// if v, ok := d.GetOk("start_time"); ok { -// startTime, _ := time.Parse(time.RFC3339, v.(string)) -// in.StartTime = aws.Time(startTime) -// } -// -// out, err := conn.CreateRotation(ctx, in) -// if err != nil { -// return create.DiagError(names.SSMContacts, create.ErrActionCreating, ResNameRotation, d.Get("name").(string), err) -// } -// -// if out == nil { -// return create.DiagError(names.SSMContacts, create.ErrActionCreating, ResNameRotation, d.Get("name").(string), errors.New("empty output")) -// } -// -// d.SetId(aws.ToString(out.RotationArn)) -// -// return resourceRotationRead(ctx, d, meta) -//} -// -//func resourceRotationRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { -// conn := meta.(*conns.AWSClient).SSMContactsClient(ctx) -// -// out, err := FindRotationByID(ctx, conn, d.Id()) -// -// if !d.IsNewResource() && tfresource.NotFound(err) { -// log.Printf("[WARN] SSMContacts Rotation (%s) not found, removing from state", d.Id()) -// d.SetId("") -// return nil -// } -// -// if err != nil { -// return create.DiagError(names.SSMContacts, create.ErrActionReading, ResNameRotation, d.Id(), err) -// } -// -// d.Set("arn", out.RotationArn) -// d.Set("contact_ids", out.ContactIds) -// d.Set("name", out.Name) -// d.Set("time_zone_id", out.TimeZoneId) -// -// if out.StartTime != nil { -// d.Set("start_time", out.StartTime.Format(time.RFC3339)) -// } -// -// if err := d.Set("recurrence", flattenRecurrence(out.Recurrence, ctx)); err != nil { -// return create.DiagError(names.SSMContacts, create.ErrActionSetting, DSNameRotation, d.Id(), err) -// } -// -// return nil -//} -// -//func resourceRotationUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { -// conn := meta.(*conns.AWSClient).SSMContactsClient(ctx) -// -// update := false -// -// in := &ssmcontacts.UpdateRotationInput{ -// RotationId: aws.String(d.Id()), -// } -// -// if d.HasChanges("contact_ids") { -// in.ContactIds = flex.ExpandStringValueList(d.Get("contact_ids").([]interface{})) -// update = true -// } -// -// // Recurrence is a required field, but we don't want to force an update every time if no changes -// //in.Recurrence = expandRecurrence(d.Get("recurrence").([]interface{}), ctx) -// if d.HasChanges("recurrence") { -// update = true -// } -// -// if d.HasChanges("start_time") { -// startTime, _ := time.Parse(time.RFC3339, d.Get("start_time").(string)) -// in.StartTime = aws.Time(startTime) -// update = true -// } -// -// if d.HasChanges("time_zone_id") { -// in.TimeZoneId = aws.String(d.Get("time_zone_id").(string)) -// update = true -// } -// -// if !update { -// return nil -// } -// -// log.Printf("[DEBUG] Updating SSMContacts Rotation (%s): %#v", d.Id(), in) -// _, err := conn.UpdateRotation(ctx, in) -// if err != nil { -// return create.DiagError(names.SSMContacts, create.ErrActionUpdating, ResNameRotation, d.Id(), err) -// } -// -// return resourceRotationRead(ctx, d, meta) -//} -// -//func resourceRotationDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { -// conn := meta.(*conns.AWSClient).SSMContactsClient(ctx) -// -// log.Printf("[INFO] Deleting SSMContacts Rotation %s", d.Id()) -// -// _, err := conn.DeleteRotation(ctx, &ssmcontacts.DeleteRotationInput{ -// RotationId: aws.String(d.Id()), -// }) -// -// if err != nil { -// var nfe *types.ResourceNotFoundException -// if errors.As(err, &nfe) { -// return nil -// } -// -// return create.DiagError(names.SSMContacts, create.ErrActionDeleting, ResNameRotation, d.Id(), err) -// } -// -// return nil -//} +import ( + "context" + + "github.com/aws/aws-sdk-go-v2/service/ssmcontacts" + awstypes "github.com/aws/aws-sdk-go-v2/service/ssmcontacts/types" + "github.com/aws/aws-sdk-go/aws" + "github.com/hashicorp/terraform-plugin-framework-validators/listvalidator" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/listplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-log/tflog" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/id" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry" + "github.com/hashicorp/terraform-provider-aws/internal/create" + "github.com/hashicorp/terraform-provider-aws/internal/errs" + "github.com/hashicorp/terraform-provider-aws/internal/framework" + "github.com/hashicorp/terraform-provider-aws/internal/framework/flex" + fwtypes "github.com/hashicorp/terraform-provider-aws/internal/framework/types" + tftags "github.com/hashicorp/terraform-provider-aws/internal/tags" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" + "github.com/hashicorp/terraform-provider-aws/names" +) + +const ( + ResNameRotation = "Rotation" +) + +// @FrameworkResource(name="Rotation") +// @Tags(identifierAttribute="arn") +func newResourceRotation(context.Context) (resource.ResourceWithConfigure, error) { + r := &resourceRotation{} + + return r, nil +} + +type resourceRotation struct { + framework.ResourceWithConfigure + framework.WithImportByID +} + +func (r *resourceRotation) Metadata(_ context.Context, request resource.MetadataRequest, response *resource.MetadataResponse) { + response.TypeName = "aws_ssmcontacts_rotation" +} + +func (r *resourceRotation) Schema(ctx context.Context, request resource.SchemaRequest, response *resource.SchemaResponse) { + s := schema.Schema{ + Attributes: map[string]schema.Attribute{ + "arn": framework.ARNAttributeComputedOnly(), + "contact_ids": schema.ListAttribute{ + CustomType: fwtypes.ListOfStringType, + ElementType: types.StringType, + Required: true, + }, + "id": framework.IDAttribute(), + "name": schema.StringAttribute{ + Required: true, + }, + "start_time": schema.StringAttribute{ + CustomType: fwtypes.TimestampType, + Optional: true, + }, + names.AttrTags: tftags.TagsAttribute(), + names.AttrTagsAll: tftags.TagsAttributeComputedOnly(), + "time_zone_id": schema.StringAttribute{ + Required: true, + }, + }, + Blocks: map[string]schema.Block{ + "recurrence": schema.ListNestedBlock{ + CustomType: fwtypes.NewListNestedObjectTypeOf[recurrenceData](ctx), + Validators: []validator.List{ + listvalidator.IsRequired(), + listvalidator.SizeAtMost(1), + }, + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "number_of_on_calls": schema.Int64Attribute{ + Required: true, + }, + "recurrence_multiplier": schema.Int64Attribute{ + Required: true, + }, + }, + Blocks: map[string]schema.Block{ + "daily_settings": handOffTimeSchema(ctx, nil), + "monthly_settings": schema.ListNestedBlock{ + CustomType: fwtypes.NewListNestedObjectTypeOf[monthlySettingsData](ctx), + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "day_of_month": schema.Int64Attribute{ + Required: true, + }, + }, + Blocks: map[string]schema.Block{ + "hand_off_time": handOffTimeSchema(ctx, aws.Int(1)), + }, + }, + }, + "shift_coverages": schema.ListNestedBlock{ + CustomType: fwtypes.NewListNestedObjectTypeOf[shiftCoveragesData](ctx), + PlanModifiers: []planmodifier.List{ + ShiftCoveragesPlanModifier(), + listplanmodifier.UseStateForUnknown(), + }, + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "map_block_key": schema.StringAttribute{ + CustomType: fwtypes.StringEnumType[awstypes.DayOfWeek](), + Required: true, + }, + }, + Blocks: map[string]schema.Block{ + "coverage_times": schema.ListNestedBlock{ + CustomType: fwtypes.NewListNestedObjectTypeOf[coverageTimesData](ctx), + NestedObject: schema.NestedBlockObject{ + Blocks: map[string]schema.Block{ + "end": handOffTimeSchema(ctx, aws.Int(1)), + "start": handOffTimeSchema(ctx, aws.Int(1)), + }, + }, + Validators: []validator.List{ + listvalidator.IsRequired(), + listvalidator.SizeAtLeast(1), + }, + }, + }, + }, + }, + "weekly_settings": schema.ListNestedBlock{ + CustomType: fwtypes.NewListNestedObjectTypeOf[weeklySettingsData](ctx), + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "day_of_week": schema.StringAttribute{ + CustomType: fwtypes.StringEnumType[awstypes.DayOfWeek](), + Required: true, + }, + }, + Blocks: map[string]schema.Block{ + "hand_off_time": handOffTimeSchema(ctx, aws.Int(1)), + }, + }, + }, + }, + }, + }, + }, + } + + if s.Blocks == nil { + s.Blocks = make(map[string]schema.Block) + } + + response.Schema = s +} + +func handOffTimeSchema(ctx context.Context, size *int) schema.ListNestedBlock { + listSchema := schema.ListNestedBlock{ + CustomType: fwtypes.NewListNestedObjectTypeOf[handOffTime](ctx), + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "hour_of_day": schema.Int64Attribute{ + Required: true, + }, + "minute_of_hour": schema.Int64Attribute{ + Required: true, + }, + }, + }, + } + + if size != nil { + listSchema.Validators = []validator.List{ + listvalidator.SizeAtMost(*size), + } + } + + return listSchema +} + +func (r *resourceRotation) Create(ctx context.Context, request resource.CreateRequest, response *resource.CreateResponse) { + conn := r.Meta().SSMContactsClient(ctx) + var plan resourceRotationData + + response.Diagnostics.Append(request.Plan.Get(ctx, &plan)...) + + if response.Diagnostics.HasError() { + return + } + + recurrenceData, diags := plan.Recurrence.ToPtr(ctx) + response.Diagnostics.Append(diags...) + if response.Diagnostics.HasError() { + return + } + + shiftCoveragesData, diags := recurrenceData.ShiftCoverages.ToSlice(ctx) + response.Diagnostics.Append(diags...) + if response.Diagnostics.HasError() { + return + } + + shiftCoverages := expandShiftCoverages(ctx, shiftCoveragesData, &response.Diagnostics) + if response.Diagnostics.HasError() { + return + } + + dailySettingsInput, dailySettingsOutput := setupSerializationObjects[handOffTime, awstypes.HandOffTime](recurrenceData.DailySettings) + response.Diagnostics.Append(flex.Expand(ctx, dailySettingsInput, &dailySettingsOutput)...) + if response.Diagnostics.HasError() { + return + } + + monthlySettingsInput, monthlySettingsOutput := setupSerializationObjects[monthlySettingsData, awstypes.MonthlySetting](recurrenceData.MonthlySettings) + response.Diagnostics.Append(flex.Expand(ctx, monthlySettingsInput, &monthlySettingsOutput)...) + if response.Diagnostics.HasError() { + return + } + + weeklySettingsInput, weeklySettingsOutput := setupSerializationObjects[weeklySettingsData, awstypes.WeeklySetting](recurrenceData.WeeklySettings) + response.Diagnostics.Append(flex.Expand(ctx, weeklySettingsInput, &weeklySettingsOutput)...) + if response.Diagnostics.HasError() { + return + } + + input := &ssmcontacts.CreateRotationInput{ + IdempotencyToken: aws.String(id.UniqueId()), + ContactIds: flex.ExpandFrameworkStringValueList(ctx, plan.ContactIds), + Name: flex.StringFromFramework(ctx, plan.Name), + Recurrence: &awstypes.RecurrenceSettings{ + NumberOfOnCalls: flex.Int32FromFramework(ctx, recurrenceData.NumberOfOnCalls), + RecurrenceMultiplier: flex.Int32FromFramework(ctx, recurrenceData.RecurrenceMultiplier), + DailySettings: dailySettingsOutput.Data, + MonthlySettings: monthlySettingsOutput.Data, + ShiftCoverages: shiftCoverages, + WeeklySettings: weeklySettingsOutput.Data, + }, + TimeZoneId: flex.StringFromFramework(ctx, plan.TimeZoneID), + StartTime: plan.StartTime.ValueTimestampPointer(), + Tags: getTagsIn(ctx), + } + + output, err := conn.CreateRotation(ctx, input) + + if err != nil { + response.Diagnostics.AddError( + create.ProblemStandardMessage(names.SSMContacts, create.ErrActionCreating, ResNameRotation, plan.Name.ValueString(), err), + err.Error(), + ) + return + } + + state := plan + + state.ID = flex.StringToFramework(ctx, output.RotationArn) + state.ARN = flex.StringToFramework(ctx, output.RotationArn) + + response.Diagnostics.Append(response.State.Set(ctx, &state)...) +} + +func (r *resourceRotation) Read(ctx context.Context, request resource.ReadRequest, response *resource.ReadResponse) { + conn := r.Meta().SSMContactsClient(ctx) + var state resourceRotationData + + response.Diagnostics.Append(request.State.Get(ctx, &state)...) + + if response.Diagnostics.HasError() { + return + } + + output, err := findRotationByID(ctx, conn, state.ID.ValueString()) + + if tfresource.NotFound(err) { + response.State.RemoveResource(ctx) + return + } + + if err != nil { + response.Diagnostics.AddError( + create.ProblemStandardMessage(names.SSMContacts, create.ErrActionSetting, ResNameRotation, state.ID.ValueString(), err), + err.Error(), + ) + return + } + + rc := &recurrenceData{} + rc.RecurrenceMultiplier = flex.Int32ToFramework(ctx, output.Recurrence.RecurrenceMultiplier) + rc.NumberOfOnCalls = flex.Int32ToFramework(ctx, output.Recurrence.NumberOfOnCalls) + + response.Diagnostics.Append(flex.Flatten(ctx, output.Recurrence.DailySettings, &rc.DailySettings)...) + if response.Diagnostics.HasError() { + return + } + + response.Diagnostics.Append(flex.Flatten(ctx, output.Recurrence.MonthlySettings, &rc.MonthlySettings)...) + if response.Diagnostics.HasError() { + return + } + + response.Diagnostics.Append(flex.Flatten(ctx, output.Recurrence.WeeklySettings, &rc.WeeklySettings)...) + if response.Diagnostics.HasError() { + return + } + + response.Diagnostics.Append(flex.Flatten(ctx, output.ContactIds, &state.ContactIds)...) + if response.Diagnostics.HasError() { + return + } + + rc.ShiftCoverages = flattenShiftCoveragesFW(ctx, output.Recurrence.ShiftCoverages) + + state.ARN = flex.StringToFramework(ctx, output.RotationArn) + state.Name = flex.StringToFramework(ctx, output.Name) + state.Recurrence = fwtypes.NewListNestedObjectValueOfPtr(ctx, rc) + state.TimeZoneID = flex.StringToFramework(ctx, output.TimeZoneId) + + response.Diagnostics.Append(response.State.Set(ctx, &state)...) +} + +func (r *resourceRotation) Update(ctx context.Context, request resource.UpdateRequest, response *resource.UpdateResponse) { + conn := r.Meta().SSMContactsClient(ctx) + var state, plan resourceRotationData + + response.Diagnostics.Append(request.State.Get(ctx, &state)...) + + if response.Diagnostics.HasError() { + return + } + + response.Diagnostics.Append(request.Plan.Get(ctx, &plan)...) + + if response.Diagnostics.HasError() { + return + } + + if !plan.Recurrence.Equal(state.Recurrence) || !plan.ContactIds.Equal(state.ContactIds) || + !plan.StartTime.Equal(state.StartTime) || !plan.TimeZoneID.Equal(state.TimeZoneID) { + + recurrenceData, diags := plan.Recurrence.ToPtr(ctx) + response.Diagnostics.Append(diags...) + if response.Diagnostics.HasError() { + return + } + + shiftCoveragesData, diags := recurrenceData.ShiftCoverages.ToSlice(ctx) + response.Diagnostics.Append(diags...) + if response.Diagnostics.HasError() { + return + } + + shiftCoverages := expandShiftCoverages(ctx, shiftCoveragesData, &response.Diagnostics) + if response.Diagnostics.HasError() { + return + } + + dailySettingsInput, dailySettingsOutput := setupSerializationObjects[handOffTime, awstypes.HandOffTime](recurrenceData.DailySettings) + response.Diagnostics.Append(flex.Expand(ctx, dailySettingsInput, &dailySettingsOutput)...) + if response.Diagnostics.HasError() { + return + } + + monthlySettingsInput, monthlySettingsOutput := setupSerializationObjects[monthlySettingsData, awstypes.MonthlySetting](recurrenceData.MonthlySettings) + response.Diagnostics.Append(flex.Expand(ctx, monthlySettingsInput, &monthlySettingsOutput)...) + if response.Diagnostics.HasError() { + return + } + + weeklySettingsInput, weeklySettingsOutput := setupSerializationObjects[weeklySettingsData, awstypes.WeeklySetting](recurrenceData.WeeklySettings) + response.Diagnostics.Append(flex.Expand(ctx, weeklySettingsInput, &weeklySettingsOutput)...) + if response.Diagnostics.HasError() { + return + } + + input := &ssmcontacts.UpdateRotationInput{ + RotationId: flex.StringFromFramework(ctx, state.ID), + Recurrence: &awstypes.RecurrenceSettings{ + NumberOfOnCalls: flex.Int32FromFramework(ctx, recurrenceData.NumberOfOnCalls), + RecurrenceMultiplier: flex.Int32FromFramework(ctx, recurrenceData.RecurrenceMultiplier), + DailySettings: dailySettingsOutput.Data, + MonthlySettings: monthlySettingsOutput.Data, + ShiftCoverages: shiftCoverages, + WeeklySettings: weeklySettingsOutput.Data, + }, + ContactIds: flex.ExpandFrameworkStringValueList(ctx, plan.ContactIds), + TimeZoneId: flex.StringFromFramework(ctx, plan.TimeZoneID), + StartTime: plan.StartTime.ValueTimestampPointer(), + } + + _, err := conn.UpdateRotation(ctx, input) + + if err != nil { + response.Diagnostics.AddError( + create.ProblemStandardMessage(names.SSMContacts, create.ErrActionUpdating, ResNameRotation, state.ID.ValueString(), err), + err.Error(), + ) + return + } + } + + response.Diagnostics.Append(response.State.Set(ctx, &plan)...) +} + +func (r *resourceRotation) Delete(ctx context.Context, request resource.DeleteRequest, response *resource.DeleteResponse) { + conn := r.Meta().SSMContactsClient(ctx) + var state resourceRotationData + + response.Diagnostics.Append(request.State.Get(ctx, &state)...) + + if response.Diagnostics.HasError() { + return + } + + tflog.Debug(ctx, "deleting TODO", map[string]interface{}{ + "id": state.ID.ValueString(), + }) + + _, err := conn.DeleteRotation(ctx, &ssmcontacts.DeleteRotationInput{ + RotationId: flex.StringFromFramework(ctx, state.ID), + }) + + if errs.IsA[*awstypes.ResourceNotFoundException](err) { + return + } + + if err != nil { + response.Diagnostics.AddError( + create.ProblemStandardMessage(names.SSMContacts, create.ErrActionDeleting, ResNameRotation, state.ID.ValueString(), err), + err.Error(), + ) + return + } +} + +func (r *resourceRotation) ModifyPlan(ctx context.Context, request resource.ModifyPlanRequest, response *resource.ModifyPlanResponse) { + r.SetTagsAll(ctx, request, response) +} + +type resourceRotationData struct { + ARN types.String `tfsdk:"arn"` + ContactIds fwtypes.ListValueOf[types.String] `tfsdk:"contact_ids"` + ID types.String `tfsdk:"id"` + Recurrence fwtypes.ListNestedObjectValueOf[recurrenceData] `tfsdk:"recurrence"` + Name types.String `tfsdk:"name"` + StartTime fwtypes.Timestamp `tfsdk:"start_time"` + Tags types.Map `tfsdk:"tags"` + TagsAll types.Map `tfsdk:"tags_all"` + TimeZoneID types.String `tfsdk:"time_zone_id"` +} + +type recurrenceData struct { + DailySettings fwtypes.ListNestedObjectValueOf[handOffTime] `tfsdk:"daily_settings"` + MonthlySettings fwtypes.ListNestedObjectValueOf[monthlySettingsData] `tfsdk:"monthly_settings"` + NumberOfOnCalls types.Int64 `tfsdk:"number_of_on_calls"` + RecurrenceMultiplier types.Int64 `tfsdk:"recurrence_multiplier"` + ShiftCoverages fwtypes.ListNestedObjectValueOf[shiftCoveragesData] `tfsdk:"shift_coverages"` + WeeklySettings fwtypes.ListNestedObjectValueOf[weeklySettingsData] `tfsdk:"weekly_settings"` +} + +type monthlySettingsData struct { + DayOfMonth types.Int64 `tfsdk:"day_of_month"` + HandOffTime fwtypes.ListNestedObjectValueOf[handOffTime] `tfsdk:"hand_off_time"` +} + +type shiftCoveragesData struct { + CoverageTimes fwtypes.ListNestedObjectValueOf[coverageTimesData] `tfsdk:"coverage_times"` + MapBlockKey fwtypes.StringEnum[awstypes.DayOfWeek] `tfsdk:"map_block_key"` +} + +type coverageTimesData struct { + End fwtypes.ListNestedObjectValueOf[handOffTime] `tfsdk:"end"` + Start fwtypes.ListNestedObjectValueOf[handOffTime] `tfsdk:"start"` +} + +type handOffTime struct { + HourOfDay types.Int64 `tfsdk:"hour_of_day"` + MinuteOfHour types.Int64 `tfsdk:"minute_of_hour"` +} + +type weeklySettingsData struct { + DayOfWeek fwtypes.StringEnum[awstypes.DayOfWeek] `tfsdk:"day_of_week"` + HandOffTime fwtypes.ListNestedObjectValueOf[handOffTime] `tfsdk:"hand_off_time"` +} + +func expandShiftCoverages(ctx context.Context, object []*shiftCoveragesData, diags *diag.Diagnostics) (result map[string][]awstypes.CoverageTime) { + if len(object) == 0 { + return + } + + result = make(map[string][]awstypes.CoverageTime) + for _, v := range object { + covTimes, diagErr := v.CoverageTimes.ToSlice(ctx) + diags.Append(diagErr...) + if diags.HasError() { + return + } + + var cTimes []awstypes.CoverageTime + for _, val := range covTimes { + end, diagErr := val.End.ToPtr(ctx) + diags.Append(diagErr...) + if diags.HasError() { + return + } + start, diagErr := val.Start.ToPtr(ctx) + diags.Append(diagErr...) + if diags.HasError() { + return + } + + cTimes = append(cTimes, awstypes.CoverageTime{ + End: &awstypes.HandOffTime{ + HourOfDay: flex.Int32ValueFromFramework(ctx, end.HourOfDay), + MinuteOfHour: flex.Int32ValueFromFramework(ctx, end.MinuteOfHour), + }, + Start: &awstypes.HandOffTime{ + HourOfDay: flex.Int32ValueFromFramework(ctx, start.HourOfDay), + MinuteOfHour: flex.Int32ValueFromFramework(ctx, start.MinuteOfHour), + }, + }) + } + + result[v.MapBlockKey.ValueString()] = cTimes + } + + return +} + +func flattenShiftCoveragesFW(ctx context.Context, object map[string][]awstypes.CoverageTime) fwtypes.ListNestedObjectValueOf[shiftCoveragesData] { + if len(object) == 0 { + return fwtypes.NewListNestedObjectValueOfNull[shiftCoveragesData](ctx) + } + + var output []shiftCoveragesData + for key, value := range object { + sc := shiftCoveragesData{ + MapBlockKey: fwtypes.StringEnumValue[awstypes.DayOfWeek](awstypes.DayOfWeek(key)), + } + + var coverageTimes []coverageTimesData + for _, v := range value { + ct := coverageTimesData{ + End: fwtypes.NewListNestedObjectValueOfPtr(ctx, &handOffTime{ + HourOfDay: flex.Int32ValueToFramework(ctx, v.End.HourOfDay), + MinuteOfHour: flex.Int32ValueToFramework(ctx, v.End.MinuteOfHour), + }), + Start: fwtypes.NewListNestedObjectValueOfPtr(ctx, &handOffTime{ + HourOfDay: flex.Int32ValueToFramework(ctx, v.Start.HourOfDay), + MinuteOfHour: flex.Int32ValueToFramework(ctx, v.End.MinuteOfHour), + }), + } + coverageTimes = append(coverageTimes, ct) + } + sc.CoverageTimes = fwtypes.NewListNestedObjectValueOfValueSlice(ctx, coverageTimes) + + output = append(output, sc) + } + + return fwtypes.NewListNestedObjectValueOfValueSlice[shiftCoveragesData](ctx, output) +} + +func findRotationByID(ctx context.Context, conn *ssmcontacts.Client, id string) (*ssmcontacts.GetRotationOutput, error) { + in := &ssmcontacts.GetRotationInput{ + RotationId: aws.String(id), + } + out, err := conn.GetRotation(ctx, in) + + if errs.IsA[*awstypes.ResourceNotFoundException](err) { + return nil, &retry.NotFoundError{ + LastError: err, + LastRequest: in, + } + } + + if err != nil { + return nil, err + } + + if out == nil { + return nil, tfresource.NewEmptyResultError(in) + } + + return out, nil +} + +type objectForInput[T any] struct { + Data fwtypes.ListNestedObjectValueOf[T] +} + +type objectForOutput[T any] struct { + Data []T +} + +func setupSerializationObjects[T any, V any](input fwtypes.ListNestedObjectValueOf[T]) (objectForInput[T], objectForOutput[V]) { + in := objectForInput[T]{ + Data: input, + } + + return in, objectForOutput[V]{} +} diff --git a/internal/service/ssmcontacts/rotation_fw.go b/internal/service/ssmcontacts/rotation_fw.go deleted file mode 100644 index a1839f20b8ac..000000000000 --- a/internal/service/ssmcontacts/rotation_fw.go +++ /dev/null @@ -1,607 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package ssmcontacts - -import ( - "context" - - "github.com/aws/aws-sdk-go-v2/service/ssmcontacts" - awstypes "github.com/aws/aws-sdk-go-v2/service/ssmcontacts/types" - "github.com/aws/aws-sdk-go/aws" - "github.com/hashicorp/terraform-plugin-framework-validators/listvalidator" - "github.com/hashicorp/terraform-plugin-framework/diag" - "github.com/hashicorp/terraform-plugin-framework/resource" - "github.com/hashicorp/terraform-plugin-framework/resource/schema" - "github.com/hashicorp/terraform-plugin-framework/resource/schema/listplanmodifier" - "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" - "github.com/hashicorp/terraform-plugin-framework/schema/validator" - "github.com/hashicorp/terraform-plugin-framework/types" - "github.com/hashicorp/terraform-plugin-log/tflog" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/id" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry" - "github.com/hashicorp/terraform-provider-aws/internal/create" - "github.com/hashicorp/terraform-provider-aws/internal/errs" - "github.com/hashicorp/terraform-provider-aws/internal/framework" - "github.com/hashicorp/terraform-provider-aws/internal/framework/flex" - fwtypes "github.com/hashicorp/terraform-provider-aws/internal/framework/types" - tftags "github.com/hashicorp/terraform-provider-aws/internal/tags" - "github.com/hashicorp/terraform-provider-aws/internal/tfresource" - "github.com/hashicorp/terraform-provider-aws/names" -) - -const ( - ResNameRotation = "Rotation" -) - -// @FrameworkResource(name="Rotation") -// @Tags(identifierAttribute="arn") -func newResourceRotation(context.Context) (resource.ResourceWithConfigure, error) { - r := &resourceRotation{} - - return r, nil -} - -type resourceRotation struct { - framework.ResourceWithConfigure - framework.WithImportByID -} - -func (r *resourceRotation) Metadata(_ context.Context, request resource.MetadataRequest, response *resource.MetadataResponse) { - response.TypeName = "aws_ssmcontacts_rotation" -} - -func (r *resourceRotation) Schema(ctx context.Context, request resource.SchemaRequest, response *resource.SchemaResponse) { - s := schema.Schema{ - Attributes: map[string]schema.Attribute{ - "arn": framework.ARNAttributeComputedOnly(), - "contact_ids": schema.ListAttribute{ - CustomType: fwtypes.ListOfStringType, - ElementType: types.StringType, - Required: true, - }, - "id": framework.IDAttribute(), - "name": schema.StringAttribute{ - Required: true, - }, - "start_time": schema.StringAttribute{ - CustomType: fwtypes.TimestampType, - Optional: true, - }, - names.AttrTags: tftags.TagsAttribute(), - names.AttrTagsAll: tftags.TagsAttributeComputedOnly(), - "time_zone_id": schema.StringAttribute{ - Required: true, - }, - }, - Blocks: map[string]schema.Block{ - "recurrence": schema.ListNestedBlock{ - CustomType: fwtypes.NewListNestedObjectTypeOf[recurrenceData](ctx), - Validators: []validator.List{ - listvalidator.IsRequired(), - listvalidator.SizeAtMost(1), - }, - NestedObject: schema.NestedBlockObject{ - Attributes: map[string]schema.Attribute{ - "number_of_on_calls": schema.Int64Attribute{ - Required: true, - }, - "recurrence_multiplier": schema.Int64Attribute{ - Required: true, - }, - }, - Blocks: map[string]schema.Block{ - "daily_settings": handOffTimeSchema(ctx, nil), - "monthly_settings": schema.ListNestedBlock{ - CustomType: fwtypes.NewListNestedObjectTypeOf[monthlySettingsData](ctx), - NestedObject: schema.NestedBlockObject{ - Attributes: map[string]schema.Attribute{ - "day_of_month": schema.Int64Attribute{ - Required: true, - }, - }, - Blocks: map[string]schema.Block{ - "hand_off_time": handOffTimeSchema(ctx, aws.Int(1)), - }, - }, - }, - "shift_coverages": schema.ListNestedBlock{ - CustomType: fwtypes.NewListNestedObjectTypeOf[shiftCoveragesData](ctx), - PlanModifiers: []planmodifier.List{ - ShiftCoveragesPlanModifier(), - listplanmodifier.UseStateForUnknown(), - }, - NestedObject: schema.NestedBlockObject{ - Attributes: map[string]schema.Attribute{ - "map_block_key": schema.StringAttribute{ - CustomType: fwtypes.StringEnumType[awstypes.DayOfWeek](), - Required: true, - }, - }, - Blocks: map[string]schema.Block{ - "coverage_times": schema.ListNestedBlock{ - CustomType: fwtypes.NewListNestedObjectTypeOf[coverageTimesData](ctx), - NestedObject: schema.NestedBlockObject{ - Blocks: map[string]schema.Block{ - "end": handOffTimeSchema(ctx, aws.Int(1)), - "start": handOffTimeSchema(ctx, aws.Int(1)), - }, - }, - Validators: []validator.List{ - listvalidator.IsRequired(), - listvalidator.SizeAtLeast(1), - }, - }, - }, - }, - }, - "weekly_settings": schema.ListNestedBlock{ - CustomType: fwtypes.NewListNestedObjectTypeOf[weeklySettingsData](ctx), - NestedObject: schema.NestedBlockObject{ - Attributes: map[string]schema.Attribute{ - "day_of_week": schema.StringAttribute{ - CustomType: fwtypes.StringEnumType[awstypes.DayOfWeek](), - Required: true, - }, - }, - Blocks: map[string]schema.Block{ - "hand_off_time": handOffTimeSchema(ctx, aws.Int(1)), - }, - }, - }, - }, - }, - }, - }, - } - - if s.Blocks == nil { - s.Blocks = make(map[string]schema.Block) - } - - response.Schema = s -} - -func handOffTimeSchema(ctx context.Context, size *int) schema.ListNestedBlock { - listSchema := schema.ListNestedBlock{ - CustomType: fwtypes.NewListNestedObjectTypeOf[handOffTime](ctx), - NestedObject: schema.NestedBlockObject{ - Attributes: map[string]schema.Attribute{ - "hour_of_day": schema.Int64Attribute{ - Required: true, - }, - "minute_of_hour": schema.Int64Attribute{ - Required: true, - }, - }, - }, - } - - if size != nil { - listSchema.Validators = []validator.List{ - listvalidator.SizeAtMost(*size), - } - } - - return listSchema -} - -func (r *resourceRotation) Create(ctx context.Context, request resource.CreateRequest, response *resource.CreateResponse) { - conn := r.Meta().SSMContactsClient(ctx) - var plan resourceRotationData - - response.Diagnostics.Append(request.Plan.Get(ctx, &plan)...) - - if response.Diagnostics.HasError() { - return - } - - recurrenceData, diags := plan.Recurrence.ToPtr(ctx) - response.Diagnostics.Append(diags...) - if response.Diagnostics.HasError() { - return - } - - shiftCoveragesData, diags := recurrenceData.ShiftCoverages.ToSlice(ctx) - response.Diagnostics.Append(diags...) - if response.Diagnostics.HasError() { - return - } - - shiftCoverages := expandShiftCoverages(ctx, shiftCoveragesData, &response.Diagnostics) - if response.Diagnostics.HasError() { - return - } - - dailySettingsInput, dailySettingsOutput := setupSerializationObjects[handOffTime, awstypes.HandOffTime](recurrenceData.DailySettings) - response.Diagnostics.Append(flex.Expand(ctx, dailySettingsInput, &dailySettingsOutput)...) - if response.Diagnostics.HasError() { - return - } - - monthlySettingsInput, monthlySettingsOutput := setupSerializationObjects[monthlySettingsData, awstypes.MonthlySetting](recurrenceData.MonthlySettings) - response.Diagnostics.Append(flex.Expand(ctx, monthlySettingsInput, &monthlySettingsOutput)...) - if response.Diagnostics.HasError() { - return - } - - weeklySettingsInput, weeklySettingsOutput := setupSerializationObjects[weeklySettingsData, awstypes.WeeklySetting](recurrenceData.WeeklySettings) - response.Diagnostics.Append(flex.Expand(ctx, weeklySettingsInput, &weeklySettingsOutput)...) - if response.Diagnostics.HasError() { - return - } - - input := &ssmcontacts.CreateRotationInput{ - IdempotencyToken: aws.String(id.UniqueId()), - ContactIds: flex.ExpandFrameworkStringValueList(ctx, plan.ContactIds), - Name: flex.StringFromFramework(ctx, plan.Name), - Recurrence: &awstypes.RecurrenceSettings{ - NumberOfOnCalls: flex.Int32FromFramework(ctx, recurrenceData.NumberOfOnCalls), - RecurrenceMultiplier: flex.Int32FromFramework(ctx, recurrenceData.RecurrenceMultiplier), - DailySettings: dailySettingsOutput.Data, - MonthlySettings: monthlySettingsOutput.Data, - ShiftCoverages: shiftCoverages, - WeeklySettings: weeklySettingsOutput.Data, - }, - TimeZoneId: flex.StringFromFramework(ctx, plan.TimeZoneID), - StartTime: plan.StartTime.ValueTimestampPointer(), - Tags: getTagsIn(ctx), - } - - output, err := conn.CreateRotation(ctx, input) - - if err != nil { - response.Diagnostics.AddError( - create.ProblemStandardMessage(names.SSMContacts, create.ErrActionCreating, ResNameRotation, plan.Name.ValueString(), err), - err.Error(), - ) - return - } - - state := plan - - state.ID = flex.StringToFramework(ctx, output.RotationArn) - state.ARN = flex.StringToFramework(ctx, output.RotationArn) - - response.Diagnostics.Append(response.State.Set(ctx, &state)...) -} - -func (r *resourceRotation) Read(ctx context.Context, request resource.ReadRequest, response *resource.ReadResponse) { - conn := r.Meta().SSMContactsClient(ctx) - var state resourceRotationData - - response.Diagnostics.Append(request.State.Get(ctx, &state)...) - - if response.Diagnostics.HasError() { - return - } - - output, err := findRotationByID(ctx, conn, state.ID.ValueString()) - - if tfresource.NotFound(err) { - response.State.RemoveResource(ctx) - return - } - - if err != nil { - response.Diagnostics.AddError( - create.ProblemStandardMessage(names.SSMContacts, create.ErrActionSetting, ResNameRotation, state.ID.ValueString(), err), - err.Error(), - ) - return - } - - rc := &recurrenceData{} - rc.RecurrenceMultiplier = flex.Int32ToFramework(ctx, output.Recurrence.RecurrenceMultiplier) - rc.NumberOfOnCalls = flex.Int32ToFramework(ctx, output.Recurrence.NumberOfOnCalls) - - response.Diagnostics.Append(flex.Flatten(ctx, output.Recurrence.DailySettings, &rc.DailySettings)...) - if response.Diagnostics.HasError() { - return - } - - response.Diagnostics.Append(flex.Flatten(ctx, output.Recurrence.MonthlySettings, &rc.MonthlySettings)...) - if response.Diagnostics.HasError() { - return - } - - response.Diagnostics.Append(flex.Flatten(ctx, output.Recurrence.WeeklySettings, &rc.WeeklySettings)...) - if response.Diagnostics.HasError() { - return - } - - response.Diagnostics.Append(flex.Flatten(ctx, output.ContactIds, &state.ContactIds)...) - if response.Diagnostics.HasError() { - return - } - - rc.ShiftCoverages = flattenShiftCoveragesFW(ctx, output.Recurrence.ShiftCoverages) - - state.ARN = flex.StringToFramework(ctx, output.RotationArn) - state.Name = flex.StringToFramework(ctx, output.Name) - state.Recurrence = fwtypes.NewListNestedObjectValueOfPtr(ctx, rc) - state.TimeZoneID = flex.StringToFramework(ctx, output.TimeZoneId) - - response.Diagnostics.Append(response.State.Set(ctx, &state)...) -} - -func (r *resourceRotation) Update(ctx context.Context, request resource.UpdateRequest, response *resource.UpdateResponse) { - conn := r.Meta().SSMContactsClient(ctx) - var state, plan resourceRotationData - - response.Diagnostics.Append(request.State.Get(ctx, &state)...) - - if response.Diagnostics.HasError() { - return - } - - response.Diagnostics.Append(request.Plan.Get(ctx, &plan)...) - - if response.Diagnostics.HasError() { - return - } - - if !plan.Recurrence.Equal(state.Recurrence) || !plan.ContactIds.Equal(state.ContactIds) || - !plan.StartTime.Equal(state.StartTime) || !plan.TimeZoneID.Equal(state.TimeZoneID) { - - recurrenceData, diags := plan.Recurrence.ToPtr(ctx) - response.Diagnostics.Append(diags...) - if response.Diagnostics.HasError() { - return - } - - shiftCoveragesData, diags := recurrenceData.ShiftCoverages.ToSlice(ctx) - response.Diagnostics.Append(diags...) - if response.Diagnostics.HasError() { - return - } - - shiftCoverages := expandShiftCoverages(ctx, shiftCoveragesData, &response.Diagnostics) - if response.Diagnostics.HasError() { - return - } - - dailySettingsInput, dailySettingsOutput := setupSerializationObjects[handOffTime, awstypes.HandOffTime](recurrenceData.DailySettings) - response.Diagnostics.Append(flex.Expand(ctx, dailySettingsInput, &dailySettingsOutput)...) - if response.Diagnostics.HasError() { - return - } - - monthlySettingsInput, monthlySettingsOutput := setupSerializationObjects[monthlySettingsData, awstypes.MonthlySetting](recurrenceData.MonthlySettings) - response.Diagnostics.Append(flex.Expand(ctx, monthlySettingsInput, &monthlySettingsOutput)...) - if response.Diagnostics.HasError() { - return - } - - weeklySettingsInput, weeklySettingsOutput := setupSerializationObjects[weeklySettingsData, awstypes.WeeklySetting](recurrenceData.WeeklySettings) - response.Diagnostics.Append(flex.Expand(ctx, weeklySettingsInput, &weeklySettingsOutput)...) - if response.Diagnostics.HasError() { - return - } - - input := &ssmcontacts.UpdateRotationInput{ - RotationId: flex.StringFromFramework(ctx, state.ID), - Recurrence: &awstypes.RecurrenceSettings{ - NumberOfOnCalls: flex.Int32FromFramework(ctx, recurrenceData.NumberOfOnCalls), - RecurrenceMultiplier: flex.Int32FromFramework(ctx, recurrenceData.RecurrenceMultiplier), - DailySettings: dailySettingsOutput.Data, - MonthlySettings: monthlySettingsOutput.Data, - ShiftCoverages: shiftCoverages, - WeeklySettings: weeklySettingsOutput.Data, - }, - ContactIds: flex.ExpandFrameworkStringValueList(ctx, plan.ContactIds), - TimeZoneId: flex.StringFromFramework(ctx, plan.TimeZoneID), - StartTime: plan.StartTime.ValueTimestampPointer(), - } - - _, err := conn.UpdateRotation(ctx, input) - - if err != nil { - response.Diagnostics.AddError( - create.ProblemStandardMessage(names.SSMContacts, create.ErrActionUpdating, ResNameRotation, state.ID.ValueString(), err), - err.Error(), - ) - return - } - } - - response.Diagnostics.Append(response.State.Set(ctx, &plan)...) -} - -func (r *resourceRotation) Delete(ctx context.Context, request resource.DeleteRequest, response *resource.DeleteResponse) { - conn := r.Meta().SSMContactsClient(ctx) - var state resourceRotationData - - response.Diagnostics.Append(request.State.Get(ctx, &state)...) - - if response.Diagnostics.HasError() { - return - } - - tflog.Debug(ctx, "deleting TODO", map[string]interface{}{ - "id": state.ID.ValueString(), - }) - - _, err := conn.DeleteRotation(ctx, &ssmcontacts.DeleteRotationInput{ - RotationId: flex.StringFromFramework(ctx, state.ID), - }) - - if errs.IsA[*awstypes.ResourceNotFoundException](err) { - return - } - - if err != nil { - response.Diagnostics.AddError( - create.ProblemStandardMessage(names.SSMContacts, create.ErrActionDeleting, ResNameRotation, state.ID.ValueString(), err), - err.Error(), - ) - return - } -} - -func (r *resourceRotation) ModifyPlan(ctx context.Context, request resource.ModifyPlanRequest, response *resource.ModifyPlanResponse) { - r.SetTagsAll(ctx, request, response) -} - -type resourceRotationData struct { - ARN types.String `tfsdk:"arn"` - ContactIds fwtypes.ListValueOf[types.String] `tfsdk:"contact_ids"` - ID types.String `tfsdk:"id"` - Recurrence fwtypes.ListNestedObjectValueOf[recurrenceData] `tfsdk:"recurrence"` - Name types.String `tfsdk:"name"` - StartTime fwtypes.Timestamp `tfsdk:"start_time"` - Tags types.Map `tfsdk:"tags"` - TagsAll types.Map `tfsdk:"tags_all"` - TimeZoneID types.String `tfsdk:"time_zone_id"` -} - -type recurrenceData struct { - DailySettings fwtypes.ListNestedObjectValueOf[handOffTime] `tfsdk:"daily_settings"` - MonthlySettings fwtypes.ListNestedObjectValueOf[monthlySettingsData] `tfsdk:"monthly_settings"` - NumberOfOnCalls types.Int64 `tfsdk:"number_of_on_calls"` - RecurrenceMultiplier types.Int64 `tfsdk:"recurrence_multiplier"` - ShiftCoverages fwtypes.ListNestedObjectValueOf[shiftCoveragesData] `tfsdk:"shift_coverages"` - WeeklySettings fwtypes.ListNestedObjectValueOf[weeklySettingsData] `tfsdk:"weekly_settings"` -} - -type monthlySettingsData struct { - DayOfMonth types.Int64 `tfsdk:"day_of_month"` - HandOffTime fwtypes.ListNestedObjectValueOf[handOffTime] `tfsdk:"hand_off_time"` -} - -type shiftCoveragesData struct { - CoverageTimes fwtypes.ListNestedObjectValueOf[coverageTimesData] `tfsdk:"coverage_times"` - MapBlockKey fwtypes.StringEnum[awstypes.DayOfWeek] `tfsdk:"map_block_key"` -} - -type coverageTimesData struct { - End fwtypes.ListNestedObjectValueOf[handOffTime] `tfsdk:"end"` - Start fwtypes.ListNestedObjectValueOf[handOffTime] `tfsdk:"start"` -} - -type handOffTime struct { - HourOfDay types.Int64 `tfsdk:"hour_of_day"` - MinuteOfHour types.Int64 `tfsdk:"minute_of_hour"` -} - -type weeklySettingsData struct { - DayOfWeek fwtypes.StringEnum[awstypes.DayOfWeek] `tfsdk:"day_of_week"` - HandOffTime fwtypes.ListNestedObjectValueOf[handOffTime] `tfsdk:"hand_off_time"` -} - -func expandShiftCoverages(ctx context.Context, object []*shiftCoveragesData, diags *diag.Diagnostics) (result map[string][]awstypes.CoverageTime) { - if len(object) == 0 { - return - } - - result = make(map[string][]awstypes.CoverageTime) - for _, v := range object { - covTimes, diagErr := v.CoverageTimes.ToSlice(ctx) - diags.Append(diagErr...) - if diags.HasError() { - return - } - - var cTimes []awstypes.CoverageTime - for _, val := range covTimes { - end, diagErr := val.End.ToPtr(ctx) - diags.Append(diagErr...) - if diags.HasError() { - return - } - start, diagErr := val.Start.ToPtr(ctx) - diags.Append(diagErr...) - if diags.HasError() { - return - } - - cTimes = append(cTimes, awstypes.CoverageTime{ - End: &awstypes.HandOffTime{ - HourOfDay: flex.Int32ValueFromFramework(ctx, end.HourOfDay), - MinuteOfHour: flex.Int32ValueFromFramework(ctx, end.MinuteOfHour), - }, - Start: &awstypes.HandOffTime{ - HourOfDay: flex.Int32ValueFromFramework(ctx, start.HourOfDay), - MinuteOfHour: flex.Int32ValueFromFramework(ctx, start.MinuteOfHour), - }, - }) - } - - result[v.MapBlockKey.ValueString()] = cTimes - } - - return -} - -func flattenShiftCoveragesFW(ctx context.Context, object map[string][]awstypes.CoverageTime) fwtypes.ListNestedObjectValueOf[shiftCoveragesData] { - if len(object) == 0 { - return fwtypes.NewListNestedObjectValueOfNull[shiftCoveragesData](ctx) - } - - var output []shiftCoveragesData - for key, value := range object { - sc := shiftCoveragesData{ - MapBlockKey: fwtypes.StringEnumValue[awstypes.DayOfWeek](awstypes.DayOfWeek(key)), - } - - var coverageTimes []coverageTimesData - for _, v := range value { - ct := coverageTimesData{ - End: fwtypes.NewListNestedObjectValueOfPtr(ctx, &handOffTime{ - HourOfDay: flex.Int32ValueToFramework(ctx, v.End.HourOfDay), - MinuteOfHour: flex.Int32ValueToFramework(ctx, v.End.MinuteOfHour), - }), - Start: fwtypes.NewListNestedObjectValueOfPtr(ctx, &handOffTime{ - HourOfDay: flex.Int32ValueToFramework(ctx, v.Start.HourOfDay), - MinuteOfHour: flex.Int32ValueToFramework(ctx, v.End.MinuteOfHour), - }), - } - coverageTimes = append(coverageTimes, ct) - } - sc.CoverageTimes = fwtypes.NewListNestedObjectValueOfValueSlice(ctx, coverageTimes) - - output = append(output, sc) - } - - return fwtypes.NewListNestedObjectValueOfValueSlice[shiftCoveragesData](ctx, output) -} - -func findRotationByID(ctx context.Context, conn *ssmcontacts.Client, id string) (*ssmcontacts.GetRotationOutput, error) { - in := &ssmcontacts.GetRotationInput{ - RotationId: aws.String(id), - } - out, err := conn.GetRotation(ctx, in) - - if errs.IsA[*awstypes.ResourceNotFoundException](err) { - return nil, &retry.NotFoundError{ - LastError: err, - LastRequest: in, - } - } - - if err != nil { - return nil, err - } - - if out == nil { - return nil, tfresource.NewEmptyResultError(in) - } - - return out, nil -} - -type objectForInput[T any] struct { - Data fwtypes.ListNestedObjectValueOf[T] -} - -type objectForOutput[T any] struct { - Data []T -} - -func setupSerializationObjects[T any, V any](input fwtypes.ListNestedObjectValueOf[T]) (objectForInput[T], objectForOutput[V]) { - in := objectForInput[T]{ - Data: input, - } - - return in, objectForOutput[V]{} -} From 28176c792e7eab3e240237d486d28feb84432206 Mon Sep 17 00:00:00 2001 From: Adrian Johnson Date: Wed, 24 Jan 2024 20:39:24 -0600 Subject: [PATCH 06/22] fmt tests --- internal/service/ssmcontacts/rotation_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/service/ssmcontacts/rotation_test.go b/internal/service/ssmcontacts/rotation_test.go index bce6e09a9df9..9b140be1486e 100644 --- a/internal/service/ssmcontacts/rotation_test.go +++ b/internal/service/ssmcontacts/rotation_test.go @@ -722,7 +722,7 @@ func testAccRotationConfig_recurrenceMultipleWeeklySettings(rName string) string fmt.Sprintf(` resource "aws_ssmcontacts_rotation" "test" { contact_ids = aws_ssmcontacts_contact.test[*].arn - name = %[1]q + name = %[1]q recurrence { number_of_on_calls = 1 From 95cd56a1541b5a4f222f6cd6f1531674a2480ab5 Mon Sep 17 00:00:00 2001 From: Adrian Johnson Date: Wed, 24 Jan 2024 20:46:11 -0600 Subject: [PATCH 07/22] chore: cleanup --- .../framework/types/list_nested_objectof.go | 60 ------------------- 1 file changed, 60 deletions(-) diff --git a/internal/framework/types/list_nested_objectof.go b/internal/framework/types/list_nested_objectof.go index 0f2234a9c12c..9a60f67f93e4 100644 --- a/internal/framework/types/list_nested_objectof.go +++ b/internal/framework/types/list_nested_objectof.go @@ -169,10 +169,6 @@ func (v ListNestedObjectValueOf[T]) ToObjectSlice(ctx context.Context) (any, dia return v.ToSlice(ctx) } -func (v ListNestedObjectValueOf[T]) ToObjectValueSlice(ctx context.Context) (any, diag.Diagnostics) { - return v.ToValueSlice(ctx) -} - // ToPtr returns a pointer to the single element of a ListNestedObject. func (v ListNestedObjectValueOf[T]) ToPtr(ctx context.Context) (*T, diag.Diagnostics) { return nestedObjectValueObjectPtr[T](ctx, v.ListValue) @@ -183,10 +179,6 @@ func (v ListNestedObjectValueOf[T]) ToSlice(ctx context.Context) ([]*T, diag.Dia return nestedObjectValueObjectSlice[T](ctx, v.ListValue) } -func (v ListNestedObjectValueOf[T]) ToValueSlice(ctx context.Context) ([]T, diag.Diagnostics) { - return nestedObjectValueObjectValueSlice[T](ctx, v.ListValue) -} - func nestedObjectValueObjectPtr[T any](ctx context.Context, val valueWithElements) (*T, diag.Diagnostics) { var diags diag.Diagnostics @@ -207,27 +199,6 @@ func nestedObjectValueObjectPtr[T any](ctx context.Context, val valueWithElement } } -func nestedObjectValueObject[T any](ctx context.Context, val valueWithElements) (T, diag.Diagnostics) { - var diags diag.Diagnostics - var zero T - - elements := val.Elements() - switch n := len(elements); n { - case 0: - return zero, diags - case 1: - value, d := nestedObjectValueObjectFromElement[T](ctx, elements[0]) - diags.Append(d...) - if diags.HasError() { - return zero, diags - } - return value, diags - default: - diags.Append(diag.NewErrorDiagnostic("Invalid list/set", fmt.Sprintf("too many elements: want 1, got %d", n))) - return zero, diags - } -} - func nestedObjectValueObjectSlice[T any](ctx context.Context, val valueWithElements) ([]*T, diag.Diagnostics) { var diags diag.Diagnostics @@ -247,25 +218,6 @@ func nestedObjectValueObjectSlice[T any](ctx context.Context, val valueWithEleme return slice, diags } -func nestedObjectValueObjectValueSlice[T any](ctx context.Context, val valueWithElements) ([]T, diag.Diagnostics) { - var diags diag.Diagnostics - - elements := val.Elements() - n := len(elements) - slice := make([]T, n) - for i := 0; i < n; i++ { - ptr, d := nestedObjectValueObjectFromElement[T](ctx, elements[i]) - diags.Append(d...) - if diags.HasError() { - return nil, diags - } - - slice[i] = ptr - } - - return slice, diags -} - func nestedObjectValueObjectPtrFromElement[T any](ctx context.Context, val attr.Value) (*T, diag.Diagnostics) { var diags diag.Diagnostics @@ -278,18 +230,6 @@ func nestedObjectValueObjectPtrFromElement[T any](ctx context.Context, val attr. return ptr, diags } -func nestedObjectValueObjectFromElement[T any](ctx context.Context, val attr.Value) (T, diag.Diagnostics) { - var diags diag.Diagnostics - - var zero T - diags.Append(val.(ObjectValueOf[T]).ObjectValue.As(ctx, zero, basetypes.ObjectAsOptions{})...) - if diags.HasError() { - return zero, diags - } - - return zero, diags -} - func NewListNestedObjectValueOfNull[T any](ctx context.Context) ListNestedObjectValueOf[T] { return ListNestedObjectValueOf[T]{ListValue: basetypes.NewListNull(NewObjectTypeOf[T](ctx))} } From 02f4d3b917012cdf973b2a5b294ec8840c825013 Mon Sep 17 00:00:00 2001 From: Adrian Johnson Date: Wed, 24 Jan 2024 20:49:02 -0600 Subject: [PATCH 08/22] liters --- go.mod | 2 +- internal/service/ssmcontacts/rotation_test.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index bddc535b4955..1a6a8d3ae69e 100644 --- a/go.mod +++ b/go.mod @@ -46,6 +46,7 @@ require ( github.com/aws/aws-sdk-go-v2/service/ecr v1.24.7 github.com/aws/aws-sdk-go-v2/service/eks v1.37.1 github.com/aws/aws-sdk-go-v2/service/elasticache v1.34.7 + github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2 v1.26.7 github.com/aws/aws-sdk-go-v2/service/emr v1.36.1 github.com/aws/aws-sdk-go-v2/service/emrserverless v1.15.0 github.com/aws/aws-sdk-go-v2/service/evidently v1.17.0 @@ -174,7 +175,6 @@ require ( github.com/aws/aws-sdk-go-v2/internal/ini v1.7.2 // indirect github.com/aws/aws-sdk-go-v2/internal/v4a v1.2.10 // indirect github.com/aws/aws-sdk-go-v2/service/dynamodb v1.26.7 // indirect - github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2 v1.26.7 // indirect github.com/aws/aws-sdk-go-v2/service/iam v1.28.6 // indirect github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.4 // indirect github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.2.10 // indirect diff --git a/internal/service/ssmcontacts/rotation_test.go b/internal/service/ssmcontacts/rotation_test.go index 9b140be1486e..c276494f738f 100644 --- a/internal/service/ssmcontacts/rotation_test.go +++ b/internal/service/ssmcontacts/rotation_test.go @@ -4,11 +4,11 @@ import ( "context" "errors" "fmt" - "regexp" "strings" "testing" "time" + "github.com/YakDriver/regexache" "github.com/aws/aws-sdk-go-v2/service/ssmcontacts" sdkacctest "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" "github.com/hashicorp/terraform-plugin-testing/helper/resource" @@ -54,7 +54,7 @@ func TestAccSSMContactsRotation_basic(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "recurrence.0.daily_settings.0.hour_of_day", "1"), resource.TestCheckResourceAttr(resourceName, "recurrence.0.daily_settings.0.minute_of_hour", "0"), resource.TestCheckResourceAttr(resourceName, "contact_ids.#", "1"), - acctest.MatchResourceAttrRegionalARN(resourceName, "arn", "ssm-contacts", regexp.MustCompile(`rotation\/+.`)), + acctest.MatchResourceAttrRegionalARN(resourceName, "arn", "ssm-contacts", regexache.MustCompile(`rotation\/+.`)), ), }, { From 4cd57c8c14767d0ecfd203ba272d07bda7920790 Mon Sep 17 00:00:00 2001 From: Adrian Johnson Date: Wed, 24 Jan 2024 20:51:21 -0600 Subject: [PATCH 09/22] add copyright headers --- internal/service/ssmcontacts/planmodifiers.go | 3 +++ internal/service/ssmcontacts/rotation_data_source.go | 3 +++ internal/service/ssmcontacts/rotation_test.go | 3 +++ internal/service/ssmcontacts/sweep.go | 3 +++ 4 files changed, 12 insertions(+) diff --git a/internal/service/ssmcontacts/planmodifiers.go b/internal/service/ssmcontacts/planmodifiers.go index 6617819a2f39..d1fe37da46f7 100644 --- a/internal/service/ssmcontacts/planmodifiers.go +++ b/internal/service/ssmcontacts/planmodifiers.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package ssmcontacts import ( diff --git a/internal/service/ssmcontacts/rotation_data_source.go b/internal/service/ssmcontacts/rotation_data_source.go index f87c6cccae1a..6733f41cfbbe 100644 --- a/internal/service/ssmcontacts/rotation_data_source.go +++ b/internal/service/ssmcontacts/rotation_data_source.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package ssmcontacts import ( diff --git a/internal/service/ssmcontacts/rotation_test.go b/internal/service/ssmcontacts/rotation_test.go index c276494f738f..bdec45a0a378 100644 --- a/internal/service/ssmcontacts/rotation_test.go +++ b/internal/service/ssmcontacts/rotation_test.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package ssmcontacts_test import ( diff --git a/internal/service/ssmcontacts/sweep.go b/internal/service/ssmcontacts/sweep.go index c573dfdd7678..6bd6af95607e 100644 --- a/internal/service/ssmcontacts/sweep.go +++ b/internal/service/ssmcontacts/sweep.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package ssmcontacts import ( From 9739ad1c83a14842e3fff1b094d1639820c0001c Mon Sep 17 00:00:00 2001 From: Adrian Johnson Date: Wed, 24 Jan 2024 20:52:52 -0600 Subject: [PATCH 10/22] add copyright headers --- internal/service/ssmcontacts/rotation_data_source_test.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/internal/service/ssmcontacts/rotation_data_source_test.go b/internal/service/ssmcontacts/rotation_data_source_test.go index 1c72089d8317..a886088c286b 100644 --- a/internal/service/ssmcontacts/rotation_data_source_test.go +++ b/internal/service/ssmcontacts/rotation_data_source_test.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package ssmcontacts_test import ( From 192128cad890ac909d190f1687384f86ed29b34c Mon Sep 17 00:00:00 2001 From: Adrian Johnson Date: Thu, 25 Jan 2024 12:14:01 -0600 Subject: [PATCH 11/22] aws_rekognition_project: testing --- internal/service/ssmcontacts/rotation_test.go | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/internal/service/ssmcontacts/rotation_test.go b/internal/service/ssmcontacts/rotation_test.go index bdec45a0a378..4d6bf3900608 100644 --- a/internal/service/ssmcontacts/rotation_test.go +++ b/internal/service/ssmcontacts/rotation_test.go @@ -233,8 +233,6 @@ func TestAccSSMContactsRotation_contactIds(t *testing.T) { testAccCheckContactExists(ctx, firstContactResourceName), testAccCheckContactExists(ctx, secondContactResourceName), resource.TestCheckResourceAttr(resourceName, "contact_ids.#", "2"), - acctest.CheckResourceAttrRegionalARN(resourceName, "contact_ids.0", "ssm-contacts", "contact/test-contact-one-for-"+rName), - acctest.CheckResourceAttrRegionalARN(resourceName, "contact_ids.1", "ssm-contacts", "contact/test-contact-two-for-"+rName), ), }, { @@ -250,16 +248,8 @@ func TestAccSSMContactsRotation_contactIds(t *testing.T) { testAccCheckContactExists(ctx, secondContactResourceName), testAccCheckContactExists(ctx, thirdContactResourceName), resource.TestCheckResourceAttr(resourceName, "contact_ids.#", "3"), - acctest.CheckResourceAttrRegionalARN(resourceName, "contact_ids.0", "ssm-contacts", "contact/test-contact-one-for-"+rName), - acctest.CheckResourceAttrRegionalARN(resourceName, "contact_ids.1", "ssm-contacts", "contact/test-contact-two-for-"+rName), - acctest.CheckResourceAttrRegionalARN(resourceName, "contact_ids.2", "ssm-contacts", "contact/test-contact-three-for-"+rName), ), }, - { - ResourceName: resourceName, - ImportState: true, - ImportStateVerify: true, - }, }, }) } @@ -287,7 +277,8 @@ func TestAccSSMContactsRotation_recurrence(t *testing.T) { Check: resource.ComposeTestCheckFunc( testAccCheckRotationExists(ctx, resourceName), resource.TestCheckResourceAttr(resourceName, "recurrence.0.daily_settings.#", "1"), - resource.TestCheckResourceAttr(resourceName, "recurrence.0.daily_settings.0", "18:00"), + resource.TestCheckResourceAttr(resourceName, "recurrence.0.daily_settings.0.hour_of_day", "18"), + resource.TestCheckResourceAttr(resourceName, "recurrence.0.daily_settings.0.minute_of_hour", "0"), ), }, { From 3d7192bc67ff484b5f0c925d7392bc45dc228910 Mon Sep 17 00:00:00 2001 From: Adrian Johnson Date: Wed, 31 Jan 2024 12:05:16 -0600 Subject: [PATCH 12/22] aws_ssmcontacts_rotation: make tests serial --- internal/service/ssmcontacts/rotation.go | 27 ++++-- internal/service/ssmcontacts/rotation_test.go | 93 ++++++++++--------- .../service/ssmcontacts/ssmcontacts_test.go | 14 +-- internal/service/ssmincidents/sweep.go | 67 +++++++++++++ internal/sweep/register_gen_test.go | 2 + 5 files changed, 140 insertions(+), 63 deletions(-) create mode 100644 internal/service/ssmincidents/sweep.go diff --git a/internal/service/ssmcontacts/rotation.go b/internal/service/ssmcontacts/rotation.go index 60069825625b..30455f18ff91 100644 --- a/internal/service/ssmcontacts/rotation.go +++ b/internal/service/ssmcontacts/rotation.go @@ -5,6 +5,7 @@ package ssmcontacts import ( "context" + "time" "github.com/aws/aws-sdk-go-v2/service/ssmcontacts" awstypes "github.com/aws/aws-sdk-go-v2/service/ssmcontacts/types" @@ -244,10 +245,13 @@ func (r *resourceRotation) Create(ctx context.Context, request resource.CreateRe WeeklySettings: weeklySettingsOutput.Data, }, TimeZoneId: flex.StringFromFramework(ctx, plan.TimeZoneID), - StartTime: plan.StartTime.ValueTimestampPointer(), Tags: getTagsIn(ctx), } + if !plan.StartTime.IsNull() || !plan.StartTime.IsUnknown() { + input.StartTime = plan.StartTime.ValueTimestampPointer() + } + output, err := conn.CreateRotation(ctx, input) if err != nil { @@ -322,6 +326,10 @@ func (r *resourceRotation) Read(ctx context.Context, request resource.ReadReques state.Recurrence = fwtypes.NewListNestedObjectValueOfPtr(ctx, rc) state.TimeZoneID = flex.StringToFramework(ctx, output.TimeZoneId) + if output.StartTime != nil { + state.StartTime = fwtypes.TimestampValue(output.StartTime.Format(time.RFC3339)) + } + response.Diagnostics.Append(response.State.Set(ctx, &state)...) } @@ -343,7 +351,6 @@ func (r *resourceRotation) Update(ctx context.Context, request resource.UpdateRe if !plan.Recurrence.Equal(state.Recurrence) || !plan.ContactIds.Equal(state.ContactIds) || !plan.StartTime.Equal(state.StartTime) || !plan.TimeZoneID.Equal(state.TimeZoneID) { - recurrenceData, diags := plan.Recurrence.ToPtr(ctx) response.Diagnostics.Append(diags...) if response.Diagnostics.HasError() { @@ -489,17 +496,17 @@ type weeklySettingsData struct { HandOffTime fwtypes.ListNestedObjectValueOf[handOffTime] `tfsdk:"hand_off_time"` } -func expandShiftCoverages(ctx context.Context, object []*shiftCoveragesData, diags *diag.Diagnostics) (result map[string][]awstypes.CoverageTime) { +func expandShiftCoverages(ctx context.Context, object []*shiftCoveragesData, diags *diag.Diagnostics) map[string][]awstypes.CoverageTime { if len(object) == 0 { - return + return nil } - result = make(map[string][]awstypes.CoverageTime) + result := make(map[string][]awstypes.CoverageTime) for _, v := range object { covTimes, diagErr := v.CoverageTimes.ToSlice(ctx) diags.Append(diagErr...) if diags.HasError() { - return + return nil } var cTimes []awstypes.CoverageTime @@ -507,12 +514,12 @@ func expandShiftCoverages(ctx context.Context, object []*shiftCoveragesData, dia end, diagErr := val.End.ToPtr(ctx) diags.Append(diagErr...) if diags.HasError() { - return + return nil } start, diagErr := val.Start.ToPtr(ctx) diags.Append(diagErr...) if diags.HasError() { - return + return nil } cTimes = append(cTimes, awstypes.CoverageTime{ @@ -530,7 +537,7 @@ func expandShiftCoverages(ctx context.Context, object []*shiftCoveragesData, dia result[v.MapBlockKey.ValueString()] = cTimes } - return + return result } func flattenShiftCoveragesFW(ctx context.Context, object map[string][]awstypes.CoverageTime) fwtypes.ListNestedObjectValueOf[shiftCoveragesData] { @@ -598,7 +605,7 @@ type objectForOutput[T any] struct { Data []T } -func setupSerializationObjects[T any, V any](input fwtypes.ListNestedObjectValueOf[T]) (objectForInput[T], objectForOutput[V]) { +func setupSerializationObjects[T any, V any](input fwtypes.ListNestedObjectValueOf[T]) (objectForInput[T], objectForOutput[V]) { //nolint:unparam in := objectForInput[T]{ Data: input, } diff --git a/internal/service/ssmcontacts/rotation_test.go b/internal/service/ssmcontacts/rotation_test.go index 4d6bf3900608..77f87fa86ced 100644 --- a/internal/service/ssmcontacts/rotation_test.go +++ b/internal/service/ssmcontacts/rotation_test.go @@ -24,7 +24,7 @@ import ( "github.com/hashicorp/terraform-provider-aws/names" ) -func TestAccSSMContactsRotation_basic(t *testing.T) { +func testRotation_basic(t *testing.T) { ctx := acctest.Context(t) if testing.Short() { t.Skip("skipping long-running test in short mode") @@ -69,14 +69,14 @@ func TestAccSSMContactsRotation_basic(t *testing.T) { // We need to explicitly test destroying this resource instead of just using CheckDestroy, // because CheckDestroy will run after the replication set has been destroyed and destroying // the replication set will destroy all other resources. - Config: testAccRotationConfig_none(), + Config: testAccRotationConfig_replicationSetBase(), Check: testAccCheckRotationDestroy(ctx), }, }, }) } -func TestAccSSMContactsRotation_disappears(t *testing.T) { +func testRotation_disappears(t *testing.T) { ctx := acctest.Context(t) if testing.Short() { t.Skip("skipping long-running test in short mode") @@ -108,7 +108,7 @@ func TestAccSSMContactsRotation_disappears(t *testing.T) { }) } -func TestAccSSMContactsRotation_updateRequiredFields(t *testing.T) { +func testRotation_updateRequiredFields(t *testing.T) { ctx := acctest.Context(t) if testing.Short() { t.Skip("skipping long-running test in short mode") @@ -156,7 +156,7 @@ func TestAccSSMContactsRotation_updateRequiredFields(t *testing.T) { }) } -func TestAccSSMContactsRotation_startTime(t *testing.T) { +func testRotation_startTime(t *testing.T) { ctx := acctest.Context(t) if testing.Short() { t.Skip("skipping long-running test in short mode") @@ -196,16 +196,11 @@ func TestAccSSMContactsRotation_startTime(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "start_time", updStartTime), ), }, - { - ResourceName: resourceName, - ImportState: true, - ImportStateVerify: true, - }, }, }) } -func TestAccSSMContactsRotation_contactIds(t *testing.T) { +func testRotation_contactIds(t *testing.T) { ctx := acctest.Context(t) if testing.Short() { t.Skip("skipping long-running test in short mode") @@ -213,9 +208,6 @@ func TestAccSSMContactsRotation_contactIds(t *testing.T) { rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_ssmcontacts_rotation.test" - firstContactResourceName := "aws_ssmcontacts_contact.test_contact_one" - secondContactResourceName := "aws_ssmcontacts_contact.test_contact_two" - thirdContactResourceName := "aws_ssmcontacts_contact.test_contact_three" resource.Test(t, resource.TestCase{ PreCheck: func() { @@ -230,8 +222,6 @@ func TestAccSSMContactsRotation_contactIds(t *testing.T) { Config: testAccRotationConfig_twoContacts(rName), Check: resource.ComposeTestCheckFunc( testAccCheckRotationExists(ctx, resourceName), - testAccCheckContactExists(ctx, firstContactResourceName), - testAccCheckContactExists(ctx, secondContactResourceName), resource.TestCheckResourceAttr(resourceName, "contact_ids.#", "2"), ), }, @@ -244,9 +234,6 @@ func TestAccSSMContactsRotation_contactIds(t *testing.T) { Config: testAccRotationConfig_threeContacts(rName), Check: resource.ComposeTestCheckFunc( testAccCheckRotationExists(ctx, resourceName), - testAccCheckContactExists(ctx, firstContactResourceName), - testAccCheckContactExists(ctx, secondContactResourceName), - testAccCheckContactExists(ctx, thirdContactResourceName), resource.TestCheckResourceAttr(resourceName, "contact_ids.#", "3"), ), }, @@ -254,7 +241,7 @@ func TestAccSSMContactsRotation_contactIds(t *testing.T) { }) } -func TestAccSSMContactsRotation_recurrence(t *testing.T) { +func testRotation_recurrence(t *testing.T) { ctx := acctest.Context(t) if testing.Short() { t.Skip("skipping long-running test in short mode") @@ -277,7 +264,7 @@ func TestAccSSMContactsRotation_recurrence(t *testing.T) { Check: resource.ComposeTestCheckFunc( testAccCheckRotationExists(ctx, resourceName), resource.TestCheckResourceAttr(resourceName, "recurrence.0.daily_settings.#", "1"), - resource.TestCheckResourceAttr(resourceName, "recurrence.0.daily_settings.0.hour_of_day", "18"), + resource.TestCheckResourceAttr(resourceName, "recurrence.0.daily_settings.0.hour_of_day", "1"), resource.TestCheckResourceAttr(resourceName, "recurrence.0.daily_settings.0.minute_of_hour", "0"), ), }, @@ -287,7 +274,8 @@ func TestAccSSMContactsRotation_recurrence(t *testing.T) { testAccCheckRotationExists(ctx, resourceName), resource.TestCheckResourceAttr(resourceName, "recurrence.0.monthly_settings.#", "1"), resource.TestCheckResourceAttr(resourceName, "recurrence.0.monthly_settings.0.day_of_month", "20"), - resource.TestCheckResourceAttr(resourceName, "recurrence.0.monthly_settings.0.hand_off_time", "08:00"), + resource.TestCheckResourceAttr(resourceName, "recurrence.0.monthly_settings.0.hand_off_time.0.hour_of_day", "8"), + resource.TestCheckResourceAttr(resourceName, "recurrence.0.monthly_settings.0.hand_off_time.0.minute_of_hour", "0"), ), }, { @@ -296,9 +284,11 @@ func TestAccSSMContactsRotation_recurrence(t *testing.T) { testAccCheckRotationExists(ctx, resourceName), resource.TestCheckResourceAttr(resourceName, "recurrence.0.monthly_settings.#", "2"), resource.TestCheckResourceAttr(resourceName, "recurrence.0.monthly_settings.0.day_of_month", "20"), - resource.TestCheckResourceAttr(resourceName, "recurrence.0.monthly_settings.0.hand_off_time", "08:00"), + resource.TestCheckResourceAttr(resourceName, "recurrence.0.monthly_settings.0.hand_off_time.0.hour_of_day", "8"), + resource.TestCheckResourceAttr(resourceName, "recurrence.0.monthly_settings.0.hand_off_time.0.minute_of_hour", "0"), resource.TestCheckResourceAttr(resourceName, "recurrence.0.monthly_settings.1.day_of_month", "13"), - resource.TestCheckResourceAttr(resourceName, "recurrence.0.monthly_settings.1.hand_off_time", "12:34"), + resource.TestCheckResourceAttr(resourceName, "recurrence.0.monthly_settings.1.hand_off_time.0.hour_of_day", "12"), + resource.TestCheckResourceAttr(resourceName, "recurrence.0.monthly_settings.1.hand_off_time.0.minute_of_hour", "34"), ), }, { @@ -307,7 +297,8 @@ func TestAccSSMContactsRotation_recurrence(t *testing.T) { testAccCheckRotationExists(ctx, resourceName), resource.TestCheckResourceAttr(resourceName, "recurrence.0.weekly_settings.#", "1"), resource.TestCheckResourceAttr(resourceName, "recurrence.0.weekly_settings.0.day_of_week", "MON"), - resource.TestCheckResourceAttr(resourceName, "recurrence.0.weekly_settings.0.hand_off_time", "10:30"), + resource.TestCheckResourceAttr(resourceName, "recurrence.0.weekly_settings.0.hand_off_time.0.hour_of_day", "10"), + resource.TestCheckResourceAttr(resourceName, "recurrence.0.weekly_settings.0.hand_off_time.0.minute_of_hour", "30"), ), }, { @@ -316,9 +307,11 @@ func TestAccSSMContactsRotation_recurrence(t *testing.T) { testAccCheckRotationExists(ctx, resourceName), resource.TestCheckResourceAttr(resourceName, "recurrence.0.weekly_settings.#", "2"), resource.TestCheckResourceAttr(resourceName, "recurrence.0.weekly_settings.0.day_of_week", "WED"), - resource.TestCheckResourceAttr(resourceName, "recurrence.0.weekly_settings.0.hand_off_time", "04:25"), + resource.TestCheckResourceAttr(resourceName, "recurrence.0.weekly_settings.0.hand_off_time.0.hour_of_day", "4"), + resource.TestCheckResourceAttr(resourceName, "recurrence.0.weekly_settings.0.hand_off_time.0.minute_of_hour", "25"), resource.TestCheckResourceAttr(resourceName, "recurrence.0.weekly_settings.1.day_of_week", "FRI"), - resource.TestCheckResourceAttr(resourceName, "recurrence.0.weekly_settings.1.hand_off_time", "15:57"), + resource.TestCheckResourceAttr(resourceName, "recurrence.0.weekly_settings.1.hand_off_time.0.hour_of_day", "15"), + resource.TestCheckResourceAttr(resourceName, "recurrence.0.weekly_settings.1.hand_off_time.0.minute_of_hour", "57"), ), }, { @@ -326,9 +319,11 @@ func TestAccSSMContactsRotation_recurrence(t *testing.T) { Check: resource.ComposeTestCheckFunc( testAccCheckRotationExists(ctx, resourceName), resource.TestCheckResourceAttr(resourceName, "recurrence.0.shift_coverages.#", "1"), - resource.TestCheckResourceAttr(resourceName, "recurrence.0.shift_coverages.0.day_of_week", "MON"), - resource.TestCheckResourceAttr(resourceName, "recurrence.0.shift_coverages.0.coverage_times.0.start_time", "08:00"), - resource.TestCheckResourceAttr(resourceName, "recurrence.0.shift_coverages.0.coverage_times.0.end_time", "17:00"), + resource.TestCheckResourceAttr(resourceName, "recurrence.0.shift_coverages.0.map_block_key", "MON"), + resource.TestCheckResourceAttr(resourceName, "recurrence.0.shift_coverages.0.coverage_times.0.start.0.hour_of_day", "8"), + resource.TestCheckResourceAttr(resourceName, "recurrence.0.shift_coverages.0.coverage_times.0.start.0.minute_of_hour", "0"), + resource.TestCheckResourceAttr(resourceName, "recurrence.0.shift_coverages.0.coverage_times.0.end.0.hour_of_day", "17"), + resource.TestCheckResourceAttr(resourceName, "recurrence.0.shift_coverages.0.coverage_times.0.end.0.minute_of_hour", "0"), ), }, { @@ -336,22 +331,28 @@ func TestAccSSMContactsRotation_recurrence(t *testing.T) { Check: resource.ComposeTestCheckFunc( testAccCheckRotationExists(ctx, resourceName), resource.TestCheckResourceAttr(resourceName, "recurrence.0.shift_coverages.#", "3"), - resource.TestCheckResourceAttr(resourceName, "recurrence.0.shift_coverages.0.day_of_week", "FRI"), - resource.TestCheckResourceAttr(resourceName, "recurrence.0.shift_coverages.0.coverage_times.0.start_time", "01:00"), - resource.TestCheckResourceAttr(resourceName, "recurrence.0.shift_coverages.0.coverage_times.0.end_time", "23:00"), - resource.TestCheckResourceAttr(resourceName, "recurrence.0.shift_coverages.1.day_of_week", "MON"), - resource.TestCheckResourceAttr(resourceName, "recurrence.0.shift_coverages.1.coverage_times.0.start_time", "01:00"), - resource.TestCheckResourceAttr(resourceName, "recurrence.0.shift_coverages.1.coverage_times.0.end_time", "23:00"), - resource.TestCheckResourceAttr(resourceName, "recurrence.0.shift_coverages.2.day_of_week", "WED"), - resource.TestCheckResourceAttr(resourceName, "recurrence.0.shift_coverages.2.coverage_times.0.start_time", "01:00"), - resource.TestCheckResourceAttr(resourceName, "recurrence.0.shift_coverages.2.coverage_times.0.end_time", "23:00"), + resource.TestCheckResourceAttr(resourceName, "recurrence.0.shift_coverages.0.map_block_key", "MON"), + resource.TestCheckResourceAttr(resourceName, "recurrence.0.shift_coverages.0.coverage_times.0.start.0.hour_of_day", "1"), + resource.TestCheckResourceAttr(resourceName, "recurrence.0.shift_coverages.0.coverage_times.0.start.0.minute_of_hour", "0"), + resource.TestCheckResourceAttr(resourceName, "recurrence.0.shift_coverages.0.coverage_times.0.end.0.hour_of_day", "23"), + resource.TestCheckResourceAttr(resourceName, "recurrence.0.shift_coverages.0.coverage_times.0.end.0.minute_of_hour", "0"), + resource.TestCheckResourceAttr(resourceName, "recurrence.0.shift_coverages.1.map_block_key", "WED"), + resource.TestCheckResourceAttr(resourceName, "recurrence.0.shift_coverages.1.coverage_times.0.start.0.hour_of_day", "1"), + resource.TestCheckResourceAttr(resourceName, "recurrence.0.shift_coverages.1.coverage_times.0.start.0.minute_of_hour", "0"), + resource.TestCheckResourceAttr(resourceName, "recurrence.0.shift_coverages.1.coverage_times.0.end.0.hour_of_day", "23"), + resource.TestCheckResourceAttr(resourceName, "recurrence.0.shift_coverages.1.coverage_times.0.end.0.minute_of_hour", "0"), + resource.TestCheckResourceAttr(resourceName, "recurrence.0.shift_coverages.2.map_block_key", "FRI"), + resource.TestCheckResourceAttr(resourceName, "recurrence.0.shift_coverages.2.coverage_times.0.start.0.hour_of_day", "1"), + resource.TestCheckResourceAttr(resourceName, "recurrence.0.shift_coverages.2.coverage_times.0.start.0.minute_of_hour", "0"), + resource.TestCheckResourceAttr(resourceName, "recurrence.0.shift_coverages.2.coverage_times.0.end.0.hour_of_day", "23"), + resource.TestCheckResourceAttr(resourceName, "recurrence.0.shift_coverages.2.coverage_times.0.end.0.minute_of_hour", "0"), ), }, }, }) } -func TestAccSSMContactsRotation_tags(t *testing.T) { +func testRotation_tags(t *testing.T) { ctx := acctest.Context(t) if testing.Short() { t.Skip("skipping long-running test in short mode") @@ -476,7 +477,7 @@ func testAccPreCheck(ctx context.Context, t *testing.T) { } } -func testAccRotationConfig_none() string { +func testAccRotationConfig_replicationSetBase() string { return fmt.Sprintf(` resource "aws_ssmincidents_replication_set" "test" { region { @@ -488,11 +489,11 @@ resource "aws_ssmincidents_replication_set" "test" { func testAccRotationConfig_base(alias string, contactCount int) string { return acctest.ConfigCompose( - testAccRotationConfig_none(), + testAccRotationConfig_replicationSetBase(), fmt.Sprintf(` resource "aws_ssmcontacts_contact" "test" { count = %[2]d - alias = %[1]q + alias = "%[1]s-${count.index}" type = "PERSONAL" depends_on = [aws_ssmincidents_replication_set.test] @@ -799,7 +800,7 @@ resource "aws_ssmcontacts_rotation" "test" { } shift_coverages { - day_of_week = "MON" + map_block_key = "MON" coverage_times { start { hour_of_day = 01 @@ -812,7 +813,7 @@ resource "aws_ssmcontacts_rotation" "test" { } } shift_coverages { - day_of_week = "WED" + map_block_key = "WED" coverage_times { start { hour_of_day = 01 @@ -825,7 +826,7 @@ resource "aws_ssmcontacts_rotation" "test" { } } shift_coverages { - day_of_week = "FRI" + map_block_key = "FRI" coverage_times { start { hour_of_day = 01 diff --git a/internal/service/ssmcontacts/ssmcontacts_test.go b/internal/service/ssmcontacts/ssmcontacts_test.go index dc7b39b7969b..2bbf09e3e7b9 100644 --- a/internal/service/ssmcontacts/ssmcontacts_test.go +++ b/internal/service/ssmcontacts/ssmcontacts_test.go @@ -51,13 +51,13 @@ func TestAccSSMContacts_serial(t *testing.T) { "basic": testPlanDataSource_basic, "channelTargetInfo": testPlanDataSource_channelTargetInfo, }, - "Rotation Resource Tests": { - "basic": TestAccSSMContactsRotation_basic, - "disappears": TestAccSSMContactsRotation_disappears, - "update": TestAccSSMContactsRotation_updateRequiredFields, - "startTime": TestAccSSMContactsRotation_startTime, - "contactIds": TestAccSSMContactsRotation_contactIds, - "recurrence": TestAccSSMContactsRotation_recurrence, + "RotationResource": { + "basic": testRotation_basic, + "disappears": testRotation_disappears, + "update": testRotation_updateRequiredFields, + "startTime": testRotation_startTime, + "contactIds": testRotation_contactIds, + "recurrence": testRotation_recurrence, }, "Rotation Data Source Tests": { "basic": TestAccSSMContactsRotationDataSource_basic, diff --git a/internal/service/ssmincidents/sweep.go b/internal/service/ssmincidents/sweep.go new file mode 100644 index 000000000000..3627b4e2f3f7 --- /dev/null +++ b/internal/service/ssmincidents/sweep.go @@ -0,0 +1,67 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package ssmincidents + +import ( + "fmt" + "log" + + "github.com/aws/aws-sdk-go-v2/service/ssmincidents" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-provider-aws/internal/sweep" + "github.com/hashicorp/terraform-provider-aws/internal/sweep/awsv2" + "github.com/hashicorp/terraform-provider-aws/names" +) + +func RegisterSweepers() { + resource.AddTestSweepers("aws_ssmincidents_replication_set", &resource.Sweeper{ + Name: "aws_ssmincidents_replication_set", + F: sweepReplicationSets, + }) +} + +func sweepReplicationSets(region string) error { + ctx := sweep.Context(region) + if region == names.USWest1RegionID { + log.Printf("[WARN] Skipping SSMIncidents Replication Sets sweep for region: %s", region) + return nil + } + client, err := sweep.SharedRegionalSweepClient(ctx, region) + if err != nil { + return fmt.Errorf("error getting client: %w", err) + } + conn := client.SSMIncidentsClient(ctx) + input := &ssmincidents.ListReplicationSetsInput{} + sweepResources := make([]sweep.Sweepable, 0) + + pages := ssmincidents.NewListReplicationSetsPaginator(conn, input) + + for pages.HasMorePages() { + page, err := pages.NextPage(ctx) + if awsv2.SkipSweepError(err) { + log.Printf("[WARN] Skipping SSMIncidents Replication Sets sweep for %s: %s", region, err) + return nil + } + if err != nil { + return fmt.Errorf("error retrieving SSMIncidents Replication Sets: %w", err) + } + + for _, rs := range page.ReplicationSetArns { + id := rs + + r := ResourceReplicationSet() + d := r.Data(nil) + d.SetId(id) + + log.Printf("[INFO] Deleting SSMIncidents Replication Set: %s", id) + sweepResources = append(sweepResources, sweep.NewSweepResource(r, d, client)) + } + } + + if err := sweep.SweepOrchestrator(ctx, sweepResources); err != nil { + return fmt.Errorf("error sweeping SSMIncidents Replication Sets for %s: %w", region, err) + } + + return nil +} diff --git a/internal/sweep/register_gen_test.go b/internal/sweep/register_gen_test.go index fe66ea29f691..1e0a409a8b8e 100644 --- a/internal/sweep/register_gen_test.go +++ b/internal/sweep/register_gen_test.go @@ -138,6 +138,7 @@ import ( "github.com/hashicorp/terraform-provider-aws/internal/service/sqs" "github.com/hashicorp/terraform-provider-aws/internal/service/ssm" "github.com/hashicorp/terraform-provider-aws/internal/service/ssmcontacts" + "github.com/hashicorp/terraform-provider-aws/internal/service/ssmincidents" "github.com/hashicorp/terraform-provider-aws/internal/service/ssoadmin" "github.com/hashicorp/terraform-provider-aws/internal/service/storagegateway" "github.com/hashicorp/terraform-provider-aws/internal/service/swf" @@ -288,6 +289,7 @@ func registerSweepers() { sqs.RegisterSweepers() ssm.RegisterSweepers() ssmcontacts.RegisterSweepers() + ssmincidents.RegisterSweepers() ssoadmin.RegisterSweepers() storagegateway.RegisterSweepers() swf.RegisterSweepers() From 955a45a54bf899b6cfb1c2b0fce74b433c3d177d Mon Sep 17 00:00:00 2001 From: Adrian Johnson Date: Wed, 31 Jan 2024 13:24:56 -0600 Subject: [PATCH 13/22] aws_ssmcontacts_rotation: datasource to framework --- internal/service/ssmcontacts/flex.go | 193 +++++----- internal/service/ssmcontacts/rotation.go | 4 +- .../ssmcontacts/rotation_data_source.go | 344 +++++++++--------- .../ssmcontacts/rotation_data_source_fw.go | 211 +++++++++++ .../ssmcontacts/rotation_data_source_test.go | 7 +- .../ssmcontacts/service_package_gen.go | 11 +- 6 files changed, 490 insertions(+), 280 deletions(-) create mode 100644 internal/service/ssmcontacts/rotation_data_source_fw.go diff --git a/internal/service/ssmcontacts/flex.go b/internal/service/ssmcontacts/flex.go index 13f43aba7d11..bc9036cdc987 100644 --- a/internal/service/ssmcontacts/flex.go +++ b/internal/service/ssmcontacts/flex.go @@ -4,7 +4,6 @@ package ssmcontacts import ( - "context" "fmt" "github.com/aws/aws-sdk-go-v2/aws" @@ -246,39 +245,39 @@ func flattenHandOffTime(handOffTime *types.HandOffTime) string { // // // // return c // //} -func flattenRecurrence(recurrence *types.RecurrenceSettings, ctx context.Context) []interface{} { - var result []interface{} - - c := make(map[string]interface{}) - - if v := recurrence.DailySettings; v != nil { - c["daily_settings"] = flattenDailySettings(v) - } - - if v := recurrence.MonthlySettings; v != nil { - c["monthly_settings"] = flattenMonthlySettings(v) - } - - if v := recurrence.NumberOfOnCalls; v != nil { - c["number_of_on_calls"] = aws.ToInt32(v) - } - - if v := recurrence.RecurrenceMultiplier; v != nil { - c["recurrence_multiplier"] = aws.ToInt32(v) - } - - if v := recurrence.ShiftCoverages; v != nil { - c["shift_coverages"] = flattenShiftCoverages(v) - } - - if v := recurrence.WeeklySettings; v != nil { - c["weekly_settings"] = flattenWeeklySettings(v) - } - - result = append(result, c) - - return result -} +//func flattenRecurrence(recurrence *types.RecurrenceSettings, ctx context.Context) []interface{} { +// var result []interface{} +// +// c := make(map[string]interface{}) +// +// if v := recurrence.DailySettings; v != nil { +// c["daily_settings"] = flattenDailySettings(v) +// } +// +// if v := recurrence.MonthlySettings; v != nil { +// c["monthly_settings"] = flattenMonthlySettings(v) +// } +// +// if v := recurrence.NumberOfOnCalls; v != nil { +// c["number_of_on_calls"] = aws.ToInt32(v) +// } +// +// if v := recurrence.RecurrenceMultiplier; v != nil { +// c["recurrence_multiplier"] = aws.ToInt32(v) +// } +// +// if v := recurrence.ShiftCoverages; v != nil { +// c["shift_coverages"] = flattenShiftCoverages(v) +// } +// +// if v := recurrence.WeeklySettings; v != nil { +// c["weekly_settings"] = flattenWeeklySettings(v) +// } +// +// result = append(result, c) +// +// return result +//} // // // //func expandDailySettings(dailySettings []interface{}) []types.HandOffTime { @@ -371,27 +370,27 @@ func flattenMonthlySettings(monthlySettings []types.MonthlySetting) []interface{ // // // // return result // //} -func flattenShiftCoverages(shiftCoverages map[string][]types.CoverageTime) []interface{} { - if len(shiftCoverages) == 0 { - return nil - } - - var result []interface{} - - for coverageDay, coverageTime := range shiftCoverages { - c := make(map[string]interface{}) - - c["coverage_times"] = flattenCoverageTimes(coverageTime) - c["day_of_week"] = coverageDay - - result = append(result, c) - } - - // API doesn't return in any consistent order. This causes flakes during testing, so we sort to always return a consistent order - sortShiftCoverages(result) - - return result -} +//func flattenShiftCoverages(shiftCoverages map[string][]types.CoverageTime) []interface{} { +// if len(shiftCoverages) == 0 { +// return nil +// } +// +// var result []interface{} +// +// for coverageDay, coverageTime := range shiftCoverages { +// c := make(map[string]interface{}) +// +// c["coverage_times"] = flattenCoverageTimes(coverageTime) +// c["day_of_week"] = coverageDay +// +// result = append(result, c) +// } +// +// // API doesn't return in any consistent order. This causes flakes during testing, so we sort to always return a consistent order +// sortShiftCoverages(result) +// +// return result +//} // func expandCoverageTimes(coverageTimes []interface{}) []types.CoverageTime { // var result []types.CoverageTime @@ -414,25 +413,25 @@ func flattenShiftCoverages(shiftCoverages map[string][]types.CoverageTime) []int // // return result // } -func flattenCoverageTimes(coverageTimes []types.CoverageTime) []interface{} { - var result []interface{} - - c := make(map[string]interface{}) - - for _, coverageTime := range coverageTimes { - if v := coverageTime.End; v != nil { - c["end_time"] = flattenHandOffTime(v) - } - - if v := coverageTime.Start; v != nil { - c["start_time"] = flattenHandOffTime(v) - } - - result = append(result, c) - } - - return result -} +//func flattenCoverageTimes(coverageTimes []types.CoverageTime) []interface{} { +// var result []interface{} +// +// c := make(map[string]interface{}) +// +// for _, coverageTime := range coverageTimes { +// if v := coverageTime.End; v != nil { +// c["end_time"] = flattenHandOffTime(v) +// } +// +// if v := coverageTime.Start; v != nil { +// c["start_time"] = flattenHandOffTime(v) +// } +// +// result = append(result, c) +// } +// +// return result +//} // func expandWeeklySettings(weeklySettings []interface{}) []types.WeeklySetting { // var result []types.WeeklySetting @@ -451,26 +450,26 @@ func flattenCoverageTimes(coverageTimes []types.CoverageTime) []interface{} { // // return result // } -func flattenWeeklySettings(weeklySettings []types.WeeklySetting) []interface{} { - if len(weeklySettings) == 0 { - return nil - } - - var result []interface{} - - for _, weeklySetting := range weeklySettings { - c := make(map[string]interface{}) - - if v := string(weeklySetting.DayOfWeek); v != "" { - c["day_of_week"] = v - } - - if v := weeklySetting.HandOffTime; v != nil { - c["hand_off_time"] = flattenHandOffTime(v) - } - - result = append(result, c) - } - - return result -} +//func flattenWeeklySettings(weeklySettings []types.WeeklySetting) []interface{} { +// if len(weeklySettings) == 0 { +// return nil +// } +// +// var result []interface{} +// +// for _, weeklySetting := range weeklySettings { +// c := make(map[string]interface{}) +// +// if v := string(weeklySetting.DayOfWeek); v != "" { +// c["day_of_week"] = v +// } +// +// if v := weeklySetting.HandOffTime; v != nil { +// c["hand_off_time"] = flattenHandOffTime(v) +// } +// +// result = append(result, c) +// } +// +// return result +//} diff --git a/internal/service/ssmcontacts/rotation.go b/internal/service/ssmcontacts/rotation.go index 30455f18ff91..876014ee6848 100644 --- a/internal/service/ssmcontacts/rotation.go +++ b/internal/service/ssmcontacts/rotation.go @@ -319,7 +319,7 @@ func (r *resourceRotation) Read(ctx context.Context, request resource.ReadReques return } - rc.ShiftCoverages = flattenShiftCoveragesFW(ctx, output.Recurrence.ShiftCoverages) + rc.ShiftCoverages = flattenShiftCoverages(ctx, output.Recurrence.ShiftCoverages) state.ARN = flex.StringToFramework(ctx, output.RotationArn) state.Name = flex.StringToFramework(ctx, output.Name) @@ -540,7 +540,7 @@ func expandShiftCoverages(ctx context.Context, object []*shiftCoveragesData, dia return result } -func flattenShiftCoveragesFW(ctx context.Context, object map[string][]awstypes.CoverageTime) fwtypes.ListNestedObjectValueOf[shiftCoveragesData] { +func flattenShiftCoverages(ctx context.Context, object map[string][]awstypes.CoverageTime) fwtypes.ListNestedObjectValueOf[shiftCoveragesData] { if len(object) == 0 { return fwtypes.NewListNestedObjectValueOfNull[shiftCoveragesData](ctx) } diff --git a/internal/service/ssmcontacts/rotation_data_source.go b/internal/service/ssmcontacts/rotation_data_source.go index 6733f41cfbbe..d8928504181f 100644 --- a/internal/service/ssmcontacts/rotation_data_source.go +++ b/internal/service/ssmcontacts/rotation_data_source.go @@ -3,175 +3,175 @@ package ssmcontacts -import ( - "context" - "time" - - "github.com/aws/aws-sdk-go-v2/aws" - "github.com/hashicorp/terraform-plugin-sdk/v2/diag" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/hashicorp/terraform-provider-aws/internal/conns" - "github.com/hashicorp/terraform-provider-aws/internal/create" - tftags "github.com/hashicorp/terraform-provider-aws/internal/tags" - "github.com/hashicorp/terraform-provider-aws/names" -) - -// @SDKDataSource("aws_ssmcontacts_rotation") -func DataSourceRotation() *schema.Resource { - return &schema.Resource{ - ReadWithoutTimeout: dataSourceRotationRead, - - Schema: map[string]*schema.Schema{ - "arn": { - Type: schema.TypeString, - Required: true, - }, - "contact_ids": { - Type: schema.TypeList, - Computed: true, - Elem: &schema.Schema{ - Type: schema.TypeString, - }, - }, - "name": { - Type: schema.TypeString, - Computed: true, - }, - "recurrence": { - Type: schema.TypeList, - Computed: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "daily_settings": { - Type: schema.TypeList, - Computed: true, - Elem: &schema.Schema{ - Type: schema.TypeString, - }, - }, - "monthly_settings": { - Type: schema.TypeList, - Computed: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "day_of_month": { - Type: schema.TypeInt, - Computed: true, - }, - "hand_off_time": { - Type: schema.TypeString, - Computed: true, - }, - }, - }, - }, - "number_of_on_calls": { - Type: schema.TypeInt, - Computed: true, - }, - "recurrence_multiplier": { - Type: schema.TypeInt, - Computed: true, - }, - "shift_coverages": { - Type: schema.TypeList, - Computed: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "coverage_times": { - Type: schema.TypeList, - Computed: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "end_time": { - Type: schema.TypeString, - Computed: true, - }, - "start_time": { - Type: schema.TypeString, - Computed: true, - }, - }, - }, - }, - "day_of_week": { - Type: schema.TypeString, - Computed: true, - }, - }, - }, - }, - "weekly_settings": { - Type: schema.TypeList, - Computed: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "day_of_week": { - Type: schema.TypeString, - Computed: true, - }, - "hand_off_time": { - Type: schema.TypeString, - Computed: true, - }, - }, - }, - }, - }, - }, - }, - "start_time": { - Type: schema.TypeString, - Computed: true, - }, - "time_zone_id": { - Type: schema.TypeString, - Computed: true, - }, - names.AttrTags: tftags.TagsSchemaComputed(), - }, - } -} - -const ( - DSNameRotation = "Rotation Data Source" -) - -func dataSourceRotationRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - conn := meta.(*conns.AWSClient).SSMContactsClient(ctx) - arn := d.Get("arn").(string) - - out, err := findRotationByID(ctx, conn, arn) - if err != nil { - return create.DiagError(names.SSMContacts, create.ErrActionReading, DSNameRotation, arn, err) - } - - d.SetId(aws.ToString(out.RotationArn)) - - d.Set("arn", out.RotationArn) - d.Set("contact_ids", out.ContactIds) - d.Set("name", out.Name) - d.Set("time_zone_id", out.TimeZoneId) - - if out.StartTime != nil { - d.Set("start_time", out.StartTime.Format(time.RFC3339)) - } - - if err := d.Set("recurrence", flattenRecurrence(out.Recurrence, ctx)); err != nil { - return create.DiagError(names.SSMContacts, create.ErrActionSetting, DSNameRotation, d.Id(), err) - } - - tags, err := listTags(ctx, conn, d.Id()) - if err != nil { - return create.DiagError(names.SSMIncidents, create.ErrActionReading, DSNameRotation, d.Id(), err) - } - - ignoreTagsConfig := meta.(*conns.AWSClient).IgnoreTagsConfig - - //lintignore:AWSR002 - if err := d.Set("tags", tags.IgnoreAWS().IgnoreConfig(ignoreTagsConfig).Map()); err != nil { - return create.DiagError(names.SSMContacts, create.ErrActionSetting, DSNameRotation, d.Id(), err) - } - - return nil -} +//import ( +// "context" +// "time" +// +// "github.com/aws/aws-sdk-go-v2/aws" +// "github.com/hashicorp/terraform-plugin-sdk/v2/diag" +// "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +// "github.com/hashicorp/terraform-provider-aws/internal/conns" +// "github.com/hashicorp/terraform-provider-aws/internal/create" +// tftags "github.com/hashicorp/terraform-provider-aws/internal/tags" +// "github.com/hashicorp/terraform-provider-aws/names" +//) +// +//// @SDKDataSource("aws_ssmcontacts_rotation") +//func DataSourceRotation() *schema.Resource { +// return &schema.Resource{ +// ReadWithoutTimeout: dataSourceRotationRead, +// +// Schema: map[string]*schema.Schema{ +// "arn": { +// Type: schema.TypeString, +// Required: true, +// }, +// "contact_ids": { +// Type: schema.TypeList, +// Computed: true, +// Elem: &schema.Schema{ +// Type: schema.TypeString, +// }, +// }, +// "name": { +// Type: schema.TypeString, +// Computed: true, +// }, +// "recurrence": { +// Type: schema.TypeList, +// Computed: true, +// Elem: &schema.Resource{ +// Schema: map[string]*schema.Schema{ +// "daily_settings": { +// Type: schema.TypeList, +// Computed: true, +// Elem: &schema.Schema{ +// Type: schema.TypeString, +// }, +// }, +// "monthly_settings": { +// Type: schema.TypeList, +// Computed: true, +// Elem: &schema.Resource{ +// Schema: map[string]*schema.Schema{ +// "day_of_month": { +// Type: schema.TypeInt, +// Computed: true, +// }, +// "hand_off_time": { +// Type: schema.TypeString, +// Computed: true, +// }, +// }, +// }, +// }, +// "number_of_on_calls": { +// Type: schema.TypeInt, +// Computed: true, +// }, +// "recurrence_multiplier": { +// Type: schema.TypeInt, +// Computed: true, +// }, +// "shift_coverages": { +// Type: schema.TypeList, +// Computed: true, +// Elem: &schema.Resource{ +// Schema: map[string]*schema.Schema{ +// "coverage_times": { +// Type: schema.TypeList, +// Computed: true, +// Elem: &schema.Resource{ +// Schema: map[string]*schema.Schema{ +// "end_time": { +// Type: schema.TypeString, +// Computed: true, +// }, +// "start_time": { +// Type: schema.TypeString, +// Computed: true, +// }, +// }, +// }, +// }, +// "day_of_week": { +// Type: schema.TypeString, +// Computed: true, +// }, +// }, +// }, +// }, +// "weekly_settings": { +// Type: schema.TypeList, +// Computed: true, +// Elem: &schema.Resource{ +// Schema: map[string]*schema.Schema{ +// "day_of_week": { +// Type: schema.TypeString, +// Computed: true, +// }, +// "hand_off_time": { +// Type: schema.TypeString, +// Computed: true, +// }, +// }, +// }, +// }, +// }, +// }, +// }, +// "start_time": { +// Type: schema.TypeString, +// Computed: true, +// }, +// "time_zone_id": { +// Type: schema.TypeString, +// Computed: true, +// }, +// names.AttrTags: tftags.TagsSchemaComputed(), +// }, +// } +//} +// +//const ( +// DSNameRotation = "Rotation Data Source" +//) +// +//func dataSourceRotationRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { +// conn := meta.(*conns.AWSClient).SSMContactsClient(ctx) +// arn := d.Get("arn").(string) +// +// out, err := findRotationByID(ctx, conn, arn) +// if err != nil { +// return create.DiagError(names.SSMContacts, create.ErrActionReading, DSNameRotation, arn, err) +// } +// +// d.SetId(aws.ToString(out.RotationArn)) +// +// d.Set("arn", out.RotationArn) +// d.Set("contact_ids", out.ContactIds) +// d.Set("name", out.Name) +// d.Set("time_zone_id", out.TimeZoneId) +// +// if out.StartTime != nil { +// d.Set("start_time", out.StartTime.Format(time.RFC3339)) +// } +// +// if err := d.Set("recurrence", flattenRecurrence(out.Recurrence, ctx)); err != nil { +// return create.DiagError(names.SSMContacts, create.ErrActionSetting, DSNameRotation, d.Id(), err) +// } +// +// tags, err := listTags(ctx, conn, d.Id()) +// if err != nil { +// return create.DiagError(names.SSMIncidents, create.ErrActionReading, DSNameRotation, d.Id(), err) +// } +// +// ignoreTagsConfig := meta.(*conns.AWSClient).IgnoreTagsConfig +// +// //lintignore:AWSR002 +// if err := d.Set("tags", tags.IgnoreAWS().IgnoreConfig(ignoreTagsConfig).Map()); err != nil { +// return create.DiagError(names.SSMContacts, create.ErrActionSetting, DSNameRotation, d.Id(), err) +// } +// +// return nil +//} diff --git a/internal/service/ssmcontacts/rotation_data_source_fw.go b/internal/service/ssmcontacts/rotation_data_source_fw.go new file mode 100644 index 000000000000..3f857ac42022 --- /dev/null +++ b/internal/service/ssmcontacts/rotation_data_source_fw.go @@ -0,0 +1,211 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package ssmcontacts + +import ( + "context" + "time" + + awstypes "github.com/aws/aws-sdk-go-v2/service/ssmcontacts/types" + "github.com/hashicorp/terraform-plugin-framework/datasource" + "github.com/hashicorp/terraform-plugin-framework/datasource/schema" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-provider-aws/internal/create" + "github.com/hashicorp/terraform-provider-aws/internal/framework" + "github.com/hashicorp/terraform-provider-aws/internal/framework/flex" + fwtypes "github.com/hashicorp/terraform-provider-aws/internal/framework/types" + "github.com/hashicorp/terraform-provider-aws/names" +) + +// @FrameworkDataSource(name="Rotation") +func newDataSourceRotation(context.Context) (datasource.DataSourceWithConfigure, error) { + d := &dataSourceRotation{} + + return d, nil +} + +type dataSourceRotation struct { + framework.DataSourceWithConfigure +} + +func (d *dataSourceRotation) Metadata(_ context.Context, request datasource.MetadataRequest, response *datasource.MetadataResponse) { + response.TypeName = "aws_ssmcontacts_rotation" +} + +// Schema returns the schema for this data source. +func (d *dataSourceRotation) Schema(ctx context.Context, request datasource.SchemaRequest, response *datasource.SchemaResponse) { + response.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "arn": schema.StringAttribute{ + CustomType: fwtypes.ARNType, + Required: true, + }, + "contact_ids": schema.ListAttribute{ + CustomType: fwtypes.ListOfStringType, + ElementType: types.StringType, + Computed: true, + }, + "id": framework.IDAttribute(), + "name": schema.StringAttribute{ + Computed: true, + }, + "recurrence": schema.ListAttribute{ + CustomType: fwtypes.NewListNestedObjectTypeOf[dsRecurrenceData](ctx), + ElementType: fwtypes.NewObjectTypeOf[dsRecurrenceData](ctx), + Computed: true, + }, + "start_time": schema.StringAttribute{ + CustomType: fwtypes.TimestampType, + Computed: true, + }, + "tags": // TODO tftags.TagsAttribute() + schema.MapAttribute{ + ElementType: types.StringType, + Optional: true, + Computed: true, + }, + "time_zone_id": schema.StringAttribute{ + Computed: true, + }, + }, + } +} + +// Read is called when the provider must read data source values in order to update state. +// Config values should be read from the ReadRequest and new state values set on the ReadResponse. +func (d *dataSourceRotation) Read(ctx context.Context, request datasource.ReadRequest, response *datasource.ReadResponse) { + conn := d.Meta().SSMContactsClient(ctx) + var data dataSourceRotationData + + response.Diagnostics.Append(request.Config.Get(ctx, &data)...) + + if response.Diagnostics.HasError() { + return + } + + output, err := findRotationByID(ctx, conn, data.ARN.ValueString()) + + if err != nil { + response.Diagnostics.AddError( + create.ProblemStandardMessage(names.SSMContacts, create.ErrActionSetting, ResNameRotation, data.ARN.ValueString(), err), + err.Error(), + ) + return + } + + rc := &dsRecurrenceData{} + rc.RecurrenceMultiplier = flex.Int32ToFramework(ctx, output.Recurrence.RecurrenceMultiplier) + rc.NumberOfOnCalls = flex.Int32ToFramework(ctx, output.Recurrence.NumberOfOnCalls) + + response.Diagnostics.Append(flex.Flatten(ctx, output.Recurrence.DailySettings, &rc.DailySettings)...) + if response.Diagnostics.HasError() { + return + } + + response.Diagnostics.Append(flex.Flatten(ctx, output.Recurrence.MonthlySettings, &rc.MonthlySettings)...) + if response.Diagnostics.HasError() { + return + } + + response.Diagnostics.Append(flex.Flatten(ctx, output.Recurrence.WeeklySettings, &rc.WeeklySettings)...) + if response.Diagnostics.HasError() { + return + } + + response.Diagnostics.Append(flex.Flatten(ctx, output.ContactIds, &data.ContactIds)...) + if response.Diagnostics.HasError() { + return + } + + rc.ShiftCoverages = flattenShiftCoveragesDataSource(ctx, output.Recurrence.ShiftCoverages) + + data.Name = flex.StringToFramework(ctx, output.Name) + data.Recurrence = fwtypes.NewListNestedObjectValueOfPtr(ctx, rc) + data.TimeZoneID = flex.StringToFramework(ctx, output.TimeZoneId) + data.ID = flex.StringToFramework(ctx, output.RotationArn) + + if output.StartTime != nil { + data.StartTime = fwtypes.TimestampValue(output.StartTime.Format(time.RFC3339)) + } + + response.Diagnostics.Append(response.State.Set(ctx, &data)...) +} + +type dataSourceRotationData struct { + ARN fwtypes.ARN `tfsdk:"arn"` + ContactIds fwtypes.ListValueOf[types.String] `tfsdk:"contact_ids"` + ID types.String `tfsdk:"id"` + Recurrence fwtypes.ListNestedObjectValueOf[dsRecurrenceData] `tfsdk:"recurrence"` + Name types.String `tfsdk:"name"` + StartTime fwtypes.Timestamp `tfsdk:"start_time"` + Tags types.Map `tfsdk:"tags"` + TagsAll types.Map `tfsdk:"tags_all"` + TimeZoneID types.String `tfsdk:"time_zone_id"` +} + +type dsRecurrenceData struct { + DailySettings fwtypes.ListNestedObjectValueOf[dsHandOffTime] `tfsdk:"daily_settings"` + MonthlySettings fwtypes.ListNestedObjectValueOf[dsMonthlySettingsData] `tfsdk:"monthly_settings"` + NumberOfOnCalls types.Int64 `tfsdk:"number_of_on_calls"` + RecurrenceMultiplier types.Int64 `tfsdk:"recurrence_multiplier"` + ShiftCoverages fwtypes.ListNestedObjectValueOf[dsShiftCoveragesData] `tfsdk:"shift_coverages"` + WeeklySettings fwtypes.ListNestedObjectValueOf[dsWeeklySettingsData] `tfsdk:"weekly_settings"` +} +type dsMonthlySettingsData struct { + DayOfMonth types.Int64 `tfsdk:"day_of_month"` + HandOffTime fwtypes.ListNestedObjectValueOf[dsHandOffTime] `tfsdk:"hand_off_time"` +} + +type dsShiftCoveragesData struct { + CoverageTimes fwtypes.ListNestedObjectValueOf[dsCoverageTimesData] `tfsdk:"coverage_times"` + MapBlockKey fwtypes.StringEnum[awstypes.DayOfWeek] `tfsdk:"map_block_key"` +} + +type dsCoverageTimesData struct { + End fwtypes.ListNestedObjectValueOf[dsHandOffTime] `tfsdk:"end"` + Start fwtypes.ListNestedObjectValueOf[dsHandOffTime] `tfsdk:"start"` +} + +type dsHandOffTime struct { + HourOfDay types.Int64 `tfsdk:"hour_of_day"` + MinuteOfHour types.Int64 `tfsdk:"minute_of_hour"` +} + +type dsWeeklySettingsData struct { + DayOfWeek fwtypes.StringEnum[awstypes.DayOfWeek] `tfsdk:"day_of_week"` + HandOffTime fwtypes.ListNestedObjectValueOf[dsHandOffTime] `tfsdk:"hand_off_time"` +} + +func flattenShiftCoveragesDataSource(ctx context.Context, object map[string][]awstypes.CoverageTime) fwtypes.ListNestedObjectValueOf[dsShiftCoveragesData] { + if len(object) == 0 { + return fwtypes.NewListNestedObjectValueOfNull[dsShiftCoveragesData](ctx) + } + + var output []dsShiftCoveragesData + for key, value := range object { + sc := dsShiftCoveragesData{ + MapBlockKey: fwtypes.StringEnumValue[awstypes.DayOfWeek](awstypes.DayOfWeek(key)), + } + + var coverageTimes []dsCoverageTimesData + for _, v := range value { + ct := dsCoverageTimesData{ + End: fwtypes.NewListNestedObjectValueOfPtr(ctx, &dsHandOffTime{ + HourOfDay: flex.Int32ValueToFramework(ctx, v.End.HourOfDay), + MinuteOfHour: flex.Int32ValueToFramework(ctx, v.End.MinuteOfHour), + }), + Start: fwtypes.NewListNestedObjectValueOfPtr(ctx, &dsHandOffTime{ + HourOfDay: flex.Int32ValueToFramework(ctx, v.Start.HourOfDay), + MinuteOfHour: flex.Int32ValueToFramework(ctx, v.End.MinuteOfHour), + }), + } + coverageTimes = append(coverageTimes, ct) + } + sc.CoverageTimes = fwtypes.NewListNestedObjectValueOfValueSlice(ctx, coverageTimes) + + output = append(output, sc) + } + + return fwtypes.NewListNestedObjectValueOfValueSlice[dsShiftCoveragesData](ctx, output) +} diff --git a/internal/service/ssmcontacts/rotation_data_source_test.go b/internal/service/ssmcontacts/rotation_data_source_test.go index a886088c286b..41cb1605fa6a 100644 --- a/internal/service/ssmcontacts/rotation_data_source_test.go +++ b/internal/service/ssmcontacts/rotation_data_source_test.go @@ -5,13 +5,13 @@ package ssmcontacts_test import ( "fmt" + "testing" + "time" + sdkacctest "github.com/hashicorp/terraform-plugin-testing/helper/acctest" "github.com/hashicorp/terraform-plugin-testing/helper/resource" "github.com/hashicorp/terraform-provider-aws/internal/acctest" "github.com/hashicorp/terraform-provider-aws/names" - "regexp" - "testing" - "time" ) func TestAccSSMContactsRotationDataSource_basic(t *testing.T) { @@ -67,7 +67,6 @@ func TestAccSSMContactsRotationDataSource_basic(t *testing.T) { resource.TestCheckResourceAttrPair(resourceName, "tags.%", dataSourceName, "tags.%"), resource.TestCheckResourceAttrPair(resourceName, "tags.key1", dataSourceName, "tags.key1"), resource.TestCheckResourceAttrPair(resourceName, "tags.key2", dataSourceName, "tags.key2"), - acctest.MatchResourceAttrRegionalARN(resourceName, "arn", "ssm-contacts", regexp.MustCompile(`rotation\/+.`)), ), }, }, diff --git a/internal/service/ssmcontacts/service_package_gen.go b/internal/service/ssmcontacts/service_package_gen.go index f0e27e58ec1a..6512b73ed0d4 100644 --- a/internal/service/ssmcontacts/service_package_gen.go +++ b/internal/service/ssmcontacts/service_package_gen.go @@ -15,7 +15,12 @@ import ( type servicePackage struct{} func (p *servicePackage) FrameworkDataSources(ctx context.Context) []*types.ServicePackageFrameworkDataSource { - return []*types.ServicePackageFrameworkDataSource{} + return []*types.ServicePackageFrameworkDataSource{ + { + Factory: newDataSourceRotation, + Name: "Rotation", + }, + } } func (p *servicePackage) FrameworkResources(ctx context.Context) []*types.ServicePackageFrameworkResource { @@ -44,10 +49,6 @@ func (p *servicePackage) SDKDataSources(ctx context.Context) []*types.ServicePac Factory: DataSourcePlan, TypeName: "aws_ssmcontacts_plan", }, - { - Factory: DataSourceRotation, - TypeName: "aws_ssmcontacts_rotation", - }, } } From 49157c9ed500c44663b854d0a9b37b4265a4af0e Mon Sep 17 00:00:00 2001 From: Adrian Johnson Date: Wed, 31 Jan 2024 15:17:12 -0600 Subject: [PATCH 14/22] aws_ssmcontacts_rotation: tweak datasource tests --- .../ssmcontacts/rotation_data_source_fw.go | 21 ++- .../ssmcontacts/rotation_data_source_test.go | 147 ++++++++++-------- .../service/ssmcontacts/ssmcontacts_test.go | 8 +- 3 files changed, 100 insertions(+), 76 deletions(-) diff --git a/internal/service/ssmcontacts/rotation_data_source_fw.go b/internal/service/ssmcontacts/rotation_data_source_fw.go index 3f857ac42022..c794e69450c4 100644 --- a/internal/service/ssmcontacts/rotation_data_source_fw.go +++ b/internal/service/ssmcontacts/rotation_data_source_fw.go @@ -15,6 +15,7 @@ import ( "github.com/hashicorp/terraform-provider-aws/internal/framework" "github.com/hashicorp/terraform-provider-aws/internal/framework/flex" fwtypes "github.com/hashicorp/terraform-provider-aws/internal/framework/types" + tftags "github.com/hashicorp/terraform-provider-aws/internal/tags" "github.com/hashicorp/terraform-provider-aws/names" ) @@ -59,12 +60,7 @@ func (d *dataSourceRotation) Schema(ctx context.Context, request datasource.Sche CustomType: fwtypes.TimestampType, Computed: true, }, - "tags": // TODO tftags.TagsAttribute() - schema.MapAttribute{ - ElementType: types.StringType, - Optional: true, - Computed: true, - }, + "tags": tftags.TagsAttributeComputedOnly(), "time_zone_id": schema.StringAttribute{ Computed: true, }, @@ -129,6 +125,18 @@ func (d *dataSourceRotation) Read(ctx context.Context, request datasource.ReadRe data.StartTime = fwtypes.TimestampValue(output.StartTime.Format(time.RFC3339)) } + tags, err := listTags(ctx, conn, data.ARN.ValueString()) + + if err != nil { + response.Diagnostics.AddError( + create.ProblemStandardMessage(names.SSMContacts, create.ErrActionSetting, ResNameRotation, data.ARN.ValueString(), err), + err.Error(), + ) + return + } + + data.Tags = flex.FlattenFrameworkStringValueMap(ctx, tags.Map()) + response.Diagnostics.Append(response.State.Set(ctx, &data)...) } @@ -140,7 +148,6 @@ type dataSourceRotationData struct { Name types.String `tfsdk:"name"` StartTime fwtypes.Timestamp `tfsdk:"start_time"` Tags types.Map `tfsdk:"tags"` - TagsAll types.Map `tfsdk:"tags_all"` TimeZoneID types.String `tfsdk:"time_zone_id"` } diff --git a/internal/service/ssmcontacts/rotation_data_source_test.go b/internal/service/ssmcontacts/rotation_data_source_test.go index 41cb1605fa6a..59539acffa9f 100644 --- a/internal/service/ssmcontacts/rotation_data_source_test.go +++ b/internal/service/ssmcontacts/rotation_data_source_test.go @@ -14,7 +14,7 @@ import ( "github.com/hashicorp/terraform-provider-aws/names" ) -func TestAccSSMContactsRotationDataSource_basic(t *testing.T) { +func testRotationDataSource_basic(t *testing.T) { ctx := acctest.Context(t) if testing.Short() { @@ -43,27 +43,19 @@ func TestAccSSMContactsRotationDataSource_basic(t *testing.T) { resource.TestCheckResourceAttrPair(resourceName, "recurrence.0.recurrence_multiplier", dataSourceName, "recurrence.0.recurrence_multiplier"), resource.TestCheckResourceAttrPair(resourceName, "recurrence.0.weekly_settings.#", dataSourceName, "recurrence.0.weekly_settings.#"), resource.TestCheckResourceAttrPair(resourceName, "recurrence.0.weekly_settings.0.day_of_week", dataSourceName, "recurrence.0.weekly_settings.0.day_of_week"), - resource.TestCheckResourceAttrPair(resourceName, "recurrence.0.weekly_settings.0.hand_off_time", dataSourceName, "recurrence.0.weekly_settings.0.hand_off_time"), + resource.TestCheckResourceAttrPair(resourceName, "recurrence.0.weekly_settings.0.hand_off_time.0.hour_of_day", dataSourceName, "recurrence.0.weekly_settings.0.hand_off_time.0.hour_of_day"), + resource.TestCheckResourceAttrPair(resourceName, "recurrence.0.weekly_settings.0.hand_off_time.0.minute_of_hour", dataSourceName, "recurrence.0.weekly_settings.0.hand_off_time.0.minute_of_hour"), resource.TestCheckResourceAttrPair(resourceName, "recurrence.0.weekly_settings.1.day_of_week", dataSourceName, "recurrence.0.weekly_settings.1.day_of_week"), - resource.TestCheckResourceAttrPair(resourceName, "recurrence.0.weekly_settings.1.hand_off_time", dataSourceName, "recurrence.0.weekly_settings.1.hand_off_time"), + resource.TestCheckResourceAttrPair(resourceName, "recurrence.0.weekly_settings.1.hand_off_time.0.hour_of_day", dataSourceName, "recurrence.0.weekly_settings.1.hand_off_time.0.hour_of_day"), + resource.TestCheckResourceAttrPair(resourceName, "recurrence.0.weekly_settings.1.hand_off_time.0.minute_of_hour", dataSourceName, "recurrence.0.weekly_settings.1.hand_off_time.0.minute_of_hour"), resource.TestCheckResourceAttrPair(resourceName, "recurrence.0.weekly_settings.2.day_of_week", dataSourceName, "recurrence.0.weekly_settings.2.day_of_week"), - resource.TestCheckResourceAttrPair(resourceName, "recurrence.0.weekly_settings.2.hand_off_time", dataSourceName, "recurrence.0.weekly_settings.2.hand_off_time"), + resource.TestCheckResourceAttrPair(resourceName, "recurrence.0.weekly_settings.2.hand_off_time.0.hour_of_day", dataSourceName, "recurrence.0.weekly_settings.2.hand_off_time.0.hour_of_day"), + resource.TestCheckResourceAttrPair(resourceName, "recurrence.0.weekly_settings.2.hand_off_time.0.minute_of_hour", dataSourceName, "recurrence.0.weekly_settings.2.hand_off_time.0.minute_of_hour"), resource.TestCheckResourceAttrPair(resourceName, "recurrence.0.shift_coverages.#", dataSourceName, "recurrence.0.shift_coverages.#"), - resource.TestCheckResourceAttrPair(resourceName, "recurrence.0.shift_coverages.0.day_of_week", dataSourceName, "recurrence.0.shift_coverages.0.day_of_week"), - resource.TestCheckResourceAttrPair(resourceName, "recurrence.0.shift_coverages.0.coverage_times.0.start_time", dataSourceName, "recurrence.0.shift_coverages.0.coverage_times.0.start_time"), - resource.TestCheckResourceAttrPair(resourceName, "recurrence.0.shift_coverages.0.coverage_times.0.end_time", dataSourceName, "recurrence.0.shift_coverages.0.coverage_times.0.end_time"), - resource.TestCheckResourceAttrPair(resourceName, "recurrence.0.shift_coverages.1.day_of_week", dataSourceName, "recurrence.0.shift_coverages.1.day_of_week"), - resource.TestCheckResourceAttrPair(resourceName, "recurrence.0.shift_coverages.1.coverage_times.0.start_time", dataSourceName, "recurrence.0.shift_coverages.1.coverage_times.0.start_time"), - resource.TestCheckResourceAttrPair(resourceName, "recurrence.0.shift_coverages.1.coverage_times.0.end_time", dataSourceName, "recurrence.0.shift_coverages.1.coverage_times.0.end_time"), - resource.TestCheckResourceAttrPair(resourceName, "recurrence.0.shift_coverages.2.day_of_week", dataSourceName, "recurrence.0.shift_coverages.2.day_of_week"), - resource.TestCheckResourceAttrPair(resourceName, "recurrence.0.shift_coverages.2.coverage_times.0.start_time", dataSourceName, "recurrence.0.shift_coverages.2.coverage_times.0.start_time"), - resource.TestCheckResourceAttrPair(resourceName, "recurrence.0.shift_coverages.2.coverage_times.0.end_time", dataSourceName, "recurrence.0.shift_coverages.2.coverage_times.0.end_time"), resource.TestCheckResourceAttrPair(resourceName, "start_time", dataSourceName, "start_time"), resource.TestCheckResourceAttrPair(resourceName, "time_zone_id", dataSourceName, "time_zone_id"), resource.TestCheckResourceAttrPair(resourceName, "contact_ids.#", dataSourceName, "contact_ids.#"), resource.TestCheckResourceAttrPair(resourceName, "contact_ids.0", dataSourceName, "contact_ids.0"), - resource.TestCheckResourceAttrPair(resourceName, "contact_ids.1", dataSourceName, "contact_ids.1"), - resource.TestCheckResourceAttrPair(resourceName, "contact_ids.2", dataSourceName, "contact_ids.2"), resource.TestCheckResourceAttrPair(resourceName, "tags.%", dataSourceName, "tags.%"), resource.TestCheckResourceAttrPair(resourceName, "tags.key1", dataSourceName, "tags.key1"), resource.TestCheckResourceAttrPair(resourceName, "tags.key2", dataSourceName, "tags.key2"), @@ -73,7 +65,7 @@ func TestAccSSMContactsRotationDataSource_basic(t *testing.T) { }) } -func TestAccSSMContactsRotationDataSource_dailySettings(t *testing.T) { +func testRotationDataSource_dailySettings(t *testing.T) { ctx := acctest.Context(t) if testing.Short() { @@ -97,14 +89,15 @@ func TestAccSSMContactsRotationDataSource_dailySettings(t *testing.T) { Config: testRotationDataSourceConfig_dailySettings(rName), Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttrPair(resourceName, "recurrence.0.daily_settings.#", dataSourceName, "recurrence.0.daily_settings.#"), - resource.TestCheckResourceAttrPair(resourceName, "recurrence.0.daily_settings.0", dataSourceName, "recurrence.0.daily_settings.0"), + resource.TestCheckResourceAttrPair(resourceName, "recurrence.0.daily_settings.0.hour_of_day", dataSourceName, "recurrence.0.daily_settings.0.hour_of_day"), + resource.TestCheckResourceAttrPair(resourceName, "recurrence.0.daily_settings.0.minute_of_hour", dataSourceName, "recurrence.0.daily_settings.0.minute_of_hour"), ), }, }, }) } -func TestAccSSMContactsRotationDataSource_monthlySettings(t *testing.T) { +func testRotationDataSource_monthlySettings(t *testing.T) { ctx := acctest.Context(t) if testing.Short() { @@ -129,11 +122,14 @@ func TestAccSSMContactsRotationDataSource_monthlySettings(t *testing.T) { Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttrPair(resourceName, "recurrence.0.monthly_settings.#", dataSourceName, "recurrence.0.monthly_settings.#"), resource.TestCheckResourceAttrPair(resourceName, "recurrence.0.monthly_settings.0.day_of_month", dataSourceName, "recurrence.0.monthly_settings.0.day_of_month"), - resource.TestCheckResourceAttrPair(resourceName, "recurrence.0.monthly_settings.0.hand_off_time", dataSourceName, "recurrence.0.monthly_settings.0.hand_off_time"), + resource.TestCheckResourceAttrPair(resourceName, "recurrence.0.monthly_settings.0.hand_off_time.0.hour_of_day", dataSourceName, "recurrence.0.monthly_settings.0.hand_off_time.0.hour_of_day"), + resource.TestCheckResourceAttrPair(resourceName, "recurrence.0.monthly_settings.0.hand_off_time.0.minute_of_hour", dataSourceName, "recurrence.0.monthly_settings.0.hand_off_time.0.minute_of_hour"), resource.TestCheckResourceAttrPair(resourceName, "recurrence.0.monthly_settings.1.day_of_month", dataSourceName, "recurrence.0.monthly_settings.1.day_of_month"), - resource.TestCheckResourceAttrPair(resourceName, "recurrence.0.monthly_settings.1.hand_off_time", dataSourceName, "recurrence.0.monthly_settings.1.hand_off_time"), + resource.TestCheckResourceAttrPair(resourceName, "recurrence.0.monthly_settings.1.hand_off_time.0.hour_of_day", dataSourceName, "recurrence.0.monthly_settings.1.hand_off_time.0.hour_of_day"), + resource.TestCheckResourceAttrPair(resourceName, "recurrence.0.monthly_settings.1.hand_off_time.0.minute_of_hour", dataSourceName, "recurrence.0.monthly_settings.1.hand_off_time.0.minute_of_hour"), resource.TestCheckResourceAttrPair(resourceName, "recurrence.0.monthly_settings.2.day_of_month", dataSourceName, "recurrence.0.monthly_settings.2.day_of_month"), - resource.TestCheckResourceAttrPair(resourceName, "recurrence.0.monthly_settings.2.hand_off_time", dataSourceName, "recurrence.0.monthly_settings.2.hand_off_time"), + resource.TestCheckResourceAttrPair(resourceName, "recurrence.0.monthly_settings.2.hand_off_time.0.hour_of_day", dataSourceName, "recurrence.0.monthly_settings.2.hand_off_time.0.hour_of_day"), + resource.TestCheckResourceAttrPair(resourceName, "recurrence.0.monthly_settings.2.hand_off_time.0.minute_of_hour", dataSourceName, "recurrence.0.monthly_settings.2.hand_off_time.0.minute_of_hour"), ), }, }, @@ -148,7 +144,7 @@ resource "aws_ssmincidents_replication_set" "test" { } } -resource "aws_ssmcontacts_contact" "test_contact_one" { +resource "aws_ssmcontacts_contact" "test" { alias = "test-contact-one-for-%[2]s" type = "PERSONAL" @@ -161,25 +157,9 @@ func testRotationDataSourceConfig_basic(rName, startTime string) string { return acctest.ConfigCompose( testRotationDataSourceConfig_base(rName), fmt.Sprintf(` -resource "aws_ssmcontacts_contact" "test_contact_two" { - alias = "test-contact-two-for-%[1]s" - type = "PERSONAL" - - depends_on = [aws_ssmincidents_replication_set.test] -} - -resource "aws_ssmcontacts_contact" "test_contact_three" { - alias = "test-contact-three-for-%[1]s" - type = "PERSONAL" - - depends_on = [aws_ssmincidents_replication_set.test] -} - resource "aws_ssmcontacts_rotation" "test" { contact_ids = [ - aws_ssmcontacts_contact.test_contact_one.arn, - aws_ssmcontacts_contact.test_contact_two.arn, - aws_ssmcontacts_contact.test_contact_three.arn + aws_ssmcontacts_contact.test.arn, ] name = %[1]q @@ -188,36 +168,63 @@ resource "aws_ssmcontacts_rotation" "test" { number_of_on_calls = 1 recurrence_multiplier = 1 weekly_settings { - day_of_week = "MON" - hand_off_time = "04:25" + day_of_week = "MON" + hand_off_time { + hour_of_day = 4 + minute_of_hour = 25 + } } weekly_settings { - day_of_week = "WED" - hand_off_time = "07:34" + day_of_week = "WED" + hand_off_time { + hour_of_day = 7 + minute_of_hour = 34 + } } weekly_settings { - day_of_week = "FRI" - hand_off_time = "15:57" + day_of_week = "FRI" + hand_off_time { + hour_of_day = 15 + minute_of_hour = 57 + } } shift_coverages { - day_of_week = "MON" + map_block_key = "MON" coverage_times { - start_time = "01:00" - end_time = "23:00" + start { + hour_of_day = 1 + minute_of_hour = 0 + } + end { + hour_of_day = 23 + minute_of_hour = 0 + } } } shift_coverages { - day_of_week = "WED" + map_block_key = "WED" coverage_times { - start_time = "01:00" - end_time = "23:00" + start { + hour_of_day = 1 + minute_of_hour = 0 + } + end { + hour_of_day = 23 + minute_of_hour = 0 + } } } shift_coverages { - day_of_week = "FRI" + map_block_key = "FRI" coverage_times { - start_time = "01:00" - end_time = "23:00" + start { + hour_of_day = 1 + minute_of_hour = 0 + } + end { + hour_of_day = 23 + minute_of_hour = 0 + } } } } @@ -246,7 +253,7 @@ func testRotationDataSourceConfig_dailySettings(rName string) string { fmt.Sprintf(` resource "aws_ssmcontacts_rotation" "test" { contact_ids = [ - aws_ssmcontacts_contact.test_contact_one.arn, + aws_ssmcontacts_contact.test.arn, ] name = %[1]q @@ -254,9 +261,10 @@ resource "aws_ssmcontacts_rotation" "test" { recurrence { number_of_on_calls = 1 recurrence_multiplier = 1 - daily_settings = [ - "18:00" - ] + daily_settings { + hour_of_day = 18 + minute_of_hour = 0 + } } time_zone_id = "Australia/Sydney" @@ -276,7 +284,7 @@ func testRotationDataSourceConfig_monthlySettings(rName string) string { fmt.Sprintf(` resource "aws_ssmcontacts_rotation" "test" { contact_ids = [ - aws_ssmcontacts_contact.test_contact_one.arn, + aws_ssmcontacts_contact.test.arn, ] name = %[1]q @@ -285,16 +293,25 @@ resource "aws_ssmcontacts_rotation" "test" { number_of_on_calls = 1 recurrence_multiplier = 1 monthly_settings { - day_of_month = 20 - hand_off_time = "08:00" + day_of_month = 20 + hand_off_time { + hour_of_day = 8 + minute_of_hour = 0 + } } monthly_settings { - day_of_month = 13 - hand_off_time = "12:34" + day_of_month = 13 + hand_off_time { + hour_of_day = 12 + minute_of_hour = 34 + } } monthly_settings { - day_of_month = 1 - hand_off_time = "04:58" + day_of_month = 1 + hand_off_time { + hour_of_day = 4 + minute_of_hour = 58 + } } } diff --git a/internal/service/ssmcontacts/ssmcontacts_test.go b/internal/service/ssmcontacts/ssmcontacts_test.go index 2bbf09e3e7b9..9b98e3690df6 100644 --- a/internal/service/ssmcontacts/ssmcontacts_test.go +++ b/internal/service/ssmcontacts/ssmcontacts_test.go @@ -59,10 +59,10 @@ func TestAccSSMContacts_serial(t *testing.T) { "contactIds": testRotation_contactIds, "recurrence": testRotation_recurrence, }, - "Rotation Data Source Tests": { - "basic": TestAccSSMContactsRotationDataSource_basic, - "dailySettings": TestAccSSMContactsRotationDataSource_dailySettings, - "monthlySettings": TestAccSSMContactsRotationDataSource_monthlySettings, + "RotationDataSource": { + "basic": testRotationDataSource_basic, + "dailySettings": testRotationDataSource_dailySettings, + "monthlySettings": testRotationDataSource_monthlySettings, }, } From 432fdd3d0d6fc8cbe9081b211e7408ba26e3caec Mon Sep 17 00:00:00 2001 From: Adrian Johnson Date: Wed, 31 Jan 2024 15:19:26 -0600 Subject: [PATCH 15/22] rename files --- internal/service/ssmcontacts/flex.go | 276 ------------- .../ssmcontacts/rotation_data_source.go | 382 ++++++++++-------- .../ssmcontacts/rotation_data_source_fw.go | 218 ---------- 3 files changed, 210 insertions(+), 666 deletions(-) delete mode 100644 internal/service/ssmcontacts/rotation_data_source_fw.go diff --git a/internal/service/ssmcontacts/flex.go b/internal/service/ssmcontacts/flex.go index bc9036cdc987..ad5bfaec5058 100644 --- a/internal/service/ssmcontacts/flex.go +++ b/internal/service/ssmcontacts/flex.go @@ -4,8 +4,6 @@ package ssmcontacts import ( - "fmt" - "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/service/ssmcontacts/types" ) @@ -199,277 +197,3 @@ func flattenContactTargetInfo(contactTargetInfo *types.ContactTargetInfo) []inte return result } - -// func expandHandOffTime(handOffTime string) *types.HandOffTime { -// split := strings.Split(handOffTime, ":") -// hour, _ := strconv.Atoi(split[0]) -// minute, _ := strconv.Atoi(split[1]) -// -// return &types.HandOffTime{ -// HourOfDay: int32(hour), -// MinuteOfHour: int32(minute), -// } -// } -func flattenHandOffTime(handOffTime *types.HandOffTime) string { - return fmt.Sprintf("%02d:%02d", handOffTime.HourOfDay, handOffTime.MinuteOfHour) -} - -// //func expandRecurrence(recurrence []interface{}, ctx context.Context) *types.RecurrenceSettings { -// // c := &types.RecurrenceSettings{} -// // -// // recurrenceSettings := recurrence[0].(map[string]interface{}) -// // -// // if v, ok := recurrenceSettings["daily_settings"].([]interface{}); ok && v != nil { -// // c.DailySettings = expandDailySettings(v) -// // } -// // -// // if v, ok := recurrenceSettings["monthly_settings"].([]interface{}); ok && v != nil { -// // c.MonthlySettings = expandMonthlySettings(v) -// // } -// // -// // if v, ok := recurrenceSettings["number_of_on_calls"].(int); ok { -// // c.NumberOfOnCalls = aws.Int32(int32(v)) -// // } -// // -// // if v, ok := recurrenceSettings["recurrence_multiplier"].(int); ok { -// // c.RecurrenceMultiplier = aws.Int32(int32(v)) -// // } -// // -// // if v, ok := recurrenceSettings["shift_coverages"].([]interface{}); ok && v != nil { -// // c.ShiftCoverages = expandShiftCoverages(v) -// // } -// // -// // if v, ok := recurrenceSettings["weekly_settings"].([]interface{}); ok && v != nil { -// // c.WeeklySettings = expandWeeklySettings(v) -// // } -// // -// // return c -// //} -//func flattenRecurrence(recurrence *types.RecurrenceSettings, ctx context.Context) []interface{} { -// var result []interface{} -// -// c := make(map[string]interface{}) -// -// if v := recurrence.DailySettings; v != nil { -// c["daily_settings"] = flattenDailySettings(v) -// } -// -// if v := recurrence.MonthlySettings; v != nil { -// c["monthly_settings"] = flattenMonthlySettings(v) -// } -// -// if v := recurrence.NumberOfOnCalls; v != nil { -// c["number_of_on_calls"] = aws.ToInt32(v) -// } -// -// if v := recurrence.RecurrenceMultiplier; v != nil { -// c["recurrence_multiplier"] = aws.ToInt32(v) -// } -// -// if v := recurrence.ShiftCoverages; v != nil { -// c["shift_coverages"] = flattenShiftCoverages(v) -// } -// -// if v := recurrence.WeeklySettings; v != nil { -// c["weekly_settings"] = flattenWeeklySettings(v) -// } -// -// result = append(result, c) -// -// return result -//} - -// // -// //func expandDailySettings(dailySettings []interface{}) []types.HandOffTime { -// // if len(dailySettings) == 0 { -// // return nil -// // } -// // -// // var result []types.HandOffTime -// // -// // -// // for _, dailySetting := range dailySettings { -// // result = append(result, *expandHandOffTime(dailySetting.(string))) -// // } -// // -// // return result -// //} -func flattenDailySettings(dailySettings []types.HandOffTime) []interface{} { - if len(dailySettings) == 0 { - return nil - } - - var result []interface{} - - for _, handOffTime := range dailySettings { - result = append(result, flattenHandOffTime(&handOffTime)) - } - - return result -} - -// func expandMonthlySettings(monthlySettings []interface{}) []types.MonthlySetting { -// if len(monthlySettings) == 0 { -// return nil -// } -// -// var result []types.MonthlySetting -// -// for _, monthlySetting := range monthlySettings { -// monthlySettingData := monthlySetting.(map[string]interface{}) -// -// c := types.MonthlySetting{ -// DayOfMonth: aws.Int32(int32(monthlySettingData["day_of_month"].(int))), -// HandOffTime: expandHandOffTime(monthlySettingData["hand_off_time"].(string)), -// } -// -// result = append(result, c) -// } -// -// return result -// } -func flattenMonthlySettings(monthlySettings []types.MonthlySetting) []interface{} { - if len(monthlySettings) == 0 { - return nil - } - - var result []interface{} - - for _, monthlySetting := range monthlySettings { - c := make(map[string]interface{}) - - if v := monthlySetting.DayOfMonth; v != nil { - c["day_of_month"] = aws.ToInt32(v) - } - - if v := monthlySetting.HandOffTime; v != nil { - c["hand_off_time"] = flattenHandOffTime(v) - } - - result = append(result, c) - } - - return result -} - -// //func expandShiftCoverages(shiftCoverages []interface{}) map[string][]types.CoverageTime { -// // if len(shiftCoverages) == 0 { -// // return nil -// // } -// // -// // result := make(map[string][]types.CoverageTime) -// // -// // for _, shiftCoverage := range shiftCoverages { -// // shiftCoverageData := shiftCoverage.(map[string]interface{}) -// // -// // dayOfWeek := shiftCoverageData["day_of_week"].(string) -// // coverageTimes := expandCoverageTimes(shiftCoverageData["coverage_times"].([]interface{})) -// // -// // result[dayOfWeek] = coverageTimes -// // } -// // -// // return result -// //} -//func flattenShiftCoverages(shiftCoverages map[string][]types.CoverageTime) []interface{} { -// if len(shiftCoverages) == 0 { -// return nil -// } -// -// var result []interface{} -// -// for coverageDay, coverageTime := range shiftCoverages { -// c := make(map[string]interface{}) -// -// c["coverage_times"] = flattenCoverageTimes(coverageTime) -// c["day_of_week"] = coverageDay -// -// result = append(result, c) -// } -// -// // API doesn't return in any consistent order. This causes flakes during testing, so we sort to always return a consistent order -// sortShiftCoverages(result) -// -// return result -//} - -// func expandCoverageTimes(coverageTimes []interface{}) []types.CoverageTime { -// var result []types.CoverageTime -// -// for _, coverageTime := range coverageTimes { -// coverageTimeData := coverageTime.(map[string]interface{}) -// -// c := types.CoverageTime{} -// -// if v, ok := coverageTimeData["end_time"].(string); ok { -// c.End = expandHandOffTime(v) -// } -// -// if v, ok := coverageTimeData["start_time"].(string); ok { -// c.Start = expandHandOffTime(v) -// } -// -// result = append(result, c) -// } -// -// return result -// } -//func flattenCoverageTimes(coverageTimes []types.CoverageTime) []interface{} { -// var result []interface{} -// -// c := make(map[string]interface{}) -// -// for _, coverageTime := range coverageTimes { -// if v := coverageTime.End; v != nil { -// c["end_time"] = flattenHandOffTime(v) -// } -// -// if v := coverageTime.Start; v != nil { -// c["start_time"] = flattenHandOffTime(v) -// } -// -// result = append(result, c) -// } -// -// return result -//} - -// func expandWeeklySettings(weeklySettings []interface{}) []types.WeeklySetting { -// var result []types.WeeklySetting -// -// for _, weeklySetting := range weeklySettings { -// weeklySettingData := weeklySetting.(map[string]interface{}) -// -// c := types.WeeklySetting{ -// DayOfWeek: types.DayOfWeek(weeklySettingData["day_of_week"].(string)), -// HandOffTime: expandHandOffTime(weeklySettingData["hand_off_time"].(string)), -// } -// -// result = append(result, c) -// -// } -// -// return result -// } -//func flattenWeeklySettings(weeklySettings []types.WeeklySetting) []interface{} { -// if len(weeklySettings) == 0 { -// return nil -// } -// -// var result []interface{} -// -// for _, weeklySetting := range weeklySettings { -// c := make(map[string]interface{}) -// -// if v := string(weeklySetting.DayOfWeek); v != "" { -// c["day_of_week"] = v -// } -// -// if v := weeklySetting.HandOffTime; v != nil { -// c["hand_off_time"] = flattenHandOffTime(v) -// } -// -// result = append(result, c) -// } -// -// return result -//} diff --git a/internal/service/ssmcontacts/rotation_data_source.go b/internal/service/ssmcontacts/rotation_data_source.go index d8928504181f..6733c96eee63 100644 --- a/internal/service/ssmcontacts/rotation_data_source.go +++ b/internal/service/ssmcontacts/rotation_data_source.go @@ -3,175 +3,213 @@ package ssmcontacts -//import ( -// "context" -// "time" -// -// "github.com/aws/aws-sdk-go-v2/aws" -// "github.com/hashicorp/terraform-plugin-sdk/v2/diag" -// "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" -// "github.com/hashicorp/terraform-provider-aws/internal/conns" -// "github.com/hashicorp/terraform-provider-aws/internal/create" -// tftags "github.com/hashicorp/terraform-provider-aws/internal/tags" -// "github.com/hashicorp/terraform-provider-aws/names" -//) -// -//// @SDKDataSource("aws_ssmcontacts_rotation") -//func DataSourceRotation() *schema.Resource { -// return &schema.Resource{ -// ReadWithoutTimeout: dataSourceRotationRead, -// -// Schema: map[string]*schema.Schema{ -// "arn": { -// Type: schema.TypeString, -// Required: true, -// }, -// "contact_ids": { -// Type: schema.TypeList, -// Computed: true, -// Elem: &schema.Schema{ -// Type: schema.TypeString, -// }, -// }, -// "name": { -// Type: schema.TypeString, -// Computed: true, -// }, -// "recurrence": { -// Type: schema.TypeList, -// Computed: true, -// Elem: &schema.Resource{ -// Schema: map[string]*schema.Schema{ -// "daily_settings": { -// Type: schema.TypeList, -// Computed: true, -// Elem: &schema.Schema{ -// Type: schema.TypeString, -// }, -// }, -// "monthly_settings": { -// Type: schema.TypeList, -// Computed: true, -// Elem: &schema.Resource{ -// Schema: map[string]*schema.Schema{ -// "day_of_month": { -// Type: schema.TypeInt, -// Computed: true, -// }, -// "hand_off_time": { -// Type: schema.TypeString, -// Computed: true, -// }, -// }, -// }, -// }, -// "number_of_on_calls": { -// Type: schema.TypeInt, -// Computed: true, -// }, -// "recurrence_multiplier": { -// Type: schema.TypeInt, -// Computed: true, -// }, -// "shift_coverages": { -// Type: schema.TypeList, -// Computed: true, -// Elem: &schema.Resource{ -// Schema: map[string]*schema.Schema{ -// "coverage_times": { -// Type: schema.TypeList, -// Computed: true, -// Elem: &schema.Resource{ -// Schema: map[string]*schema.Schema{ -// "end_time": { -// Type: schema.TypeString, -// Computed: true, -// }, -// "start_time": { -// Type: schema.TypeString, -// Computed: true, -// }, -// }, -// }, -// }, -// "day_of_week": { -// Type: schema.TypeString, -// Computed: true, -// }, -// }, -// }, -// }, -// "weekly_settings": { -// Type: schema.TypeList, -// Computed: true, -// Elem: &schema.Resource{ -// Schema: map[string]*schema.Schema{ -// "day_of_week": { -// Type: schema.TypeString, -// Computed: true, -// }, -// "hand_off_time": { -// Type: schema.TypeString, -// Computed: true, -// }, -// }, -// }, -// }, -// }, -// }, -// }, -// "start_time": { -// Type: schema.TypeString, -// Computed: true, -// }, -// "time_zone_id": { -// Type: schema.TypeString, -// Computed: true, -// }, -// names.AttrTags: tftags.TagsSchemaComputed(), -// }, -// } -//} -// -//const ( -// DSNameRotation = "Rotation Data Source" -//) -// -//func dataSourceRotationRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { -// conn := meta.(*conns.AWSClient).SSMContactsClient(ctx) -// arn := d.Get("arn").(string) -// -// out, err := findRotationByID(ctx, conn, arn) -// if err != nil { -// return create.DiagError(names.SSMContacts, create.ErrActionReading, DSNameRotation, arn, err) -// } -// -// d.SetId(aws.ToString(out.RotationArn)) -// -// d.Set("arn", out.RotationArn) -// d.Set("contact_ids", out.ContactIds) -// d.Set("name", out.Name) -// d.Set("time_zone_id", out.TimeZoneId) -// -// if out.StartTime != nil { -// d.Set("start_time", out.StartTime.Format(time.RFC3339)) -// } -// -// if err := d.Set("recurrence", flattenRecurrence(out.Recurrence, ctx)); err != nil { -// return create.DiagError(names.SSMContacts, create.ErrActionSetting, DSNameRotation, d.Id(), err) -// } -// -// tags, err := listTags(ctx, conn, d.Id()) -// if err != nil { -// return create.DiagError(names.SSMIncidents, create.ErrActionReading, DSNameRotation, d.Id(), err) -// } -// -// ignoreTagsConfig := meta.(*conns.AWSClient).IgnoreTagsConfig -// -// //lintignore:AWSR002 -// if err := d.Set("tags", tags.IgnoreAWS().IgnoreConfig(ignoreTagsConfig).Map()); err != nil { -// return create.DiagError(names.SSMContacts, create.ErrActionSetting, DSNameRotation, d.Id(), err) -// } -// -// return nil -//} +import ( + "context" + "time" + + awstypes "github.com/aws/aws-sdk-go-v2/service/ssmcontacts/types" + "github.com/hashicorp/terraform-plugin-framework/datasource" + "github.com/hashicorp/terraform-plugin-framework/datasource/schema" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-provider-aws/internal/create" + "github.com/hashicorp/terraform-provider-aws/internal/framework" + "github.com/hashicorp/terraform-provider-aws/internal/framework/flex" + fwtypes "github.com/hashicorp/terraform-provider-aws/internal/framework/types" + tftags "github.com/hashicorp/terraform-provider-aws/internal/tags" + "github.com/hashicorp/terraform-provider-aws/names" +) + +// @FrameworkDataSource(name="Rotation") +func newDataSourceRotation(context.Context) (datasource.DataSourceWithConfigure, error) { + d := &dataSourceRotation{} + + return d, nil +} + +type dataSourceRotation struct { + framework.DataSourceWithConfigure +} + +func (d *dataSourceRotation) Metadata(_ context.Context, request datasource.MetadataRequest, response *datasource.MetadataResponse) { + response.TypeName = "aws_ssmcontacts_rotation" +} + +func (d *dataSourceRotation) Schema(ctx context.Context, request datasource.SchemaRequest, response *datasource.SchemaResponse) { + response.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "arn": schema.StringAttribute{ + CustomType: fwtypes.ARNType, + Required: true, + }, + "contact_ids": schema.ListAttribute{ + CustomType: fwtypes.ListOfStringType, + ElementType: types.StringType, + Computed: true, + }, + "id": framework.IDAttribute(), + "name": schema.StringAttribute{ + Computed: true, + }, + "recurrence": schema.ListAttribute{ + CustomType: fwtypes.NewListNestedObjectTypeOf[dsRecurrenceData](ctx), + ElementType: fwtypes.NewObjectTypeOf[dsRecurrenceData](ctx), + Computed: true, + }, + "start_time": schema.StringAttribute{ + CustomType: fwtypes.TimestampType, + Computed: true, + }, + "tags": tftags.TagsAttributeComputedOnly(), + "time_zone_id": schema.StringAttribute{ + Computed: true, + }, + }, + } +} + +func (d *dataSourceRotation) Read(ctx context.Context, request datasource.ReadRequest, response *datasource.ReadResponse) { + conn := d.Meta().SSMContactsClient(ctx) + var data dataSourceRotationData + + response.Diagnostics.Append(request.Config.Get(ctx, &data)...) + + if response.Diagnostics.HasError() { + return + } + + output, err := findRotationByID(ctx, conn, data.ARN.ValueString()) + + if err != nil { + response.Diagnostics.AddError( + create.ProblemStandardMessage(names.SSMContacts, create.ErrActionSetting, ResNameRotation, data.ARN.ValueString(), err), + err.Error(), + ) + return + } + + rc := &dsRecurrenceData{} + rc.RecurrenceMultiplier = flex.Int32ToFramework(ctx, output.Recurrence.RecurrenceMultiplier) + rc.NumberOfOnCalls = flex.Int32ToFramework(ctx, output.Recurrence.NumberOfOnCalls) + + response.Diagnostics.Append(flex.Flatten(ctx, output.Recurrence.DailySettings, &rc.DailySettings)...) + if response.Diagnostics.HasError() { + return + } + + response.Diagnostics.Append(flex.Flatten(ctx, output.Recurrence.MonthlySettings, &rc.MonthlySettings)...) + if response.Diagnostics.HasError() { + return + } + + response.Diagnostics.Append(flex.Flatten(ctx, output.Recurrence.WeeklySettings, &rc.WeeklySettings)...) + if response.Diagnostics.HasError() { + return + } + + response.Diagnostics.Append(flex.Flatten(ctx, output.ContactIds, &data.ContactIds)...) + if response.Diagnostics.HasError() { + return + } + + rc.ShiftCoverages = flattenShiftCoveragesDataSource(ctx, output.Recurrence.ShiftCoverages) + + data.Name = flex.StringToFramework(ctx, output.Name) + data.Recurrence = fwtypes.NewListNestedObjectValueOfPtr(ctx, rc) + data.TimeZoneID = flex.StringToFramework(ctx, output.TimeZoneId) + data.ID = flex.StringToFramework(ctx, output.RotationArn) + + if output.StartTime != nil { + data.StartTime = fwtypes.TimestampValue(output.StartTime.Format(time.RFC3339)) + } + + tags, err := listTags(ctx, conn, data.ARN.ValueString()) + + if err != nil { + response.Diagnostics.AddError( + create.ProblemStandardMessage(names.SSMContacts, create.ErrActionSetting, ResNameRotation, data.ARN.ValueString(), err), + err.Error(), + ) + return + } + + data.Tags = flex.FlattenFrameworkStringValueMap(ctx, tags.Map()) + + response.Diagnostics.Append(response.State.Set(ctx, &data)...) +} + +type dataSourceRotationData struct { + ARN fwtypes.ARN `tfsdk:"arn"` + ContactIds fwtypes.ListValueOf[types.String] `tfsdk:"contact_ids"` + ID types.String `tfsdk:"id"` + Recurrence fwtypes.ListNestedObjectValueOf[dsRecurrenceData] `tfsdk:"recurrence"` + Name types.String `tfsdk:"name"` + StartTime fwtypes.Timestamp `tfsdk:"start_time"` + Tags types.Map `tfsdk:"tags"` + TimeZoneID types.String `tfsdk:"time_zone_id"` +} + +type dsRecurrenceData struct { + DailySettings fwtypes.ListNestedObjectValueOf[dsHandOffTime] `tfsdk:"daily_settings"` + MonthlySettings fwtypes.ListNestedObjectValueOf[dsMonthlySettingsData] `tfsdk:"monthly_settings"` + NumberOfOnCalls types.Int64 `tfsdk:"number_of_on_calls"` + RecurrenceMultiplier types.Int64 `tfsdk:"recurrence_multiplier"` + ShiftCoverages fwtypes.ListNestedObjectValueOf[dsShiftCoveragesData] `tfsdk:"shift_coverages"` + WeeklySettings fwtypes.ListNestedObjectValueOf[dsWeeklySettingsData] `tfsdk:"weekly_settings"` +} +type dsMonthlySettingsData struct { + DayOfMonth types.Int64 `tfsdk:"day_of_month"` + HandOffTime fwtypes.ListNestedObjectValueOf[dsHandOffTime] `tfsdk:"hand_off_time"` +} + +type dsShiftCoveragesData struct { + CoverageTimes fwtypes.ListNestedObjectValueOf[dsCoverageTimesData] `tfsdk:"coverage_times"` + MapBlockKey fwtypes.StringEnum[awstypes.DayOfWeek] `tfsdk:"map_block_key"` +} + +type dsCoverageTimesData struct { + End fwtypes.ListNestedObjectValueOf[dsHandOffTime] `tfsdk:"end"` + Start fwtypes.ListNestedObjectValueOf[dsHandOffTime] `tfsdk:"start"` +} + +type dsHandOffTime struct { + HourOfDay types.Int64 `tfsdk:"hour_of_day"` + MinuteOfHour types.Int64 `tfsdk:"minute_of_hour"` +} + +type dsWeeklySettingsData struct { + DayOfWeek fwtypes.StringEnum[awstypes.DayOfWeek] `tfsdk:"day_of_week"` + HandOffTime fwtypes.ListNestedObjectValueOf[dsHandOffTime] `tfsdk:"hand_off_time"` +} + +func flattenShiftCoveragesDataSource(ctx context.Context, object map[string][]awstypes.CoverageTime) fwtypes.ListNestedObjectValueOf[dsShiftCoveragesData] { + if len(object) == 0 { + return fwtypes.NewListNestedObjectValueOfNull[dsShiftCoveragesData](ctx) + } + + var output []dsShiftCoveragesData + for key, value := range object { + sc := dsShiftCoveragesData{ + MapBlockKey: fwtypes.StringEnumValue[awstypes.DayOfWeek](awstypes.DayOfWeek(key)), + } + + var coverageTimes []dsCoverageTimesData + for _, v := range value { + ct := dsCoverageTimesData{ + End: fwtypes.NewListNestedObjectValueOfPtr(ctx, &dsHandOffTime{ + HourOfDay: flex.Int32ValueToFramework(ctx, v.End.HourOfDay), + MinuteOfHour: flex.Int32ValueToFramework(ctx, v.End.MinuteOfHour), + }), + Start: fwtypes.NewListNestedObjectValueOfPtr(ctx, &dsHandOffTime{ + HourOfDay: flex.Int32ValueToFramework(ctx, v.Start.HourOfDay), + MinuteOfHour: flex.Int32ValueToFramework(ctx, v.End.MinuteOfHour), + }), + } + coverageTimes = append(coverageTimes, ct) + } + sc.CoverageTimes = fwtypes.NewListNestedObjectValueOfValueSlice(ctx, coverageTimes) + + output = append(output, sc) + } + + return fwtypes.NewListNestedObjectValueOfValueSlice[dsShiftCoveragesData](ctx, output) +} diff --git a/internal/service/ssmcontacts/rotation_data_source_fw.go b/internal/service/ssmcontacts/rotation_data_source_fw.go deleted file mode 100644 index c794e69450c4..000000000000 --- a/internal/service/ssmcontacts/rotation_data_source_fw.go +++ /dev/null @@ -1,218 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package ssmcontacts - -import ( - "context" - "time" - - awstypes "github.com/aws/aws-sdk-go-v2/service/ssmcontacts/types" - "github.com/hashicorp/terraform-plugin-framework/datasource" - "github.com/hashicorp/terraform-plugin-framework/datasource/schema" - "github.com/hashicorp/terraform-plugin-framework/types" - "github.com/hashicorp/terraform-provider-aws/internal/create" - "github.com/hashicorp/terraform-provider-aws/internal/framework" - "github.com/hashicorp/terraform-provider-aws/internal/framework/flex" - fwtypes "github.com/hashicorp/terraform-provider-aws/internal/framework/types" - tftags "github.com/hashicorp/terraform-provider-aws/internal/tags" - "github.com/hashicorp/terraform-provider-aws/names" -) - -// @FrameworkDataSource(name="Rotation") -func newDataSourceRotation(context.Context) (datasource.DataSourceWithConfigure, error) { - d := &dataSourceRotation{} - - return d, nil -} - -type dataSourceRotation struct { - framework.DataSourceWithConfigure -} - -func (d *dataSourceRotation) Metadata(_ context.Context, request datasource.MetadataRequest, response *datasource.MetadataResponse) { - response.TypeName = "aws_ssmcontacts_rotation" -} - -// Schema returns the schema for this data source. -func (d *dataSourceRotation) Schema(ctx context.Context, request datasource.SchemaRequest, response *datasource.SchemaResponse) { - response.Schema = schema.Schema{ - Attributes: map[string]schema.Attribute{ - "arn": schema.StringAttribute{ - CustomType: fwtypes.ARNType, - Required: true, - }, - "contact_ids": schema.ListAttribute{ - CustomType: fwtypes.ListOfStringType, - ElementType: types.StringType, - Computed: true, - }, - "id": framework.IDAttribute(), - "name": schema.StringAttribute{ - Computed: true, - }, - "recurrence": schema.ListAttribute{ - CustomType: fwtypes.NewListNestedObjectTypeOf[dsRecurrenceData](ctx), - ElementType: fwtypes.NewObjectTypeOf[dsRecurrenceData](ctx), - Computed: true, - }, - "start_time": schema.StringAttribute{ - CustomType: fwtypes.TimestampType, - Computed: true, - }, - "tags": tftags.TagsAttributeComputedOnly(), - "time_zone_id": schema.StringAttribute{ - Computed: true, - }, - }, - } -} - -// Read is called when the provider must read data source values in order to update state. -// Config values should be read from the ReadRequest and new state values set on the ReadResponse. -func (d *dataSourceRotation) Read(ctx context.Context, request datasource.ReadRequest, response *datasource.ReadResponse) { - conn := d.Meta().SSMContactsClient(ctx) - var data dataSourceRotationData - - response.Diagnostics.Append(request.Config.Get(ctx, &data)...) - - if response.Diagnostics.HasError() { - return - } - - output, err := findRotationByID(ctx, conn, data.ARN.ValueString()) - - if err != nil { - response.Diagnostics.AddError( - create.ProblemStandardMessage(names.SSMContacts, create.ErrActionSetting, ResNameRotation, data.ARN.ValueString(), err), - err.Error(), - ) - return - } - - rc := &dsRecurrenceData{} - rc.RecurrenceMultiplier = flex.Int32ToFramework(ctx, output.Recurrence.RecurrenceMultiplier) - rc.NumberOfOnCalls = flex.Int32ToFramework(ctx, output.Recurrence.NumberOfOnCalls) - - response.Diagnostics.Append(flex.Flatten(ctx, output.Recurrence.DailySettings, &rc.DailySettings)...) - if response.Diagnostics.HasError() { - return - } - - response.Diagnostics.Append(flex.Flatten(ctx, output.Recurrence.MonthlySettings, &rc.MonthlySettings)...) - if response.Diagnostics.HasError() { - return - } - - response.Diagnostics.Append(flex.Flatten(ctx, output.Recurrence.WeeklySettings, &rc.WeeklySettings)...) - if response.Diagnostics.HasError() { - return - } - - response.Diagnostics.Append(flex.Flatten(ctx, output.ContactIds, &data.ContactIds)...) - if response.Diagnostics.HasError() { - return - } - - rc.ShiftCoverages = flattenShiftCoveragesDataSource(ctx, output.Recurrence.ShiftCoverages) - - data.Name = flex.StringToFramework(ctx, output.Name) - data.Recurrence = fwtypes.NewListNestedObjectValueOfPtr(ctx, rc) - data.TimeZoneID = flex.StringToFramework(ctx, output.TimeZoneId) - data.ID = flex.StringToFramework(ctx, output.RotationArn) - - if output.StartTime != nil { - data.StartTime = fwtypes.TimestampValue(output.StartTime.Format(time.RFC3339)) - } - - tags, err := listTags(ctx, conn, data.ARN.ValueString()) - - if err != nil { - response.Diagnostics.AddError( - create.ProblemStandardMessage(names.SSMContacts, create.ErrActionSetting, ResNameRotation, data.ARN.ValueString(), err), - err.Error(), - ) - return - } - - data.Tags = flex.FlattenFrameworkStringValueMap(ctx, tags.Map()) - - response.Diagnostics.Append(response.State.Set(ctx, &data)...) -} - -type dataSourceRotationData struct { - ARN fwtypes.ARN `tfsdk:"arn"` - ContactIds fwtypes.ListValueOf[types.String] `tfsdk:"contact_ids"` - ID types.String `tfsdk:"id"` - Recurrence fwtypes.ListNestedObjectValueOf[dsRecurrenceData] `tfsdk:"recurrence"` - Name types.String `tfsdk:"name"` - StartTime fwtypes.Timestamp `tfsdk:"start_time"` - Tags types.Map `tfsdk:"tags"` - TimeZoneID types.String `tfsdk:"time_zone_id"` -} - -type dsRecurrenceData struct { - DailySettings fwtypes.ListNestedObjectValueOf[dsHandOffTime] `tfsdk:"daily_settings"` - MonthlySettings fwtypes.ListNestedObjectValueOf[dsMonthlySettingsData] `tfsdk:"monthly_settings"` - NumberOfOnCalls types.Int64 `tfsdk:"number_of_on_calls"` - RecurrenceMultiplier types.Int64 `tfsdk:"recurrence_multiplier"` - ShiftCoverages fwtypes.ListNestedObjectValueOf[dsShiftCoveragesData] `tfsdk:"shift_coverages"` - WeeklySettings fwtypes.ListNestedObjectValueOf[dsWeeklySettingsData] `tfsdk:"weekly_settings"` -} -type dsMonthlySettingsData struct { - DayOfMonth types.Int64 `tfsdk:"day_of_month"` - HandOffTime fwtypes.ListNestedObjectValueOf[dsHandOffTime] `tfsdk:"hand_off_time"` -} - -type dsShiftCoveragesData struct { - CoverageTimes fwtypes.ListNestedObjectValueOf[dsCoverageTimesData] `tfsdk:"coverage_times"` - MapBlockKey fwtypes.StringEnum[awstypes.DayOfWeek] `tfsdk:"map_block_key"` -} - -type dsCoverageTimesData struct { - End fwtypes.ListNestedObjectValueOf[dsHandOffTime] `tfsdk:"end"` - Start fwtypes.ListNestedObjectValueOf[dsHandOffTime] `tfsdk:"start"` -} - -type dsHandOffTime struct { - HourOfDay types.Int64 `tfsdk:"hour_of_day"` - MinuteOfHour types.Int64 `tfsdk:"minute_of_hour"` -} - -type dsWeeklySettingsData struct { - DayOfWeek fwtypes.StringEnum[awstypes.DayOfWeek] `tfsdk:"day_of_week"` - HandOffTime fwtypes.ListNestedObjectValueOf[dsHandOffTime] `tfsdk:"hand_off_time"` -} - -func flattenShiftCoveragesDataSource(ctx context.Context, object map[string][]awstypes.CoverageTime) fwtypes.ListNestedObjectValueOf[dsShiftCoveragesData] { - if len(object) == 0 { - return fwtypes.NewListNestedObjectValueOfNull[dsShiftCoveragesData](ctx) - } - - var output []dsShiftCoveragesData - for key, value := range object { - sc := dsShiftCoveragesData{ - MapBlockKey: fwtypes.StringEnumValue[awstypes.DayOfWeek](awstypes.DayOfWeek(key)), - } - - var coverageTimes []dsCoverageTimesData - for _, v := range value { - ct := dsCoverageTimesData{ - End: fwtypes.NewListNestedObjectValueOfPtr(ctx, &dsHandOffTime{ - HourOfDay: flex.Int32ValueToFramework(ctx, v.End.HourOfDay), - MinuteOfHour: flex.Int32ValueToFramework(ctx, v.End.MinuteOfHour), - }), - Start: fwtypes.NewListNestedObjectValueOfPtr(ctx, &dsHandOffTime{ - HourOfDay: flex.Int32ValueToFramework(ctx, v.Start.HourOfDay), - MinuteOfHour: flex.Int32ValueToFramework(ctx, v.End.MinuteOfHour), - }), - } - coverageTimes = append(coverageTimes, ct) - } - sc.CoverageTimes = fwtypes.NewListNestedObjectValueOfValueSlice(ctx, coverageTimes) - - output = append(output, sc) - } - - return fwtypes.NewListNestedObjectValueOfValueSlice[dsShiftCoveragesData](ctx, output) -} From 55e1e9718bbbeba7db0ced73df04c82651065973 Mon Sep 17 00:00:00 2001 From: Adrian Johnson Date: Wed, 31 Jan 2024 15:49:03 -0600 Subject: [PATCH 16/22] fmt resource docs --- .../docs/r/ssmcontacts_rotation.html.markdown | 183 ++++++++++-------- 1 file changed, 104 insertions(+), 79 deletions(-) diff --git a/website/docs/r/ssmcontacts_rotation.html.markdown b/website/docs/r/ssmcontacts_rotation.html.markdown index 182c34ae4f7f..64ade1f74e76 100644 --- a/website/docs/r/ssmcontacts_rotation.html.markdown +++ b/website/docs/r/ssmcontacts_rotation.html.markdown @@ -17,17 +17,18 @@ Provides a Terraform resource for managing a Contacts Rotation in AWS Systems Ma ```terraform resource "aws_ssmcontacts_rotation" "example" { contact_ids = [ - aws_ssmcontacts_contact.example.arn + aws_ssmcontacts_contact.example.arn ] name = "rotation" recurrence { - number_of_on_calls = 1 + number_of_on_calls = 1 recurrence_multiplier = 1 - daily_settings = [ - "01:00" - ] + daily_settings { + hour_of_day = 9 + minute_of_hour = 00 + } } time_zone_id = "Australia/Sydney" @@ -41,47 +42,43 @@ resource "aws_ssmcontacts_rotation" "example" { ```terraform resource "aws_ssmcontacts_rotation" "example" { contact_ids = [ - aws_ssmcontacts_contact.example.arn + aws_ssmcontacts_contact.example.arn ] name = "rotation" recurrence { - number_of_on_calls = 1 + number_of_on_calls = 1 recurrence_multiplier = 1 - weekly_settings { - day_of_week = "MON" - hand_off_time = "04:25" - } - weekly_settings { - day_of_week = "WED" - hand_off_time = "07:34" - } - weekly_settings { - day_of_week = "FRI" - hand_off_time = "15:57" - } + weekly_settings { + day_of_week = "WED" + hand_off_time { + hour_of_day = 04 + minute_of_hour = 25 + } + } + + weekly_settings { + day_of_week = "FRI" + hand_off_time { + hour_of_day = 15 + minute_of_hour = 57 + } + } + shift_coverages { - day_of_week = "MON" - coverage_times { - start_time = "01:00" - end_time = "23:00" - } - } - shift_coverages { - day_of_week = "WED" - coverage_times { - start_time = "01:00" - end_time = "23:00" - } - } - shift_coverages { - day_of_week = "FRI" - coverage_times { - start_time = "01:00" - end_time = "23:00" - } - } + map_block_key = "MON" + coverage_times { + start { + hour_of_day = 01 + minute_of_hour = 00 + } + end { + hour_of_day = 23 + minute_of_hour = 00 + } + } + } } start_time = "2023-07-20T02:21:49+00:00" @@ -102,31 +99,33 @@ resource "aws_ssmcontacts_rotation" "example" { ```terraform resource "aws_ssmcontacts_rotation" "example" { contact_ids = [ - aws_ssmcontacts_contact.example.arn, + aws_ssmcontacts_contact.example.arn, ] name = "rotation" recurrence { - number_of_on_calls = 1 + number_of_on_calls = 1 recurrence_multiplier = 1 - monthly_settings { - day_of_month = 20 - hand_off_time = "08:00" - } - monthly_settings { - day_of_month = 13 - hand_off_time = "12:34" - } - monthly_settings { - day_of_month = 1 - hand_off_time = "04:58" - } + monthly_settings { + day_of_month = 20 + hand_off_time { + hour_of_day = 8 + minute_of_hour = 00 + } + } + monthly_settings { + day_of_month = 13 + hand_off_time { + hour_of_day = 12 + minute_of_hour = 34 + } + } } time_zone_id = "Australia/Sydney" - depends_on = [aws_ssmincidents_replication_set.test] + depends_on = [aws_ssmincidents_replication_set.example] } ``` @@ -139,46 +138,72 @@ The following arguments are required: * `contact_ids` - (Required) Amazon Resource Names (ARNs) of the contacts to add to the rotation. The order in which you list the contacts is their shift order in the rotation schedule. * `name` - (Required) The name for the rotation. * `time_zone_id` - (Required) The time zone to base the rotation’s activity on in Internet Assigned Numbers Authority (IANA) format. -* `recurrence` - (Required) Information about when an on-call rotation is in effect and how long the rotation period lasts. Exactly one of either `daily_settings`, `monthly_settings`, or `weekly_settings` must be populated. - * `number_of_oncalls` - (Required) The number of contacts, or shift team members designated to be on call concurrently during a shift. - * `recurrence_multiplier` - (Required) The number of days, weeks, or months a single rotation lasts. - * `daily_settings` - (Optional) Information about on-call rotations that recur daily. Composed of a list of times, in 24-hour format, for when daily on-call shift rotations begin. - * `monthly_settings` - (Optional) Information about on-call rotations that recur monthly. - * `day_of_month` - (Required) The day of the month when monthly recurring on-call rotations begin. - * `hand_off_time` - (Required) The time of day, in 24-hour format, when a monthly recurring on-call shift rotation begins. - * `weekly_settings` - (Optional) Information about rotations that recur weekly. - * `day_of_week` - (Required) The day of the week when weekly recurring on-call shift rotations begins. - * `hand_off_time` - (Required) The time of day, in 24-hour format, when a weekly recurring on-call shift rotation begins. - * `shift_coverages` - (Optional) Information about the days of the week that the on-call rotation coverage includes. - * `coverage_times` - (Required) Information about when an on-call shift begins and ends. - * `end_time` - (Optional) The time, in 24-hour format, that the on-call rotation shift ends. - * `start_time` - (Optional) The time, in 24-hour format, that the on-call rotation shift begins. - * `day_of_week` - (Required) The day of the week when the shift coverage occurs. +* `recurrence` - (Required) Information about when an on-call rotation is in effect and how long the rotation period lasts. Exactly one of either `daily_settings`, `monthly_settings`, or `weekly_settings` must be populated. See [Recurrence](#recurrence) for more details. The following arguments are optional: * `start_time` - (Optional) The date and time, in RFC 3339 format, that the rotation goes into effect. -* `tags` - (Optional) A map of tags to assign to the resource. +* `tags` - (Optional) A map of tags to assign to the resource. If configured with a provider [`default_tags` configuration block](https://registry.terraform.io/providers/hashicorp/aws/latest/docs#default_tags-configuration-block) present, tags with matching keys will overwrite those defined at the provider-level. -## Attributes Reference +## Attribute Reference -In addition to all arguments above, the following attributes are exported: +This resource exports the following attributes in addition to the arguments above: * `arn` - The Amazon Resource Name (ARN) of the rotation. * `tags_all` - A map of tags assigned to the resource, including those inherited from the provider [`default_tags` configuration block](https://registry.terraform.io/providers/hashicorp/aws/latest/docs#default_tags-configuration-block). -## Timeouts -[Configuration options](https://developer.hashicorp.com/terraform/language/resources/syntax#operation-timeouts): +### Recurrence + +* `number_of_on_calls` - (Required) The number of contacts, or shift team members designated to be on call concurrently during a shift. +* `recurrence_multiplier` - (Required) The number of days, weeks, or months a single rotation lasts. +* `daly_settings` - (Optional) Information about on-call rotations that recur daily. Composed of a list of times, in 24-hour format, for when daily on-call shift rotations begin. See [Daily Settings](#daily-settings) for more details. +* `monthly_settings` - (Optional) Information about on-call rotations that recur monthly. See [Monthly Settings](#monthly-settings) for more details. +* `weekly_settings` - (Optional) Information about on-call rotations that recur weekly. See [Weekly Settings](#weekly-settings) for more details. +* `shift_coverages` - (Optional) Information about the days of the week that the on-call rotation coverage includes. See [Shift Coverages](#shift-coverages) for more details. + +### Daily Settings + +* `hour_of_day` - (Required) The hour of the day. +* `minute_of_hour` - (Required) The minutes of the hour. + +### Monthly Settings + +* `day_of_month` - (Required) The day of the month when monthly recurring on-call rotations begin. +* `hand_off_time` - (Required) The hand off time. See [Hand Off Time](#hand-off-time) for more details. + +### Weekly Settings +* `day_of_week` - (Required) The day of the week when weekly recurring on-call shift rotations begins. +* `hand_off_time` - (Required) The hand off time. See [Hand Off Time](#hand-off-time) for more details. + +### Hand Off Time -* `create` - (Default `30m`) -* `update` - (Default `30m`) -* `delete` - (Default `30m`) +* `hour_of_day` - (Required) The hour of the day. +* `minute_of_hour` - (Required) The minutes of the hour. + +### Shift Coverages + +* `coverage_times` - (Required) Information about when an on-call shift begins and ends. See [Coverage Times](#coverage-times) for more details. +* `day_of_week` - (Required) The day of the week when the shift coverage occurs. + +### Coverage Times + +* `start` - (Required) The start time of the on-call shift. See [Hand Off Time](#hand-off-time) for more details. +* `end` - (Required) The end time of the on-call shift. See [Hand Off Time](#hand-off-time) for more details. ## Import -An Incident Manager Contacts Rotation can be imported using the `ARN`. For example: +In Terraform v1.5.0 and later, use an [`import` block](https://developer.hashicorp.com/terraform/language/import) to import SSMContacts Rotation using the `arn`. For example: +```terraform +import { + to = aws_ssmcontacts_rotation.example + id = "arn:aws:ssm-contacts:us-east-1:012345678910:rotation/example" +} ``` -$ terraform import aws_ssmcontacts_rotation.example {ARNValue} + +Using `terraform import`, import CodeGuru Profiler Profiling Group using the `arn`. For example: + +```console +% terraform import aws_ssmcontacts_rotation.example arn:aws:ssm-contacts:us-east-1:012345678910:rotation/example ``` From 36a0420a431b149f78788d6a46f6ccc4a58802e0 Mon Sep 17 00:00:00 2001 From: Adrian Johnson Date: Wed, 31 Jan 2024 15:51:59 -0600 Subject: [PATCH 17/22] fmt resource docs --- .../docs/d/ssmcontacts_rotation.html.markdown | 24 ++++--------------- .../docs/r/ssmcontacts_rotation.html.markdown | 2 +- 2 files changed, 6 insertions(+), 20 deletions(-) diff --git a/website/docs/d/ssmcontacts_rotation.html.markdown b/website/docs/d/ssmcontacts_rotation.html.markdown index 6221c7ce09da..10586063fbda 100644 --- a/website/docs/d/ssmcontacts_rotation.html.markdown +++ b/website/docs/d/ssmcontacts_rotation.html.markdown @@ -16,7 +16,7 @@ Provides a Terraform data source for managing a Contacts Rotation in AWS Systems ```terraform data "aws_ssmcontacts_rotation" "example" { - arn = "exampleARN" + arn = "arn:aws:ssm-contacts:us-east-1:012345678910:rotation/example" } ``` @@ -26,27 +26,13 @@ The following arguments are required: * `arn` - (Required) The Amazon Resource Name (ARN) of the rotation. -## Attributes Reference +## Attribute Reference -In addition to all arguments above, the following attributes are exported: +This data source exports the following attributes in addition to the arguments above: * `contact_ids` - The Amazon Resource Names (ARNs) of the contacts to add to the rotation. The order in which you list the contacts is their shift order in the rotation schedule. * `name` - The name for the rotation. * `time_zone_id` - The time zone to base the rotation’s activity on in Internet Assigned Numbers Authority (IANA) format. -* `recurrence` - Information about when an on-call rotation is in effect and how long the rotation period lasts. Exactly one of either `daily_settings`, `monthly_settings`, or `weekly_settings` must be populated. - * `number_of_oncalls` - The number of contacts, or shift team members designated to be on call concurrently during a shift. - * `recurrence_multiplier` - The number of days, weeks, or months a single rotation lasts. - * `daily_settings` - - Information about on-call rotations that recur daily. Composed of a list of times, in 24-hour format, for when daily on-call shift rotation begins. - * `monthly_settings` - Information about on-call rotations that recur monthly. - * `day_of_month` - The day of the month when monthly recurring on-call rotations begin. - * `hand_off_time` The time of day, in 24-hour format, when a monthly recurring on-call shift rotation begins. - * `weekly_settings` - Information about rotations that recur weekly. - * `day_of_week` - The day of the week when weekly recurring on-call shift rotations begins. - * `hand_off_time` - The time of day, in 24-hour format, when a weekly recurring on-call shift rotation begins. - * `shift_coverages` - Information about the days of the week that the on-call rotation coverage includes. - * `coverage_times` - Information about when an on-call shift begins and ends. - * `end_time` - The time, in 24-hour format,, hat the on-call rotation shift ends. - * `start_time` - The time, in 24-hour format, that the on-call rotation shift begins. - * `day_of_week` - The day of the week when the shift coverage occurs. +* `recurrence` - Information about when an on-call rotation is in effect and how long the rotation period lasts. * `start_time` - The date and time, in RFC 3339 format, that the rotation goes into effect. -* `tags` - A map of tags to assign to the resource. +* `tags` - A map of tags to assign to the resource. \ No newline at end of file diff --git a/website/docs/r/ssmcontacts_rotation.html.markdown b/website/docs/r/ssmcontacts_rotation.html.markdown index 64ade1f74e76..542a28c7b62e 100644 --- a/website/docs/r/ssmcontacts_rotation.html.markdown +++ b/website/docs/r/ssmcontacts_rotation.html.markdown @@ -206,4 +206,4 @@ Using `terraform import`, import CodeGuru Profiler Profiling Group using the `ar ```console % terraform import aws_ssmcontacts_rotation.example arn:aws:ssm-contacts:us-east-1:012345678910:rotation/example -``` +``` \ No newline at end of file From 4e559737cda4151bd790096fa483ec46b74a5ef5 Mon Sep 17 00:00:00 2001 From: Adrian Johnson Date: Wed, 31 Jan 2024 15:55:17 -0600 Subject: [PATCH 18/22] fix document linter --- website/docs/d/ssmcontacts_rotation.html.markdown | 2 +- website/docs/r/ssmcontacts_rotation.html.markdown | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/website/docs/d/ssmcontacts_rotation.html.markdown b/website/docs/d/ssmcontacts_rotation.html.markdown index 10586063fbda..0574c92ae447 100644 --- a/website/docs/d/ssmcontacts_rotation.html.markdown +++ b/website/docs/d/ssmcontacts_rotation.html.markdown @@ -35,4 +35,4 @@ This data source exports the following attributes in addition to the arguments a * `time_zone_id` - The time zone to base the rotation’s activity on in Internet Assigned Numbers Authority (IANA) format. * `recurrence` - Information about when an on-call rotation is in effect and how long the rotation period lasts. * `start_time` - The date and time, in RFC 3339 format, that the rotation goes into effect. -* `tags` - A map of tags to assign to the resource. \ No newline at end of file +* `tags` - A map of tags to assign to the resource. diff --git a/website/docs/r/ssmcontacts_rotation.html.markdown b/website/docs/r/ssmcontacts_rotation.html.markdown index 542a28c7b62e..f7d1816c0a62 100644 --- a/website/docs/r/ssmcontacts_rotation.html.markdown +++ b/website/docs/r/ssmcontacts_rotation.html.markdown @@ -152,7 +152,6 @@ This resource exports the following attributes in addition to the arguments abov * `arn` - The Amazon Resource Name (ARN) of the rotation. * `tags_all` - A map of tags assigned to the resource, including those inherited from the provider [`default_tags` configuration block](https://registry.terraform.io/providers/hashicorp/aws/latest/docs#default_tags-configuration-block). - ### Recurrence * `number_of_on_calls` - (Required) The number of contacts, or shift team members designated to be on call concurrently during a shift. @@ -173,6 +172,7 @@ This resource exports the following attributes in addition to the arguments abov * `hand_off_time` - (Required) The hand off time. See [Hand Off Time](#hand-off-time) for more details. ### Weekly Settings + * `day_of_week` - (Required) The day of the week when weekly recurring on-call shift rotations begins. * `hand_off_time` - (Required) The hand off time. See [Hand Off Time](#hand-off-time) for more details. @@ -206,4 +206,4 @@ Using `terraform import`, import CodeGuru Profiler Profiling Group using the `ar ```console % terraform import aws_ssmcontacts_rotation.example arn:aws:ssm-contacts:us-east-1:012345678910:rotation/example -``` \ No newline at end of file +``` From 174a528433e9ecf02836ad9d654a463867608f21 Mon Sep 17 00:00:00 2001 From: Adrian Johnson Date: Wed, 31 Jan 2024 15:59:06 -0600 Subject: [PATCH 19/22] fix document linter --- .../docs/r/ssmcontacts_rotation.html.markdown | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/website/docs/r/ssmcontacts_rotation.html.markdown b/website/docs/r/ssmcontacts_rotation.html.markdown index f7d1816c0a62..bc5218f3998f 100644 --- a/website/docs/r/ssmcontacts_rotation.html.markdown +++ b/website/docs/r/ssmcontacts_rotation.html.markdown @@ -24,16 +24,16 @@ resource "aws_ssmcontacts_rotation" "example" { recurrence { number_of_on_calls = 1 - recurrence_multiplier = 1 + recurrence_multiplier = 1 daily_settings { hour_of_day = 9 minute_of_hour = 00 } } - - time_zone_id = "Australia/Sydney" - depends_on = [aws_ssmincidents_replication_set.example] + time_zone_id = "Australia/Sydney" + + depends_on = [aws_ssmincidents_replication_set.example] } ``` @@ -49,7 +49,7 @@ resource "aws_ssmcontacts_rotation" "example" { recurrence { number_of_on_calls = 1 - recurrence_multiplier = 1 + recurrence_multiplier = 1 weekly_settings { day_of_week = "WED" hand_off_time { @@ -65,7 +65,7 @@ resource "aws_ssmcontacts_rotation" "example" { minute_of_hour = 57 } } - + shift_coverages { map_block_key = "MON" coverage_times { @@ -86,10 +86,10 @@ resource "aws_ssmcontacts_rotation" "example" { time_zone_id = "Australia/Sydney" tags = { - key1 = "tag1" - key2 = "tag2" + key1 = "tag1" + key2 = "tag2" } - + depends_on = [aws_ssmincidents_replication_set.example] } ``` @@ -106,7 +106,7 @@ resource "aws_ssmcontacts_rotation" "example" { recurrence { number_of_on_calls = 1 - recurrence_multiplier = 1 + recurrence_multiplier = 1 monthly_settings { day_of_month = 20 hand_off_time { @@ -122,10 +122,10 @@ resource "aws_ssmcontacts_rotation" "example" { } } } - - time_zone_id = "Australia/Sydney" - depends_on = [aws_ssmincidents_replication_set.example] + time_zone_id = "Australia/Sydney" + + depends_on = [aws_ssmincidents_replication_set.example] } ``` From 7979c59d1ae924ab7c2ad20a34402703a2c37826 Mon Sep 17 00:00:00 2001 From: Adrian Johnson Date: Wed, 31 Jan 2024 16:11:34 -0600 Subject: [PATCH 20/22] remove unused code --- internal/service/ssmcontacts/helper.go | 7 ------- 1 file changed, 7 deletions(-) diff --git a/internal/service/ssmcontacts/helper.go b/internal/service/ssmcontacts/helper.go index f31bd43d4215..3e541c4a4048 100644 --- a/internal/service/ssmcontacts/helper.go +++ b/internal/service/ssmcontacts/helper.go @@ -5,7 +5,6 @@ package ssmcontacts import ( "fmt" - "sort" "github.com/aws/aws-sdk-go-v2/service/ssmcontacts" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" @@ -41,9 +40,3 @@ func setPlanResourceData(d *schema.ResourceData, getContactOutput *ssmcontacts.G return nil } - -func sortShiftCoverages(shiftCoverages []interface{}) { - sort.Slice(shiftCoverages, func(i, j int) bool { - return shiftCoverages[i].(map[string]interface{})["day_of_week"].(string) < shiftCoverages[j].(map[string]interface{})["day_of_week"].(string) - }) -} From 2f8731359a5b2b3dac230d19cddb5ec610e09138 Mon Sep 17 00:00:00 2001 From: Adrian Johnson Date: Wed, 31 Jan 2024 17:02:13 -0600 Subject: [PATCH 21/22] add tags test --- internal/service/ssmcontacts/ssmcontacts_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/internal/service/ssmcontacts/ssmcontacts_test.go b/internal/service/ssmcontacts/ssmcontacts_test.go index 9b98e3690df6..c9b336de1f0a 100644 --- a/internal/service/ssmcontacts/ssmcontacts_test.go +++ b/internal/service/ssmcontacts/ssmcontacts_test.go @@ -58,6 +58,7 @@ func TestAccSSMContacts_serial(t *testing.T) { "startTime": testRotation_startTime, "contactIds": testRotation_contactIds, "recurrence": testRotation_recurrence, + "tags": testRotation_tags, }, "RotationDataSource": { "basic": testRotationDataSource_basic, From 1ef9e8924890b8e3eb75d3c5dbb0ed30fe5caf33 Mon Sep 17 00:00:00 2001 From: Adrian Johnson Date: Wed, 31 Jan 2024 17:38:28 -0600 Subject: [PATCH 22/22] add CHANGELOG entry --- .changelog/32710.txt | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .changelog/32710.txt diff --git a/.changelog/32710.txt b/.changelog/32710.txt new file mode 100644 index 000000000000..34db2ab67eef --- /dev/null +++ b/.changelog/32710.txt @@ -0,0 +1,7 @@ +```release-note:new-resource +aws_ssmcontacts_rotation +``` + +```release-note:new-data-source +aws_ssmcontacts_rotation +``` \ No newline at end of file