From 41095fdbf5f8bcdb09a3e55bd9332d3a2dad3c80 Mon Sep 17 00:00:00 2001 From: Roy Rajan Date: Sun, 19 Mar 2023 23:02:58 +0000 Subject: [PATCH 01/10] Adding resource - OAM Link --- internal/service/oam/link.go | 274 +++++++++ internal/service/oam/link_test.go | 614 ++++++++++++++++++++ internal/service/oam/service_package_gen.go | 4 + website/docs/r/oam_link.html.markdown | 64 ++ 4 files changed, 956 insertions(+) create mode 100644 internal/service/oam/link.go create mode 100644 internal/service/oam/link_test.go create mode 100644 website/docs/r/oam_link.html.markdown diff --git a/internal/service/oam/link.go b/internal/service/oam/link.go new file mode 100644 index 000000000000..a4e98b74178a --- /dev/null +++ b/internal/service/oam/link.go @@ -0,0 +1,274 @@ +package oam + +import ( + "context" + "errors" + "log" + "time" + + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/oam" + "github.com/aws/aws-sdk-go-v2/service/oam/types" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/create" + "github.com/hashicorp/terraform-provider-aws/internal/flex" + 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_oam_link") +func ResourceLink() *schema.Resource { + return &schema.Resource{ + CreateWithoutTimeout: resourceLinkCreate, + ReadWithoutTimeout: resourceLinkRead, + UpdateWithoutTimeout: resourceLinkUpdate, + DeleteWithoutTimeout: resourceLinkDelete, + + Importer: &schema.ResourceImporter{ + StateContext: schema.ImportStatePassthroughContext, + }, + + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(1 * time.Minute), + Update: schema.DefaultTimeout(1 * time.Minute), + Delete: schema.DefaultTimeout(1 * time.Minute), + }, + + Schema: map[string]*schema.Schema{ + "arn": { + Type: schema.TypeString, + Computed: true, + }, + "label": { + Type: schema.TypeString, + Computed: true, + }, + "label_template": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "link_id": { + Type: schema.TypeString, + Computed: true, + }, + "resource_types": { + Type: schema.TypeSet, + Required: true, + MinItems: 1, + MaxItems: 50, + Elem: &schema.Schema{ + Type: schema.TypeString, + ValidateFunc: validation.StringInSlice(ResourceTypeValues(types.ResourceType("").Values()), false), + }, + Set: schema.HashString, + }, + "sink_arn": { + Type: schema.TypeString, + Computed: true, + }, + "sink_identifier": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "tags": tftags.TagsSchema(), + "tags_all": tftags.TagsSchemaComputed(), + }, + + CustomizeDiff: verify.SetTagsDiff, + } +} + +const ( + ResNameLink = "Link" +) + +func resourceLinkCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.AWSClient).ObservabilityAccessManagerClient() + + in := &oam.CreateLinkInput{ + LabelTemplate: aws.String(d.Get("label_template").(string)), + ResourceTypes: ExpandResourceTypes(d.Get("resource_types").(*schema.Set).List()), + SinkIdentifier: aws.String(d.Get("sink_identifier").(string)), + } + + defaultTagsConfig := meta.(*conns.AWSClient).DefaultTagsConfig + tags := defaultTagsConfig.MergeTags(tftags.New(ctx, d.Get("tags").(map[string]interface{}))) + + if len(tags) > 0 { + in.Tags = Tags(tags.IgnoreAWS()) + } + + out, err := conn.CreateLink(ctx, in) + if err != nil { + return create.DiagError(names.ObservabilityAccessManager, create.ErrActionCreating, ResNameLink, d.Get("sink_identifier").(string), err) + } + + if out == nil || out.Id == nil { + return create.DiagError(names.ObservabilityAccessManager, create.ErrActionCreating, ResNameLink, d.Get("sink_identifier").(string), errors.New("empty output")) + } + + d.SetId(aws.ToString(out.Arn)) + + return resourceLinkRead(ctx, d, meta) +} + +func resourceLinkRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.AWSClient).ObservabilityAccessManagerClient() + + out, err := findLinkByID(ctx, conn, d.Id()) + + if !d.IsNewResource() && tfresource.NotFound(err) { + log.Printf("[WARN] ObservabilityAccessManager Link (%s) not found, removing from state", d.Id()) + d.SetId("") + return nil + } + + if err != nil { + return create.DiagError(names.ObservabilityAccessManager, create.ErrActionReading, ResNameLink, d.Id(), err) + } + + d.Set("arn", out.Arn) + d.Set("label", out.Label) + d.Set("label_template", out.LabelTemplate) + d.Set("link_id", out.Id) + d.Set("resource_types", flex.FlattenStringValueList(out.ResourceTypes)) + d.Set("sink_arn", out.SinkArn) + if _, ok := d.GetOk("sink_identifier"); !ok { + d.Set("sink_identifier", out.SinkArn) + } + + tags, err := ListTags(ctx, conn, d.Id()) + if err != nil { + return create.DiagError(names.ObservabilityAccessManager, create.ErrActionReading, ResNameLink, d.Id(), err) + } + + defaultTagsConfig := meta.(*conns.AWSClient).DefaultTagsConfig + ignoreTagsConfig := meta.(*conns.AWSClient).IgnoreTagsConfig + tags = tags.IgnoreAWS().IgnoreConfig(ignoreTagsConfig) + + if err := d.Set("tags", tags.RemoveDefaultConfig(defaultTagsConfig).Map()); err != nil { + return create.DiagError(names.ObservabilityAccessManager, create.ErrActionSetting, ResNameLink, d.Id(), err) + } + + if err := d.Set("tags_all", tags.Map()); err != nil { + return create.DiagError(names.ObservabilityAccessManager, create.ErrActionSetting, ResNameLink, d.Id(), err) + } + + return nil +} + +func resourceLinkUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.AWSClient).ObservabilityAccessManagerClient() + + update := false + + in := &oam.UpdateLinkInput{ + Identifier: aws.String(d.Id()), + } + + if d.HasChanges("resource_types") { + in.ResourceTypes = ExpandResourceTypes(d.Get("resource_types").(*schema.Set).List()) + update = true + } + + if d.HasChange("tags_all") { + log.Printf("[DEBUG] Updating ObservabilityAccessManager Link Tags (%s): %#v", d.Id(), d.Get("tags_all")) + oldTags, newTags := d.GetChange("tags_all") + if err := UpdateTags(ctx, conn, d.Get("arn").(string), oldTags, newTags); err != nil { + return create.DiagError(names.ObservabilityAccessManager, create.ErrActionUpdating, ResNameSink, d.Id(), err) + } + } + + if update { + log.Printf("[DEBUG] Updating ObservabilityAccessManager Link (%s): %#v", d.Id(), in) + _, err := conn.UpdateLink(ctx, in) + if err != nil { + return create.DiagError(names.ObservabilityAccessManager, create.ErrActionUpdating, ResNameLink, d.Id(), err) + } + } + + return resourceLinkRead(ctx, d, meta) +} + +func resourceLinkDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.AWSClient).ObservabilityAccessManagerClient() + + log.Printf("[INFO] Deleting ObservabilityAccessManager Link %s", d.Id()) + + _, err := conn.DeleteLink(ctx, &oam.DeleteLinkInput{ + Identifier: aws.String(d.Id()), + }) + + if err != nil { + var nfe *types.ResourceNotFoundException + if errors.As(err, &nfe) { + return nil + } + + return create.DiagError(names.ObservabilityAccessManager, create.ErrActionDeleting, ResNameLink, d.Id(), err) + } + + return nil +} + +func findLinkByID(ctx context.Context, conn *oam.Client, id string) (*oam.GetLinkOutput, error) { + in := &oam.GetLinkInput{ + Identifier: aws.String(id), + } + out, err := conn.GetLink(ctx, in) + if err != nil { + var nfe *types.ResourceNotFoundException + if errors.As(err, &nfe) { + return nil, &resource.NotFoundError{ + LastError: err, + LastRequest: in, + } + } + + return nil, err + } + + if out == nil || out.Arn == nil { + return nil, tfresource.NewEmptyResultError(in) + } + + return out, nil +} + +func ExpandResourceTypes(resourceTypeList []interface{}) []types.ResourceType { + if len(resourceTypeList) == 0 { + return nil + } + + var resourceTypes []types.ResourceType + + for _, resourceTypeString := range resourceTypeList { + if resourceTypeString == nil { + continue + } + + resourceType := types.ResourceType(resourceTypeString.(string)) + resourceTypes = append(resourceTypes, resourceType) + } + + return resourceTypes +} + +func ResourceTypeValues(resourceTypes []types.ResourceType) []string { + var out []string + + for _, v := range resourceTypes { + out = append(out, string(v)) + } + + return out +} diff --git a/internal/service/oam/link_test.go b/internal/service/oam/link_test.go new file mode 100644 index 000000000000..60265502521a --- /dev/null +++ b/internal/service/oam/link_test.go @@ -0,0 +1,614 @@ +package oam_test + +import ( + "context" + "errors" + "fmt" + "regexp" + "testing" + + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/oam" + "github.com/aws/aws-sdk-go-v2/service/oam/types" + "github.com/aws/aws-sdk-go/aws/awsutil" + sdkacctest "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/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" + tfoam "github.com/hashicorp/terraform-provider-aws/internal/service/oam" + "github.com/hashicorp/terraform-provider-aws/names" +) + +func TestExpandResourceTypesUnitTest(t *testing.T) { + testCases := []struct { + TestName string + Input []interface{} + Expected []types.ResourceType + }{ + { + TestName: "Empty resource types", + Input: []interface{}{}, + Expected: nil, + }, + { + TestName: "Non-empty resource types", + Input: []interface{}{ + "AWS::CloudWatch::Metric", + "AWS::Logs::LogGroup", + }, + Expected: []types.ResourceType{ + types.ResourceTypeAwsCloudwatchMetric, + types.ResourceTypeAwsLogsLoggroup, + }, + }, + } + + for _, testCase := range testCases { + t.Run(testCase.TestName, func(t *testing.T) { + got := tfoam.ExpandResourceTypes(testCase.Input) + + if got == nil && testCase.Expected == nil { + return + } + if !awsutil.DeepEqual(got, testCase.Expected) { + t.Errorf("got %s, expected %s", got, testCase.Expected) + } + }) + } +} + +func TestResourceTypeValuesUnitTest(t *testing.T) { + testCases := []struct { + TestName string + Input []types.ResourceType + Expected []string + }{ + { + TestName: "Empty resource types", + Input: []types.ResourceType{}, + Expected: []string{}, + }, + { + TestName: "Non-empty resource types", + Input: []types.ResourceType{ + types.ResourceTypeAwsCloudwatchMetric, + types.ResourceTypeAwsLogsLoggroup, + }, + Expected: []string{ + "AWS::CloudWatch::Metric", + "AWS::Logs::LogGroup", + }, + }, + } + + for _, testCase := range testCases { + t.Run(testCase.TestName, func(t *testing.T) { + got := tfoam.ResourceTypeValues(testCase.Input) + + if len(got) == 0 && len(testCase.Expected) == 0 { + return + } + + if !awsutil.DeepEqual(got, testCase.Expected) { + t.Errorf("got %s, expected %s", got, testCase.Expected) + } + }) + } +} + +func TestAccObservabilityAccessManagerLink_basic(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + ctx := acctest.Context(t) + + var link oam.GetLinkOutput + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_oam_link.test" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(ctx, t) + acctest.PreCheckAlternateAccount(t) + acctest.PreCheckPartitionHasService(t, names.ObservabilityAccessManagerEndpointID) + testAccPreCheck(ctx, t) + }, + ErrorCheck: acctest.ErrorCheck(t, names.ObservabilityAccessManagerEndpointID), + ProtoV5ProviderFactories: acctest.ProtoV5FactoriesAlternate(ctx, t), + CheckDestroy: testAccCheckLinkDestroy, + Steps: []resource.TestStep{ + { + Config: testAccLinkConfig_basic(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckLinkExists(resourceName, &link), + acctest.MatchResourceAttrRegionalARN(resourceName, "arn", "oam", regexp.MustCompile(`link/+.`)), + resource.TestCheckResourceAttrSet(resourceName, "label"), + resource.TestCheckResourceAttr(resourceName, "label_template", "$AccountName"), + resource.TestCheckResourceAttrSet(resourceName, "link_id"), + resource.TestCheckResourceAttr(resourceName, "resource_types.#", "1"), + resource.TestCheckResourceAttr(resourceName, "resource_types.0", "AWS::CloudWatch::Metric"), + resource.TestCheckResourceAttrSet(resourceName, "sink_arn"), + resource.TestCheckResourceAttrSet(resourceName, "sink_identifier"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccObservabilityAccessManagerLink_disappears(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + ctx := acctest.Context(t) + + var link oam.GetLinkOutput + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_oam_link.test" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(ctx, t) + acctest.PreCheckAlternateAccount(t) + acctest.PreCheckPartitionHasService(t, names.ObservabilityAccessManagerEndpointID) + testAccPreCheck(ctx, t) + }, + ErrorCheck: acctest.ErrorCheck(t, names.ObservabilityAccessManagerEndpointID), + ProtoV5ProviderFactories: acctest.ProtoV5FactoriesAlternate(ctx, t), + CheckDestroy: testAccCheckLinkDestroy, + Steps: []resource.TestStep{ + { + Config: testAccLinkConfig_basic(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckLinkExists(resourceName, &link), + acctest.CheckResourceDisappears(ctx, acctest.Provider, tfoam.ResourceLink(), resourceName), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +func TestAccObservabilityAccessManagerLink_update(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + ctx := acctest.Context(t) + + var link oam.GetLinkOutput + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_oam_link.test" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(ctx, t) + acctest.PreCheckAlternateAccount(t) + acctest.PreCheckPartitionHasService(t, names.ObservabilityAccessManagerEndpointID) + testAccPreCheck(ctx, t) + }, + ErrorCheck: acctest.ErrorCheck(t, names.ObservabilityAccessManagerEndpointID), + ProtoV5ProviderFactories: acctest.ProtoV5FactoriesAlternate(ctx, t), + CheckDestroy: testAccCheckLinkDestroy, + Steps: []resource.TestStep{ + { + Config: testAccLinkConfig_basic(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckLinkExists(resourceName, &link), + acctest.MatchResourceAttrRegionalARN(resourceName, "arn", "oam", regexp.MustCompile(`link/+.`)), + resource.TestCheckResourceAttrSet(resourceName, "label"), + resource.TestCheckResourceAttr(resourceName, "label_template", "$AccountName"), + resource.TestCheckResourceAttrSet(resourceName, "link_id"), + resource.TestCheckResourceAttr(resourceName, "resource_types.#", "1"), + resource.TestCheckResourceAttr(resourceName, "resource_types.0", "AWS::CloudWatch::Metric"), + resource.TestCheckResourceAttrSet(resourceName, "sink_arn"), + resource.TestCheckResourceAttrSet(resourceName, "sink_identifier"), + ), + }, + { + Config: testAccLinkConfig_update(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckLinkExists(resourceName, &link), + acctest.MatchResourceAttrRegionalARN(resourceName, "arn", "oam", regexp.MustCompile(`link/+.`)), + resource.TestCheckResourceAttrSet(resourceName, "label"), + resource.TestCheckResourceAttr(resourceName, "label_template", "$AccountName"), + resource.TestCheckResourceAttrSet(resourceName, "link_id"), + resource.TestCheckResourceAttr(resourceName, "resource_types.#", "2"), + resource.TestCheckResourceAttr(resourceName, "resource_types.0", "AWS::CloudWatch::Metric"), + resource.TestCheckResourceAttr(resourceName, "resource_types.1", "AWS::Logs::LogGroup"), + resource.TestCheckResourceAttrSet(resourceName, "sink_arn"), + resource.TestCheckResourceAttrSet(resourceName, "sink_identifier"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccObservabilityAccessManagerLink_tags(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + ctx := acctest.Context(t) + + var link oam.GetLinkOutput + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_oam_link.test" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(ctx, t) + acctest.PreCheckAlternateAccount(t) + acctest.PreCheckPartitionHasService(t, names.ObservabilityAccessManagerEndpointID) + testAccPreCheck(ctx, t) + }, + ErrorCheck: acctest.ErrorCheck(t, names.ObservabilityAccessManagerEndpointID), + ProtoV5ProviderFactories: acctest.ProtoV5FactoriesAlternate(ctx, t), + CheckDestroy: testAccCheckLinkDestroy, + Steps: []resource.TestStep{ + { + Config: testAccLinkConfig_tags1(rName, "key1", "value1"), + Check: resource.ComposeTestCheckFunc( + testAccCheckLinkExists(resourceName, &link), + resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), + resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1"), + ), + }, + { + Config: testAccLinkConfig_tags2(rName, "key1", "value1updated", "key2", "value2"), + Check: resource.ComposeTestCheckFunc( + testAccCheckLinkExists(resourceName, &link), + resource.TestCheckResourceAttr(resourceName, "tags.%", "2"), + resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1updated"), + resource.TestCheckResourceAttr(resourceName, "tags.key2", "value2"), + ), + }, + { + Config: testAccLinkConfig_tags1(rName, "key2", "value2"), + Check: resource.ComposeTestCheckFunc( + testAccCheckLinkExists(resourceName, &link), + resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), + resource.TestCheckResourceAttr(resourceName, "tags.key2", "value2"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +//func TestAccObservabilityAccessManagerLink_tags(t *testing.T) { +// if testing.Short() { +// t.Skip("skipping long-running test in short mode") +// } +// ctx := acctest.Context(t) +// +// var link oam.GetLinkOutput +// rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) +// resourceName := "aws_oam_link.test" +// +// resource.Test(t, resource.TestCase{ +// PreCheck: func() { +// acctest.PreCheck(ctx, t) +// acctest.PreCheckAlternateAccount(t) +// acctest.PreCheckPartitionHasService(t, names.ObservabilityAccessManagerEndpointID) +// testAccPreCheck(ctx, t) +// }, +// ErrorCheck: acctest.ErrorCheck(t, names.ObservabilityAccessManagerEndpointID), +// ProtoV5ProviderFactories: acctest.ProtoV5FactoriesAlternate(ctx, t), +// CheckDestroy: testAccCheckLinkDestroy, +// Steps: []resource.TestStep{ +// { +// Config: testAccLinkConfig_tags1(rName, "key1", "value1"), +// Check: resource.ComposeTestCheckFunc( +// testAccCheckLinkExists(resourceName, &link), +// resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), +// resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1"), +// ), +// }, +// { +// Config: testAccLinkConfig_tags2(rName, "key1", "value1updated", "key2", "value2"), +// Check: resource.ComposeTestCheckFunc( +// testAccCheckLinkExists(resourceName, &link), +// resource.TestCheckResourceAttr(resourceName, "tags.%", "2"), +// resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1updated"), +// resource.TestCheckResourceAttr(resourceName, "tags.key2", "value2"), +// ), +// }, +// { +// Config: testAccLinkConfig_tags1(rName, "key2", "value2"), +// Check: resource.ComposeTestCheckFunc( +// testAccCheckLinkExists(resourceName, &link), +// resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), +// resource.TestCheckResourceAttr(resourceName, "tags.key2", "value2"), +// ), +// }, +// { +// ResourceName: resourceName, +// ImportState: true, +// ImportStateVerify: true, +// }, +// }, +// } + +func testAccCheckLinkDestroy(s *terraform.State) error { + conn := acctest.Provider.Meta().(*conns.AWSClient).ObservabilityAccessManagerClient() + ctx := context.Background() + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_oam_link" { + continue + } + + input := &oam.GetLinkInput{ + Identifier: aws.String(rs.Primary.ID), + } + _, err := conn.GetLink(ctx, input) + if err != nil { + var nfe *types.ResourceNotFoundException + if errors.As(err, &nfe) { + return nil + } + return err + } + + return create.Error(names.ObservabilityAccessManager, create.ErrActionCheckingDestroyed, tfoam.ResNameLink, rs.Primary.ID, errors.New("not destroyed")) + } + + return nil +} + +func testAccCheckLinkExists(name string, link *oam.GetLinkOutput) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[name] + if !ok { + return create.Error(names.ObservabilityAccessManager, create.ErrActionCheckingExistence, tfoam.ResNameLink, name, errors.New("not found")) + } + + if rs.Primary.ID == "" { + return create.Error(names.ObservabilityAccessManager, create.ErrActionCheckingExistence, tfoam.ResNameLink, name, errors.New("not set")) + } + + conn := acctest.Provider.Meta().(*conns.AWSClient).ObservabilityAccessManagerClient() + ctx := context.Background() + resp, err := conn.GetLink(ctx, &oam.GetLinkInput{ + Identifier: aws.String(rs.Primary.ID), + }) + + if err != nil { + return create.Error(names.ObservabilityAccessManager, create.ErrActionCheckingExistence, tfoam.ResNameLink, rs.Primary.ID, err) + } + + *link = *resp + + return nil + } +} + +func testAccLinkConfig_basic(rName string) string { + return acctest.ConfigCompose( + acctest.ConfigAlternateAccountProvider(), + fmt.Sprintf(` +data "aws_caller_identity" "source" {} +data "aws_partition" "source" {} + +data "aws_caller_identity" "monitoring" { + provider = "awsalternate" +} +data "aws_partition" "monitoring" { + provider = "awsalternate" +} + +resource "aws_oam_sink" "test" { + provider = "awsalternate" + + name = %[1]q +} + +resource "aws_oam_sink_policy" "test" { + provider = "awsalternate" + + sink_identifier = aws_oam_sink.test.id + policy = jsonencode({ + Version = "2012-10-17" + Statement = [ + { + Action = ["oam:CreateLink", "oam:UpdateLink"] + Effect = "Allow" + Resource = "*" + Principal = { + "AWS" = "arn:${data.aws_partition.source.partition}:iam::${data.aws_caller_identity.source.account_id}:root" + } + Condition = { + "ForAnyValue:StringEquals" = { + "oam:ResourceTypes" = ["AWS::CloudWatch::Metric", "AWS::Logs::LogGroup"] + } + } + } + ] + }) +} + +resource "aws_oam_link" "test" { + label_template = "$AccountName" + resource_types = ["AWS::CloudWatch::Metric"] + sink_identifier = aws_oam_sink.test.id +} +`, rName)) +} + +func testAccLinkConfig_update(rName string) string { + return acctest.ConfigCompose( + acctest.ConfigAlternateAccountProvider(), + fmt.Sprintf(` +data "aws_caller_identity" "source" {} +data "aws_partition" "source" {} + +data "aws_caller_identity" "monitoring" { + provider = "awsalternate" +} +data "aws_partition" "monitoring" { + provider = "awsalternate" +} + +resource "aws_oam_sink" "test" { + provider = "awsalternate" + + name = %[1]q +} + +resource "aws_oam_sink_policy" "test" { + provider = "awsalternate" + + sink_identifier = aws_oam_sink.test.id + policy = jsonencode({ + Version = "2012-10-17" + Statement = [ + { + Action = ["oam:CreateLink", "oam:UpdateLink"] + Effect = "Allow" + Resource = "*" + Principal = { + "AWS" = "arn:${data.aws_partition.source.partition}:iam::${data.aws_caller_identity.source.account_id}:root" + } + Condition = { + "ForAnyValue:StringEquals" = { + "oam:ResourceTypes" = ["AWS::CloudWatch::Metric", "AWS::Logs::LogGroup"] + } + } + } + ] + }) +} + +resource "aws_oam_link" "test" { + label_template = "$AccountName" + resource_types = ["AWS::CloudWatch::Metric", "AWS::Logs::LogGroup"] + sink_identifier = aws_oam_sink.test.id +} +`, rName)) +} + +func testAccLinkConfig_tags1(rName, tag1Key, tag1Value string) string { + return acctest.ConfigCompose( + acctest.ConfigAlternateAccountProvider(), + fmt.Sprintf(` +data "aws_caller_identity" "source" {} +data "aws_partition" "source" {} + +data "aws_caller_identity" "monitoring" { + provider = "awsalternate" +} +data "aws_partition" "monitoring" { + provider = "awsalternate" +} + +resource "aws_oam_sink" "test" { + provider = "awsalternate" + + name = %[1]q +} + +resource "aws_oam_sink_policy" "test" { + provider = "awsalternate" + + sink_identifier = aws_oam_sink.test.id + policy = jsonencode({ + Version = "2012-10-17" + Statement = [ + { + Action = ["oam:CreateLink", "oam:UpdateLink"] + Effect = "Allow" + Resource = "*" + Principal = { + "AWS" = "arn:${data.aws_partition.source.partition}:iam::${data.aws_caller_identity.source.account_id}:root" + } + Condition = { + "ForAnyValue:StringEquals" = { + "oam:ResourceTypes" = ["AWS::CloudWatch::Metric", "AWS::Logs::LogGroup"] + } + } + } + ] + }) +} + +resource "aws_oam_link" "test" { + label_template = "$AccountName" + resource_types = ["AWS::CloudWatch::Metric"] + sink_identifier = aws_oam_sink.test.id + tags = { + %[2]q = %[3]q + } +} +`, rName, tag1Key, tag1Value)) +} + +func testAccLinkConfig_tags2(rName, tag1Key, tag1Value, tag2Key, tag2Value string) string { + return acctest.ConfigCompose( + acctest.ConfigAlternateAccountProvider(), + fmt.Sprintf(` +data "aws_caller_identity" "source" {} +data "aws_partition" "source" {} + +data "aws_caller_identity" "monitoring" { + provider = "awsalternate" +} +data "aws_partition" "monitoring" { + provider = "awsalternate" +} + +resource "aws_oam_sink" "test" { + provider = "awsalternate" + + name = %[1]q +} + +resource "aws_oam_sink_policy" "test" { + provider = "awsalternate" + + sink_identifier = aws_oam_sink.test.id + policy = jsonencode({ + Version = "2012-10-17" + Statement = [ + { + Action = ["oam:CreateLink", "oam:UpdateLink"] + Effect = "Allow" + Resource = "*" + Principal = { + "AWS" = "arn:${data.aws_partition.source.partition}:iam::${data.aws_caller_identity.source.account_id}:root" + } + Condition = { + "ForAnyValue:StringEquals" = { + "oam:ResourceTypes" = ["AWS::CloudWatch::Metric", "AWS::Logs::LogGroup"] + } + } + } + ] + }) +} + +resource "aws_oam_link" "test" { + label_template = "$AccountName" + resource_types = ["AWS::CloudWatch::Metric"] + sink_identifier = aws_oam_sink.test.id + + tags = { + %[2]q = %[3]q + %[4]q = %[5]q + } +} +`, rName, tag1Key, tag1Value, tag2Key, tag2Value)) +} diff --git a/internal/service/oam/service_package_gen.go b/internal/service/oam/service_package_gen.go index 0e2d896ebe7c..7b4750874ac7 100644 --- a/internal/service/oam/service_package_gen.go +++ b/internal/service/oam/service_package_gen.go @@ -25,6 +25,10 @@ func (p *servicePackage) SDKDataSources(ctx context.Context) []*types.ServicePac func (p *servicePackage) SDKResources(ctx context.Context) []*types.ServicePackageSDKResource { return []*types.ServicePackageSDKResource{ + { + Factory: ResourceLink, + TypeName: "aws_oam_link", + }, { Factory: ResourceSink, TypeName: "aws_oam_sink", diff --git a/website/docs/r/oam_link.html.markdown b/website/docs/r/oam_link.html.markdown new file mode 100644 index 000000000000..549bd4a93788 --- /dev/null +++ b/website/docs/r/oam_link.html.markdown @@ -0,0 +1,64 @@ +--- +subcategory: "CloudWatch Observability Access Manager" +layout: "aws" +page_title: "AWS: aws_oam_link" +description: |- + Terraform resource for managing an AWS CloudWatch Observability Access Manager Link. +--- + +# Resource: aws_oam_link + +Terraform resource for managing an AWS CloudWatch Observability Access Manager Link. + +## Example Usage + +### Basic Usage + +```terraform +resource "aws_oam_link" "example" { + label_template = "$AccountName" + resource_types = ["AWS::CloudWatch::Metric"] + sink_identifier = aws_oam_sink.test.id + tags = { + Env = "prod" + } +} +``` + +## Argument Reference + +The following arguments are required: + +* `label_template` - (Required) Human-readable name to use to identify this source account when you are viewing data from it in the monitoring account. +* `resource_types` - (Required) Types of data that the source account shares with the monitoring account. +* `sink_identifier` - (Required) Identifier of the sink to use to create this link. + +The following arguments are optional: + +* `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 + +In addition to all arguments above, the following attributes are exported: + +* `arn` - ARN of the link. +* `label` - Label that is assigned to this link. +* `link_id` - ID string that AWS generated as part of the link ARN. +* `sink_arn` - ARN of the sink that is used for this link. +* + +## Timeouts + +[Configuration options](https://developer.hashicorp.com/terraform/language/resources/syntax#operation-timeouts): + +* `create` - (Default `1m`) +* `update` - (Default `1m`) +* `delete` - (Default `1m`) + +## Import + +CloudWatch Observability Access Manager Link can be imported using the `arn`, e.g., + +``` +$ terraform import aws_oam_link.example arn:aws:oam:us-west-2:123456789012:link/link-id +``` From 85091c3a86377e0d70584a0597d67a9609f8dd41 Mon Sep 17 00:00:00 2001 From: Roy Rajan Date: Mon, 20 Mar 2023 00:11:14 +0000 Subject: [PATCH 02/10] Fix golangci-lint Checks --- .changelog/30125.txt | 3 +++ internal/service/oam/link_test.go | 4 ++++ website/docs/r/oam_link.html.markdown | 1 - 3 files changed, 7 insertions(+), 1 deletion(-) create mode 100644 .changelog/30125.txt diff --git a/.changelog/30125.txt b/.changelog/30125.txt new file mode 100644 index 000000000000..8f8409489a53 --- /dev/null +++ b/.changelog/30125.txt @@ -0,0 +1,3 @@ +```release-note:new-resource +aws_oam_link +``` \ No newline at end of file diff --git a/internal/service/oam/link_test.go b/internal/service/oam/link_test.go index 60265502521a..76a964e712de 100644 --- a/internal/service/oam/link_test.go +++ b/internal/service/oam/link_test.go @@ -47,6 +47,8 @@ func TestExpandResourceTypesUnitTest(t *testing.T) { for _, testCase := range testCases { t.Run(testCase.TestName, func(t *testing.T) { + t.Parallel() + got := tfoam.ExpandResourceTypes(testCase.Input) if got == nil && testCase.Expected == nil { @@ -85,6 +87,8 @@ func TestResourceTypeValuesUnitTest(t *testing.T) { for _, testCase := range testCases { t.Run(testCase.TestName, func(t *testing.T) { + t.Parallel() + got := tfoam.ResourceTypeValues(testCase.Input) if len(got) == 0 && len(testCase.Expected) == 0 { diff --git a/website/docs/r/oam_link.html.markdown b/website/docs/r/oam_link.html.markdown index 549bd4a93788..8486e15fd618 100644 --- a/website/docs/r/oam_link.html.markdown +++ b/website/docs/r/oam_link.html.markdown @@ -45,7 +45,6 @@ In addition to all arguments above, the following attributes are exported: * `label` - Label that is assigned to this link. * `link_id` - ID string that AWS generated as part of the link ARN. * `sink_arn` - ARN of the sink that is used for this link. -* ## Timeouts From fabc0d7ce7c81874ec92558038f340258764c381 Mon Sep 17 00:00:00 2001 From: Roy Rajan Date: Mon, 20 Mar 2023 00:56:30 +0000 Subject: [PATCH 03/10] Fixing more golangci-lint Checks --- internal/service/oam/link_test.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/internal/service/oam/link_test.go b/internal/service/oam/link_test.go index 76a964e712de..70c5f50bf47d 100644 --- a/internal/service/oam/link_test.go +++ b/internal/service/oam/link_test.go @@ -22,6 +22,8 @@ import ( ) func TestExpandResourceTypesUnitTest(t *testing.T) { + t.Parallel() + testCases := []struct { TestName string Input []interface{} @@ -46,6 +48,7 @@ func TestExpandResourceTypesUnitTest(t *testing.T) { } for _, testCase := range testCases { + testCase := testCase t.Run(testCase.TestName, func(t *testing.T) { t.Parallel() @@ -62,6 +65,8 @@ func TestExpandResourceTypesUnitTest(t *testing.T) { } func TestResourceTypeValuesUnitTest(t *testing.T) { + t.Parallel() + testCases := []struct { TestName string Input []types.ResourceType @@ -86,6 +91,7 @@ func TestResourceTypeValuesUnitTest(t *testing.T) { } for _, testCase := range testCases { + testCase := testCase t.Run(testCase.TestName, func(t *testing.T) { t.Parallel() From fdc9f8958bd575141caa629eb6742d9a794b6611 Mon Sep 17 00:00:00 2001 From: Roy Rajan Date: Mon, 20 Mar 2023 09:55:13 +0000 Subject: [PATCH 04/10] Fix error message --- internal/service/oam/link.go | 2 +- internal/service/oam/link_test.go | 54 ------------------------------- 2 files changed, 1 insertion(+), 55 deletions(-) diff --git a/internal/service/oam/link.go b/internal/service/oam/link.go index a4e98b74178a..a1ee74858c52 100644 --- a/internal/service/oam/link.go +++ b/internal/service/oam/link.go @@ -184,7 +184,7 @@ func resourceLinkUpdate(ctx context.Context, d *schema.ResourceData, meta interf log.Printf("[DEBUG] Updating ObservabilityAccessManager Link Tags (%s): %#v", d.Id(), d.Get("tags_all")) oldTags, newTags := d.GetChange("tags_all") if err := UpdateTags(ctx, conn, d.Get("arn").(string), oldTags, newTags); err != nil { - return create.DiagError(names.ObservabilityAccessManager, create.ErrActionUpdating, ResNameSink, d.Id(), err) + return create.DiagError(names.ObservabilityAccessManager, create.ErrActionUpdating, ResNameLink, d.Id(), err) } } diff --git a/internal/service/oam/link_test.go b/internal/service/oam/link_test.go index 70c5f50bf47d..8f1eb5bae74c 100644 --- a/internal/service/oam/link_test.go +++ b/internal/service/oam/link_test.go @@ -299,60 +299,6 @@ func TestAccObservabilityAccessManagerLink_tags(t *testing.T) { }) } -//func TestAccObservabilityAccessManagerLink_tags(t *testing.T) { -// if testing.Short() { -// t.Skip("skipping long-running test in short mode") -// } -// ctx := acctest.Context(t) -// -// var link oam.GetLinkOutput -// rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) -// resourceName := "aws_oam_link.test" -// -// resource.Test(t, resource.TestCase{ -// PreCheck: func() { -// acctest.PreCheck(ctx, t) -// acctest.PreCheckAlternateAccount(t) -// acctest.PreCheckPartitionHasService(t, names.ObservabilityAccessManagerEndpointID) -// testAccPreCheck(ctx, t) -// }, -// ErrorCheck: acctest.ErrorCheck(t, names.ObservabilityAccessManagerEndpointID), -// ProtoV5ProviderFactories: acctest.ProtoV5FactoriesAlternate(ctx, t), -// CheckDestroy: testAccCheckLinkDestroy, -// Steps: []resource.TestStep{ -// { -// Config: testAccLinkConfig_tags1(rName, "key1", "value1"), -// Check: resource.ComposeTestCheckFunc( -// testAccCheckLinkExists(resourceName, &link), -// resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), -// resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1"), -// ), -// }, -// { -// Config: testAccLinkConfig_tags2(rName, "key1", "value1updated", "key2", "value2"), -// Check: resource.ComposeTestCheckFunc( -// testAccCheckLinkExists(resourceName, &link), -// resource.TestCheckResourceAttr(resourceName, "tags.%", "2"), -// resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1updated"), -// resource.TestCheckResourceAttr(resourceName, "tags.key2", "value2"), -// ), -// }, -// { -// Config: testAccLinkConfig_tags1(rName, "key2", "value2"), -// Check: resource.ComposeTestCheckFunc( -// testAccCheckLinkExists(resourceName, &link), -// resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), -// resource.TestCheckResourceAttr(resourceName, "tags.key2", "value2"), -// ), -// }, -// { -// ResourceName: resourceName, -// ImportState: true, -// ImportStateVerify: true, -// }, -// }, -// } - func testAccCheckLinkDestroy(s *terraform.State) error { conn := acctest.Provider.Meta().(*conns.AWSClient).ObservabilityAccessManagerClient() ctx := context.Background() From 33b1c02c7d99ce352d1350063a4b0c6581152f26 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Mon, 20 Mar 2023 14:04:18 -0400 Subject: [PATCH 05/10] r/aws_oam_link: Use 'enum.Values' for validation. --- internal/service/oam/link.go | 22 +++------------- internal/service/oam/link_test.go | 44 ------------------------------- 2 files changed, 4 insertions(+), 62 deletions(-) diff --git a/internal/service/oam/link.go b/internal/service/oam/link.go index a1ee74858c52..e0822758a8b9 100644 --- a/internal/service/oam/link.go +++ b/internal/service/oam/link.go @@ -12,9 +12,9 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" "github.com/hashicorp/terraform-provider-aws/internal/conns" "github.com/hashicorp/terraform-provider-aws/internal/create" + "github.com/hashicorp/terraform-provider-aws/internal/enum" "github.com/hashicorp/terraform-provider-aws/internal/flex" tftags "github.com/hashicorp/terraform-provider-aws/internal/tags" "github.com/hashicorp/terraform-provider-aws/internal/tfresource" @@ -64,10 +64,9 @@ func ResourceLink() *schema.Resource { MinItems: 1, MaxItems: 50, Elem: &schema.Schema{ - Type: schema.TypeString, - ValidateFunc: validation.StringInSlice(ResourceTypeValues(types.ResourceType("").Values()), false), + Type: schema.TypeString, + ValidateDiagFunc: enum.Validate[types.ResourceType](), }, - Set: schema.HashString, }, "sink_arn": { Type: schema.TypeString, @@ -78,7 +77,6 @@ func ResourceLink() *schema.Resource { Required: true, ForceNew: true, }, - "tags": tftags.TagsSchema(), "tags_all": tftags.TagsSchemaComputed(), }, @@ -142,9 +140,7 @@ func resourceLinkRead(ctx context.Context, d *schema.ResourceData, meta interfac d.Set("link_id", out.Id) d.Set("resource_types", flex.FlattenStringValueList(out.ResourceTypes)) d.Set("sink_arn", out.SinkArn) - if _, ok := d.GetOk("sink_identifier"); !ok { - d.Set("sink_identifier", out.SinkArn) - } + d.Set("sink_identifier", out.SinkArn) tags, err := ListTags(ctx, conn, d.Id()) if err != nil { @@ -262,13 +258,3 @@ func ExpandResourceTypes(resourceTypeList []interface{}) []types.ResourceType { return resourceTypes } - -func ResourceTypeValues(resourceTypes []types.ResourceType) []string { - var out []string - - for _, v := range resourceTypes { - out = append(out, string(v)) - } - - return out -} diff --git a/internal/service/oam/link_test.go b/internal/service/oam/link_test.go index 8f1eb5bae74c..d1b2dee73028 100644 --- a/internal/service/oam/link_test.go +++ b/internal/service/oam/link_test.go @@ -64,50 +64,6 @@ func TestExpandResourceTypesUnitTest(t *testing.T) { } } -func TestResourceTypeValuesUnitTest(t *testing.T) { - t.Parallel() - - testCases := []struct { - TestName string - Input []types.ResourceType - Expected []string - }{ - { - TestName: "Empty resource types", - Input: []types.ResourceType{}, - Expected: []string{}, - }, - { - TestName: "Non-empty resource types", - Input: []types.ResourceType{ - types.ResourceTypeAwsCloudwatchMetric, - types.ResourceTypeAwsLogsLoggroup, - }, - Expected: []string{ - "AWS::CloudWatch::Metric", - "AWS::Logs::LogGroup", - }, - }, - } - - for _, testCase := range testCases { - testCase := testCase - t.Run(testCase.TestName, func(t *testing.T) { - t.Parallel() - - got := tfoam.ResourceTypeValues(testCase.Input) - - if len(got) == 0 && len(testCase.Expected) == 0 { - return - } - - if !awsutil.DeepEqual(got, testCase.Expected) { - t.Errorf("got %s, expected %s", got, testCase.Expected) - } - }) - } -} - func TestAccObservabilityAccessManagerLink_basic(t *testing.T) { if testing.Short() { t.Skip("skipping long-running test in short mode") From d02f09639196ae15aa133920c994fa55076943ac Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Mon, 20 Mar 2023 14:25:57 -0400 Subject: [PATCH 06/10] Use 'go-cmp' in unit tests. --- internal/flex/flex_test.go | 58 +++++++++++++++----------------------- 1 file changed, 23 insertions(+), 35 deletions(-) diff --git a/internal/flex/flex_test.go b/internal/flex/flex_test.go index 6a33994c431e..73fd11af9fe9 100644 --- a/internal/flex/flex_test.go +++ b/internal/flex/flex_test.go @@ -1,66 +1,57 @@ package flex import ( - "reflect" "strings" "testing" "github.com/aws/aws-sdk-go/aws" + "github.com/google/go-cmp/cmp" ) func TestExpandStringList(t *testing.T) { t.Parallel() - expanded := []interface{}{"us-east-1a", "us-east-1b"} //lintignore:AWSAT003 - stringList := ExpandStringList(expanded) - expected := []*string{ - aws.String("us-east-1a"), //lintignore:AWSAT003 - aws.String("us-east-1b"), //lintignore:AWSAT003 + configured := []interface{}{"abc", "xyz123"} + got := ExpandStringList(configured) + want := []*string{ + aws.String("abc"), + aws.String("xyz123"), } - if !reflect.DeepEqual(stringList, expected) { - t.Fatalf( - "Got:\n\n%#v\n\nExpected:\n\n%#v\n", - stringList, - expected) + if !cmp.Equal(got, want) { + t.Errorf("expanded = %v, want = %v", got, want) } } func TestExpandStringListEmptyItems(t *testing.T) { t.Parallel() - expanded := []interface{}{"foo", "bar", "", "baz"} - stringList := ExpandStringList(expanded) - expected := []*string{ + configured := []interface{}{"foo", "bar", "", "baz"} + got := ExpandStringList(configured) + want := []*string{ aws.String("foo"), aws.String("bar"), aws.String("baz"), } - if !reflect.DeepEqual(stringList, expected) { - t.Fatalf( - "Got:\n\n%#v\n\nExpected:\n\n%#v\n", - stringList, - expected) + if !cmp.Equal(got, want) { + t.Errorf("expanded = %v, want = %v", got, want) } } func TestExpandResourceId(t *testing.T) { t.Parallel() - resourceId := "foo,bar,baz" - expandedId, _ := ExpandResourceId(resourceId, 3) - expected := []string{ + id := "foo,bar,baz" + got, _ := ExpandResourceId(id, 3) + want := []string{ "foo", "bar", "baz", } - if !reflect.DeepEqual(expandedId, expected) { - t.Fatalf( - "Got:\n\n%#v\n\nExpected:\n\n%#v\n", - expandedId, - expected) + if !cmp.Equal(got, want) { + t.Errorf("expanded = %v, want = %v", got, want) } } @@ -101,14 +92,11 @@ func TestFlattenResourceId(t *testing.T) { t.Parallel() idParts := []string{"foo", "bar", "baz"} - flattenedId, _ := FlattenResourceId(idParts, 3) - expected := "foo,bar,baz" - - if !reflect.DeepEqual(flattenedId, expected) { - t.Fatalf( - "Got:\n\n%#v\n\nExpected:\n\n%#v\n", - flattenedId, - expected) + got, _ := FlattenResourceId(idParts, 3) + want := "foo,bar,baz" + + if !cmp.Equal(got, want) { + t.Errorf("flattened = %v, want = %v", got, want) } } From a893bad3e8ada23e8ec51df136260e464b8ca3f1 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Mon, 20 Mar 2023 14:29:57 -0400 Subject: [PATCH 07/10] Test 'flex.ExpandStringValueList'. --- internal/flex/flex_test.go | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/internal/flex/flex_test.go b/internal/flex/flex_test.go index 73fd11af9fe9..055c3d0435ea 100644 --- a/internal/flex/flex_test.go +++ b/internal/flex/flex_test.go @@ -39,6 +39,30 @@ func TestExpandStringListEmptyItems(t *testing.T) { } } +func TestExpandStringValueList(t *testing.T) { + t.Parallel() + + configured := []interface{}{"abc", "xyz123"} + got := ExpandStringValueList(configured) + want := []string{"abc", "xyz123"} + + if !cmp.Equal(got, want) { + t.Errorf("expanded = %v, want = %v", got, want) + } +} + +func TestExpandStringValueListEmptyItems(t *testing.T) { + t.Parallel() + + configured := []interface{}{"foo", "bar", "", "baz"} + got := ExpandStringValueList(configured) + want := []string{"foo", "bar", "baz"} + + if !cmp.Equal(got, want) { + t.Errorf("expanded = %v, want = %v", got, want) + } +} + func TestExpandResourceId(t *testing.T) { t.Parallel() From 42ee913d2b7d886190a640a754e061915d94a199 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Mon, 20 Mar 2023 14:42:47 -0400 Subject: [PATCH 08/10] Add 'flex.ExpandStringyValueList' and 'flex.ExpandStringyValueSet'. --- internal/flex/flex.go | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/internal/flex/flex.go b/internal/flex/flex.go index e8addc64bafe..abf300fd9665 100644 --- a/internal/flex/flex.go +++ b/internal/flex/flex.go @@ -30,11 +30,14 @@ func ExpandStringList(configured []interface{}) []*string { // ExpandStringValueList takes the result of flatmap.Expand for an array of strings // and returns a []string func ExpandStringValueList(configured []interface{}) []string { - vs := make([]string, 0, len(configured)) + return ExpandStringyValueList[string](configured) +} + +func ExpandStringyValueList[E ~string](configured []any) []E { + vs := make([]E, 0, len(configured)) for _, v := range configured { - val, ok := v.(string) - if ok && val != "" { - vs = append(vs, v.(string)) + if val, ok := v.(string); ok && val != "" { + vs = append(vs, E(val)) } } return vs @@ -116,6 +119,10 @@ func ExpandStringValueSet(configured *schema.Set) []string { return ExpandStringValueList(configured.List()) // nosemgrep:ci.helper-schema-Set-extraneous-ExpandStringList-with-List } +func ExpandStringyValueSet[E ~string](configured *schema.Set) []E { + return ExpandStringyValueList[E](configured.List()) +} + func FlattenStringSet(list []*string) *schema.Set { return schema.NewSet(schema.HashString, FlattenStringList(list)) // nosemgrep:ci.helper-schema-Set-extraneous-NewSet-with-FlattenStringList } From 0d2f897cda062cdc986bca17ccd3cdbf92fd92ca Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Mon, 20 Mar 2023 14:43:45 -0400 Subject: [PATCH 09/10] r/aws_inspector2_enabler: Use 'flex.ExpandStringyValueSet'. --- internal/service/inspector2/enabler.go | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/internal/service/inspector2/enabler.go b/internal/service/inspector2/enabler.go index f9a999bc9d12..bcb84a88e039 100644 --- a/internal/service/inspector2/enabler.go +++ b/internal/service/inspector2/enabler.go @@ -71,7 +71,7 @@ func resourceEnablerCreate(ctx context.Context, d *schema.ResourceData, meta int in := &inspector2.EnableInput{ AccountIds: flex.ExpandStringValueSet(d.Get("account_ids").(*schema.Set)), - ResourceTypes: expandResourceScanTypes(flex.ExpandStringValueSet(d.Get("resource_types").(*schema.Set))), + ResourceTypes: flex.ExpandStringyValueSet[types.ResourceScanType](d.Get("resource_types").(*schema.Set)), ClientToken: aws.String(resource.UniqueId()), } @@ -129,7 +129,7 @@ func resourceEnablerDelete(ctx context.Context, d *schema.ResourceData, meta int in := &inspector2.DisableInput{ AccountIds: flex.ExpandStringValueSet(d.Get("account_ids").(*schema.Set)), - ResourceTypes: expandResourceScanTypes(flex.ExpandStringValueSet(d.Get("resource_types").(*schema.Set))), + ResourceTypes: flex.ExpandStringyValueSet[types.ResourceScanType](d.Get("resource_types").(*schema.Set)), } _, err := conn.Disable(ctx, in) @@ -347,16 +347,6 @@ func compositeStatus(ec2, ecr bool, ec2Status, ecrStatus string) string { return string(types.StatusSuspended) } -func expandResourceScanTypes(s []string) []types.ResourceScanType { - vs := make([]types.ResourceScanType, 0, len(s)) - for _, v := range s { - if v != "" { - vs = append(vs, types.ResourceScanType(v)) - } - } - return vs -} - func EnablerID(accountIDs []string, types []string) string { return fmt.Sprintf("%s-%s", strings.Join(accountIDs, ":"), strings.Join(types, ":")) } From 25e642fb8592f1f85295f3d0fb62ba64b5908419 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Mon, 20 Mar 2023 14:47:43 -0400 Subject: [PATCH 10/10] r/aws_oam_link: Use 'flex.ExpandStringyValueSet'. --- internal/service/oam/link.go | 23 ++------------ internal/service/oam/link_test.go | 52 +++---------------------------- 2 files changed, 6 insertions(+), 69 deletions(-) diff --git a/internal/service/oam/link.go b/internal/service/oam/link.go index e0822758a8b9..8610082e3fa6 100644 --- a/internal/service/oam/link.go +++ b/internal/service/oam/link.go @@ -94,7 +94,7 @@ func resourceLinkCreate(ctx context.Context, d *schema.ResourceData, meta interf in := &oam.CreateLinkInput{ LabelTemplate: aws.String(d.Get("label_template").(string)), - ResourceTypes: ExpandResourceTypes(d.Get("resource_types").(*schema.Set).List()), + ResourceTypes: flex.ExpandStringyValueSet[types.ResourceType](d.Get("resource_types").(*schema.Set)), SinkIdentifier: aws.String(d.Get("sink_identifier").(string)), } @@ -172,7 +172,7 @@ func resourceLinkUpdate(ctx context.Context, d *schema.ResourceData, meta interf } if d.HasChanges("resource_types") { - in.ResourceTypes = ExpandResourceTypes(d.Get("resource_types").(*schema.Set).List()) + in.ResourceTypes = flex.ExpandStringyValueSet[types.ResourceType](d.Get("resource_types").(*schema.Set)) update = true } @@ -239,22 +239,3 @@ func findLinkByID(ctx context.Context, conn *oam.Client, id string) (*oam.GetLin return out, nil } - -func ExpandResourceTypes(resourceTypeList []interface{}) []types.ResourceType { - if len(resourceTypeList) == 0 { - return nil - } - - var resourceTypes []types.ResourceType - - for _, resourceTypeString := range resourceTypeList { - if resourceTypeString == nil { - continue - } - - resourceType := types.ResourceType(resourceTypeString.(string)) - resourceTypes = append(resourceTypes, resourceType) - } - - return resourceTypes -} diff --git a/internal/service/oam/link_test.go b/internal/service/oam/link_test.go index d1b2dee73028..5316fea446d6 100644 --- a/internal/service/oam/link_test.go +++ b/internal/service/oam/link_test.go @@ -10,7 +10,6 @@ import ( "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/service/oam" "github.com/aws/aws-sdk-go-v2/service/oam/types" - "github.com/aws/aws-sdk-go/aws/awsutil" sdkacctest "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" @@ -21,54 +20,11 @@ import ( "github.com/hashicorp/terraform-provider-aws/names" ) -func TestExpandResourceTypesUnitTest(t *testing.T) { - t.Parallel() - - testCases := []struct { - TestName string - Input []interface{} - Expected []types.ResourceType - }{ - { - TestName: "Empty resource types", - Input: []interface{}{}, - Expected: nil, - }, - { - TestName: "Non-empty resource types", - Input: []interface{}{ - "AWS::CloudWatch::Metric", - "AWS::Logs::LogGroup", - }, - Expected: []types.ResourceType{ - types.ResourceTypeAwsCloudwatchMetric, - types.ResourceTypeAwsLogsLoggroup, - }, - }, - } - - for _, testCase := range testCases { - testCase := testCase - t.Run(testCase.TestName, func(t *testing.T) { - t.Parallel() - - got := tfoam.ExpandResourceTypes(testCase.Input) - - if got == nil && testCase.Expected == nil { - return - } - if !awsutil.DeepEqual(got, testCase.Expected) { - t.Errorf("got %s, expected %s", got, testCase.Expected) - } - }) - } -} - func TestAccObservabilityAccessManagerLink_basic(t *testing.T) { + ctx := acctest.Context(t) if testing.Short() { t.Skip("skipping long-running test in short mode") } - ctx := acctest.Context(t) var link oam.GetLinkOutput rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) @@ -109,10 +65,10 @@ func TestAccObservabilityAccessManagerLink_basic(t *testing.T) { } func TestAccObservabilityAccessManagerLink_disappears(t *testing.T) { + ctx := acctest.Context(t) if testing.Short() { t.Skip("skipping long-running test in short mode") } - ctx := acctest.Context(t) var link oam.GetLinkOutput rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) @@ -142,10 +98,10 @@ func TestAccObservabilityAccessManagerLink_disappears(t *testing.T) { } func TestAccObservabilityAccessManagerLink_update(t *testing.T) { + ctx := acctest.Context(t) if testing.Short() { t.Skip("skipping long-running test in short mode") } - ctx := acctest.Context(t) var link oam.GetLinkOutput rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) @@ -201,10 +157,10 @@ func TestAccObservabilityAccessManagerLink_update(t *testing.T) { } func TestAccObservabilityAccessManagerLink_tags(t *testing.T) { + ctx := acctest.Context(t) if testing.Short() { t.Skip("skipping long-running test in short mode") } - ctx := acctest.Context(t) var link oam.GetLinkOutput rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix)