From 74094821105ba245663d83a2765bcc378c3a6b86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgar=20L=C3=B3pez?= Date: Fri, 3 Dec 2021 14:20:13 -0700 Subject: [PATCH 01/10] feat: added new resource and docs for detective graph --- internal/provider/provider.go | 3 + internal/service/detective/find.go | 52 ++++++ internal/service/detective/generate.go | 4 + internal/service/detective/graph.go | 141 ++++++++++++++ internal/service/detective/graph_test.go | 184 +++++++++++++++++++ internal/service/detective/tags_gen.go | 19 ++ internal/service/detective/wait.go | 8 + website/docs/r/detective_graph.html.markdown | 39 ++++ 8 files changed, 450 insertions(+) create mode 100644 internal/service/detective/find.go create mode 100644 internal/service/detective/generate.go create mode 100644 internal/service/detective/graph.go create mode 100644 internal/service/detective/graph_test.go create mode 100644 internal/service/detective/tags_gen.go create mode 100644 internal/service/detective/wait.go create mode 100644 website/docs/r/detective_graph.html.markdown diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 7240ab1490b..d4cae9563ca 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -51,6 +51,7 @@ import ( "github.com/hashicorp/terraform-provider-aws/internal/service/datapipeline" "github.com/hashicorp/terraform-provider-aws/internal/service/datasync" "github.com/hashicorp/terraform-provider-aws/internal/service/dax" + "github.com/hashicorp/terraform-provider-aws/internal/service/detective" "github.com/hashicorp/terraform-provider-aws/internal/service/devicefarm" "github.com/hashicorp/terraform-provider-aws/internal/service/directconnect" "github.com/hashicorp/terraform-provider-aws/internal/service/dlm" @@ -970,6 +971,8 @@ func Provider() *schema.Provider { "aws_devicefarm_project": devicefarm.ResourceProject(), + "aws_detective_graph": detective.ResourceGraph(), + "aws_dx_bgp_peer": directconnect.ResourceBGPPeer(), "aws_dx_connection": directconnect.ResourceConnection(), "aws_dx_connection_association": directconnect.ResourceConnectionAssociation(), diff --git a/internal/service/detective/find.go b/internal/service/detective/find.go new file mode 100644 index 00000000000..27a7496d118 --- /dev/null +++ b/internal/service/detective/find.go @@ -0,0 +1,52 @@ +package detective + +import ( + "context" + "fmt" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/detective" + "github.com/hashicorp/aws-sdk-go-base/tfawserr" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" +) + +func FindDetectiveGraphByArn(conn *detective.Detective, ctx context.Context, arn string) (*detective.Graph, error) { + input := &detective.ListGraphsInput{} + var result *detective.Graph + + err := conn.ListGraphsPagesWithContext(ctx, input, func(page *detective.ListGraphsOutput, lastPage bool) bool { + if page == nil { + return !lastPage + } + + for _, graph := range page.GraphList { + if graph == nil { + continue + } + + if aws.StringValue(graph.Arn) == arn { + result = graph + return false + } + } + return !lastPage + }) + if tfawserr.ErrCodeEquals(err, detective.ErrCodeResourceNotFoundException) { + return nil, &resource.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + if err != nil { + return nil, err + } + + if result == nil { + return nil, &resource.NotFoundError{ + Message: fmt.Sprintf("No detective graph with arn %q", arn), + LastRequest: input, + } + } + + return result, nil +} diff --git a/internal/service/detective/generate.go b/internal/service/detective/generate.go new file mode 100644 index 00000000000..31ba2e359e6 --- /dev/null +++ b/internal/service/detective/generate.go @@ -0,0 +1,4 @@ +//go:generate go run ../../generate/tags/main.go -ServiceTagsMap +// ONLY generate directives and package declaration! Do not add anything else to this file. + +package detective diff --git a/internal/service/detective/graph.go b/internal/service/detective/graph.go new file mode 100644 index 00000000000..9fc7f434583 --- /dev/null +++ b/internal/service/detective/graph.go @@ -0,0 +1,141 @@ +package detective + +import ( + "context" + "fmt" + "log" + "time" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/detective" + "github.com/hashicorp/aws-sdk-go-base/tfawserr" + "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-provider-aws/internal/conns" + tftags "github.com/hashicorp/terraform-provider-aws/internal/tags" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" +) + +func ResourceGraph() *schema.Resource { + return &schema.Resource{ + CreateWithoutTimeout: resourceGraphCreate, + ReadWithoutTimeout: resourceGraphRead, + DeleteWithoutTimeout: resourceGraphDelete, + Importer: &schema.ResourceImporter{ + StateContext: schema.ImportStatePassthroughContext, + }, + Schema: map[string]*schema.Schema{ + "created_time": { + Type: schema.TypeString, + Computed: true, + }, + "graph_arn": { + Type: schema.TypeString, + Computed: true, + }, + "tags": tftags.TagsSchemaForceNew(), + "tags_all": tftags.TagsSchemaComputed(), + }, + } +} + +func resourceGraphCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.AWSClient).DetectiveConn + + defaultTagsConfig := meta.(*conns.AWSClient).DefaultTagsConfig + tags := defaultTagsConfig.MergeTags(tftags.New(d.Get("tags").(map[string]interface{}))) + + input := &detective.CreateGraphInput{} + + if len(tags) > 0 { + input.Tags = Tags(tags.IgnoreAWS()) + } + + var output *detective.CreateGraphOutput + var err error + err = resource.RetryContext(ctx, DetectiveOperationTimeout, func() *resource.RetryError { + output, err = conn.CreateGraphWithContext(ctx, input) + if err != nil { + if tfawserr.ErrCodeEquals(err, detective.ErrCodeInternalServerException) { + return resource.RetryableError(err) + } + + return resource.NonRetryableError(err) + } + + return nil + }) + + if tfresource.TimedOut(err) { + output, err = conn.CreateGraphWithContext(ctx, input) + } + + if err != nil { + return diag.FromErr(fmt.Errorf("error creating detective Graph: %w", err)) + } + + d.SetId(aws.StringValue(output.GraphArn)) + + return resourceGraphRead(ctx, d, meta) +} + +func resourceGraphRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.AWSClient).DetectiveConn + + defaultTagsConfig := meta.(*conns.AWSClient).DefaultTagsConfig + ignoreTagsConfig := meta.(*conns.AWSClient).IgnoreTagsConfig + + resp, err := FindDetectiveGraphByArn(conn, ctx, d.Id()) + + if !d.IsNewResource() && tfawserr.ErrCodeEquals(err, detective.ErrCodeResourceNotFoundException) || resp == nil { + d.SetId("") + return nil + } + if err != nil { + return diag.FromErr(fmt.Errorf("error reading detective Graph (%s): %w", d.Id(), err)) + } + + d.Set("created_time", aws.TimeValue(resp.CreatedTime).Format(time.RFC3339)) + d.Set("graph_arn", aws.StringValue(resp.Arn)) + + tg, err := conn.ListTagsForResource(&detective.ListTagsForResourceInput{ + ResourceArn: resp.Arn, + }) + if err != nil { + return diag.FromErr(fmt.Errorf("error listing tags for Detective Graph (%s): %w", d.Id(), err)) + } + if tg.Tags == nil { + log.Printf("[DEBUG] Detective Graph tags (%s) not found", d.Id()) + return nil + } + tags := KeyValueTags(tg.Tags).IgnoreAWS().IgnoreConfig(ignoreTagsConfig) + + if err = d.Set("tags", tags.RemoveDefaultConfig(defaultTagsConfig).Map()); err != nil { + return diag.FromErr(fmt.Errorf("error setting `%s` for Detective Graph (%s): %w", "tags", d.Id(), err)) + } + + if err = d.Set("tags_all", tags.Map()); err != nil { + return diag.FromErr(fmt.Errorf("error setting `%s` for Detective Graph (%s): %w", "tags_all", d.Id(), err)) + } + + return nil +} + +func resourceGraphDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.AWSClient).DetectiveConn + + input := &detective.DeleteGraphInput{ + GraphArn: aws.String(d.Id()), + } + + _, err := conn.DeleteGraphWithContext(ctx, input) + if err != nil { + if tfawserr.ErrCodeEquals(err, detective.ErrCodeResourceNotFoundException) { + return nil + } + return diag.FromErr(fmt.Errorf("error deleting detective Graph (%s): %w", d.Id(), err)) + } + + return nil +} diff --git a/internal/service/detective/graph_test.go b/internal/service/detective/graph_test.go new file mode 100644 index 00000000000..a117ec8178f --- /dev/null +++ b/internal/service/detective/graph_test.go @@ -0,0 +1,184 @@ +package detective_test + +import ( + "context" + "fmt" + "testing" + + "github.com/aws/aws-sdk-go/service/detective" + "github.com/hashicorp/aws-sdk-go-base/tfawserr" + "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" + tfdetective "github.com/hashicorp/terraform-provider-aws/internal/service/detective" +) + +func TestAccAwsDetectiveGraph_basic(t *testing.T) { + var graphOutput detective.Graph + resourceName := "aws_detective_graph.test" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ProviderFactories: acctest.ProviderFactories, + CheckDestroy: testAccCheckAwsDetectiveGraphDestroy, + ErrorCheck: acctest.ErrorCheck(t, detective.EndpointsID), + Steps: []resource.TestStep{ + { + Config: testAccAwsDetectiveGraphConfigBasic(), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsDetectiveGraphExists(resourceName, &graphOutput), + acctest.CheckResourceAttrRFC3339(resourceName, "created_time"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccAwsDetectiveGraph_WithTags(t *testing.T) { + var graphOutput detective.Graph + resourceName := "aws_detective_graph.test" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ProviderFactories: acctest.ProviderFactories, + CheckDestroy: testAccCheckAwsDetectiveGraphDestroy, + ErrorCheck: acctest.ErrorCheck(t, detective.EndpointsID), + Steps: []resource.TestStep{ + { + Config: testAccAwsDetectiveGraphConfigWithTags(), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsDetectiveGraphExists(resourceName, &graphOutput), + acctest.CheckResourceAttrRFC3339(resourceName, "created_time"), + resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), + resource.TestCheckResourceAttr(resourceName, "tags.Key", "value"), + resource.TestCheckResourceAttr(resourceName, "tags_all.%", "1"), + resource.TestCheckResourceAttr(resourceName, "tags_all.Key", "value"), + ), + }, + { + Config: testAccAwsDetectiveGraphConfigWithTagsUpdate(), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsDetectiveGraphExists(resourceName, &graphOutput), + acctest.CheckResourceAttrRFC3339(resourceName, "created_time"), + resource.TestCheckResourceAttr(resourceName, "tags.%", "2"), + resource.TestCheckResourceAttr(resourceName, "tags.Key", "value"), + resource.TestCheckResourceAttr(resourceName, "tags.Key2", "value2"), + resource.TestCheckResourceAttr(resourceName, "tags_all.%", "2"), + resource.TestCheckResourceAttr(resourceName, "tags_all.Key", "value"), + resource.TestCheckResourceAttr(resourceName, "tags_all.Key2", "value2"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccAwsDetectiveGraph_disappears(t *testing.T) { + var graphOutput detective.Graph + resourceName := "aws_detective_graph.test" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ProviderFactories: acctest.ProviderFactories, + CheckDestroy: testAccCheckAwsDetectiveGraphDestroy, + ErrorCheck: acctest.ErrorCheck(t, detective.EndpointsID), + Steps: []resource.TestStep{ + { + Config: testAccAwsDetectiveGraphConfigBasic(), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsDetectiveGraphExists(resourceName, &graphOutput), + acctest.CheckResourceDisappears(acctest.Provider, tfdetective.ResourceGraph(), resourceName), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +func testAccCheckAwsDetectiveGraphDestroy(s *terraform.State) error { + conn := acctest.Provider.Meta().(*conns.AWSClient).DetectiveConn + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_detective_graph" { + continue + } + + resp, err := tfdetective.FindDetectiveGraphByArn(conn, context.Background(), rs.Primary.ID) + + if tfawserr.ErrCodeEquals(err, detective.ErrCodeResourceNotFoundException) || resp == nil { + continue + } + + if err != nil { + return err + } + + if resp != nil { + return fmt.Errorf("detective graph %q still exists", rs.Primary.ID) + } + } + + return nil + +} + +func testAccCheckAwsDetectiveGraphExists(resourceName string, graph *detective.Graph) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[resourceName] + if !ok { + return fmt.Errorf("not found: %s", resourceName) + } + + conn := acctest.Provider.Meta().(*conns.AWSClient).DetectiveConn + resp, err := tfdetective.FindDetectiveGraphByArn(conn, context.Background(), rs.Primary.ID) + + if err != nil { + return err + } + + if resp == nil { + return fmt.Errorf("detective graph %q does not exist", rs.Primary.ID) + } + + *graph = *resp + + return nil + } +} + +func testAccAwsDetectiveGraphConfigBasic() string { + return ` +resource "aws_detective_graph" "test" {} +` +} + +func testAccAwsDetectiveGraphConfigWithTags() string { + return ` +resource "aws_detective_graph" "test" { + tags = { + Key = "value" + } +} +` +} + +func testAccAwsDetectiveGraphConfigWithTagsUpdate() string { + return ` +resource "aws_detective_graph" "test" { + tags = { + Key = "value" + Key2 = "value2" + } +} +` +} diff --git a/internal/service/detective/tags_gen.go b/internal/service/detective/tags_gen.go new file mode 100644 index 00000000000..3048cf23ff1 --- /dev/null +++ b/internal/service/detective/tags_gen.go @@ -0,0 +1,19 @@ +// Code generated by internal/generate/tags/main.go; DO NOT EDIT. +package detective + +import ( + "github.com/aws/aws-sdk-go/aws" + tftags "github.com/hashicorp/terraform-provider-aws/internal/tags" +) + +// map[string]*string handling + +// Tags returns detective service tags. +func Tags(tags tftags.KeyValueTags) map[string]*string { + return aws.StringMap(tags.Map()) +} + +// KeyValueTags creates KeyValueTags from detective service tags. +func KeyValueTags(tags map[string]*string) tftags.KeyValueTags { + return tftags.New(tags) +} diff --git a/internal/service/detective/wait.go b/internal/service/detective/wait.go new file mode 100644 index 00000000000..ab5759cba06 --- /dev/null +++ b/internal/service/detective/wait.go @@ -0,0 +1,8 @@ +package detective + +import "time" + +const ( + // DetectiveOperationTimeout Maximum amount of time to wait for a detective graph to be created, deleted + DetectiveOperationTimeout = 4 * time.Minute +) diff --git a/website/docs/r/detective_graph.html.markdown b/website/docs/r/detective_graph.html.markdown new file mode 100644 index 00000000000..2f4ce14a785 --- /dev/null +++ b/website/docs/r/detective_graph.html.markdown @@ -0,0 +1,39 @@ +--- +subcategory: "Detective" +layout: "aws" +page_title: "AWS: aws_detective_graph" +description: |- + Provides a resource to manage Amazon Detective on a Graph. +--- + +# Resource: aws_detective_graph + +Provides a resource to manage an [AWS Detective Graph](https://docs.aws.amazon.com/detective/latest/APIReference/API_CreateGraph.html). + +## Example Usage + +```terraform +resource "aws_detective_graph" "example" {} +``` + +## Argument Reference + +The following arguments are optional: + +* `tags` - (Optional) A map of tags to assign to the instance. If configured with a provider [`default_tags` configuration block](/docs/providers/aws/index.html#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: + +* `id` - ARN of the Detective Graph. +* `graph_arn` - ARN of the Detective Graph. +* `created_time` - Date and time, in UTC and extended RFC 3339 format, when the Amazon Detective Graph was created. + +## Import + +`aws_detective_graph` can be imported using the arn, e.g. + +``` +$ terraform import aws_detective_graph.example arn:aws:detective:us-east-1:123456789101:graph:231684d34gh74g4bae1dbc7bd807d02d +``` \ No newline at end of file From 6770cc2a80800088c4c99203796966c8c42fd110 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgar=20L=C3=B3pez?= Date: Fri, 3 Dec 2021 14:24:24 -0700 Subject: [PATCH 02/10] feat: added changelog --- .changelog/22042.txt | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .changelog/22042.txt diff --git a/.changelog/22042.txt b/.changelog/22042.txt new file mode 100644 index 00000000000..4565457e663 --- /dev/null +++ b/.changelog/22042.txt @@ -0,0 +1,3 @@ +```release-note:new-resource +aws_detective_graph +``` \ No newline at end of file From 8751f8dc9669d5969bf26f920554befdca4e4fd3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgar=20L=C3=B3pez?= Date: Fri, 3 Dec 2021 14:29:50 -0700 Subject: [PATCH 03/10] fixes linter --- internal/service/detective/graph.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/service/detective/graph.go b/internal/service/detective/graph.go index 9fc7f434583..49611bde97c 100644 --- a/internal/service/detective/graph.go +++ b/internal/service/detective/graph.go @@ -97,7 +97,7 @@ func resourceGraphRead(ctx context.Context, d *schema.ResourceData, meta interfa } d.Set("created_time", aws.TimeValue(resp.CreatedTime).Format(time.RFC3339)) - d.Set("graph_arn", aws.StringValue(resp.Arn)) + d.Set("graph_arn", resp.Arn) tg, err := conn.ListTagsForResource(&detective.ListTagsForResourceInput{ ResourceArn: resp.Arn, From 41fecd10467109bb28316babd4f3775d3aa53dcb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgar=20L=C3=B3pez?= Date: Wed, 8 Dec 2021 13:37:31 -0700 Subject: [PATCH 04/10] updated generator to add function to update tags --- internal/service/detective/generate.go | 2 +- internal/service/detective/tags_gen.go | 39 ++++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/internal/service/detective/generate.go b/internal/service/detective/generate.go index 31ba2e359e6..f83d5dd9bbc 100644 --- a/internal/service/detective/generate.go +++ b/internal/service/detective/generate.go @@ -1,4 +1,4 @@ -//go:generate go run ../../generate/tags/main.go -ServiceTagsMap +//go:generate go run ../../generate/tags/main.go -ServiceTagsMap -UpdateTags // ONLY generate directives and package declaration! Do not add anything else to this file. package detective diff --git a/internal/service/detective/tags_gen.go b/internal/service/detective/tags_gen.go index 3048cf23ff1..5b7c663e8e6 100644 --- a/internal/service/detective/tags_gen.go +++ b/internal/service/detective/tags_gen.go @@ -2,7 +2,10 @@ package detective import ( + "fmt" + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/detective" tftags "github.com/hashicorp/terraform-provider-aws/internal/tags" ) @@ -17,3 +20,39 @@ func Tags(tags tftags.KeyValueTags) map[string]*string { func KeyValueTags(tags map[string]*string) tftags.KeyValueTags { return tftags.New(tags) } + +// UpdateTags updates detective service tags. +// The identifier is typically the Amazon Resource Name (ARN), although +// it may also be a different identifier depending on the service. +func UpdateTags(conn *detective.Detective, identifier string, oldTagsMap interface{}, newTagsMap interface{}) error { + oldTags := tftags.New(oldTagsMap) + newTags := tftags.New(newTagsMap) + + if removedTags := oldTags.Removed(newTags); len(removedTags) > 0 { + input := &detective.UntagResourceInput{ + ResourceArn: aws.String(identifier), + TagKeys: aws.StringSlice(removedTags.IgnoreAWS().Keys()), + } + + _, err := conn.UntagResource(input) + + if err != nil { + return fmt.Errorf("error untagging resource (%s): %w", identifier, err) + } + } + + if updatedTags := oldTags.Updated(newTags); len(updatedTags) > 0 { + input := &detective.TagResourceInput{ + ResourceArn: aws.String(identifier), + Tags: Tags(updatedTags.IgnoreAWS()), + } + + _, err := conn.TagResource(input) + + if err != nil { + return fmt.Errorf("error tagging resource (%s): %w", identifier, err) + } + } + + return nil +} From e3b939974b805f23114717c696d4afa89af621e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgar=20L=C3=B3pez?= Date: Wed, 8 Dec 2021 13:38:08 -0700 Subject: [PATCH 05/10] fixes the naming of function in testing, added update for updating tags and update docs --- internal/service/detective/graph.go | 22 +++++++-- internal/service/detective/graph_test.go | 52 ++++++++++++-------- website/docs/r/detective_graph.html.markdown | 8 ++- 3 files changed, 56 insertions(+), 26 deletions(-) diff --git a/internal/service/detective/graph.go b/internal/service/detective/graph.go index 49611bde97c..13486e1e0e0 100644 --- a/internal/service/detective/graph.go +++ b/internal/service/detective/graph.go @@ -19,9 +19,10 @@ import ( func ResourceGraph() *schema.Resource { return &schema.Resource{ - CreateWithoutTimeout: resourceGraphCreate, - ReadWithoutTimeout: resourceGraphRead, - DeleteWithoutTimeout: resourceGraphDelete, + CreateContext: resourceGraphCreate, + ReadContext: resourceGraphRead, + UpdateContext: resourceGraphUpdate, + DeleteContext: resourceGraphDelete, Importer: &schema.ResourceImporter{ StateContext: schema.ImportStatePassthroughContext, }, @@ -34,7 +35,7 @@ func ResourceGraph() *schema.Resource { Type: schema.TypeString, Computed: true, }, - "tags": tftags.TagsSchemaForceNew(), + "tags": tftags.TagsSchema(), "tags_all": tftags.TagsSchemaComputed(), }, } @@ -122,6 +123,19 @@ func resourceGraphRead(ctx context.Context, d *schema.ResourceData, meta interfa return nil } +func resourceGraphUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.AWSClient).DetectiveConn + + if d.HasChange("tags") { + o, n := d.GetChange("tags") + if err := UpdateTags(conn, d.Id(), o, n); err != nil { + return diag.FromErr(fmt.Errorf("error updating detective Graph tags (%s): %w", d.Id(), err)) + } + } + + return resourceGraphRead(ctx, d, meta) +} + func resourceGraphDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { conn := meta.(*conns.AWSClient).DetectiveConn diff --git a/internal/service/detective/graph_test.go b/internal/service/detective/graph_test.go index a117ec8178f..b0b2a52c268 100644 --- a/internal/service/detective/graph_test.go +++ b/internal/service/detective/graph_test.go @@ -5,6 +5,7 @@ import ( "fmt" "testing" + "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/detective" "github.com/hashicorp/aws-sdk-go-base/tfawserr" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" @@ -14,20 +15,20 @@ import ( tfdetective "github.com/hashicorp/terraform-provider-aws/internal/service/detective" ) -func TestAccAwsDetectiveGraph_basic(t *testing.T) { +func TestAccDetectiveGraph_basic(t *testing.T) { var graphOutput detective.Graph resourceName := "aws_detective_graph.test" resource.Test(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, ProviderFactories: acctest.ProviderFactories, - CheckDestroy: testAccCheckAwsDetectiveGraphDestroy, + CheckDestroy: testAccCheckDetectiveGraphDestroy, ErrorCheck: acctest.ErrorCheck(t, detective.EndpointsID), Steps: []resource.TestStep{ { - Config: testAccAwsDetectiveGraphConfigBasic(), + Config: testAccDetectiveGraphConfigBasic(), Check: resource.ComposeTestCheckFunc( - testAccCheckAwsDetectiveGraphExists(resourceName, &graphOutput), + testAccCheckDetectiveGraphExists(resourceName, &graphOutput), acctest.CheckResourceAttrRFC3339(resourceName, "created_time"), ), }, @@ -40,20 +41,20 @@ func TestAccAwsDetectiveGraph_basic(t *testing.T) { }) } -func TestAccAwsDetectiveGraph_WithTags(t *testing.T) { - var graphOutput detective.Graph +func TestAccDetectiveGraph_tags(t *testing.T) { + var graph1, graph2 detective.Graph resourceName := "aws_detective_graph.test" resource.Test(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, ProviderFactories: acctest.ProviderFactories, - CheckDestroy: testAccCheckAwsDetectiveGraphDestroy, + CheckDestroy: testAccCheckDetectiveGraphDestroy, ErrorCheck: acctest.ErrorCheck(t, detective.EndpointsID), Steps: []resource.TestStep{ { - Config: testAccAwsDetectiveGraphConfigWithTags(), + Config: testAccDetectiveGraphConfigWithTags(), Check: resource.ComposeTestCheckFunc( - testAccCheckAwsDetectiveGraphExists(resourceName, &graphOutput), + testAccCheckDetectiveGraphExists(resourceName, &graph1), acctest.CheckResourceAttrRFC3339(resourceName, "created_time"), resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), resource.TestCheckResourceAttr(resourceName, "tags.Key", "value"), @@ -62,9 +63,10 @@ func TestAccAwsDetectiveGraph_WithTags(t *testing.T) { ), }, { - Config: testAccAwsDetectiveGraphConfigWithTagsUpdate(), + Config: testAccDetectiveGraphConfigTagsUpdate(), Check: resource.ComposeTestCheckFunc( - testAccCheckAwsDetectiveGraphExists(resourceName, &graphOutput), + testAccCheckDetectiveGraphExists(resourceName, &graph2), + testAccCheckDetectiveGraphNotRecreated(&graph1, &graph2), acctest.CheckResourceAttrRFC3339(resourceName, "created_time"), resource.TestCheckResourceAttr(resourceName, "tags.%", "2"), resource.TestCheckResourceAttr(resourceName, "tags.Key", "value"), @@ -83,20 +85,20 @@ func TestAccAwsDetectiveGraph_WithTags(t *testing.T) { }) } -func TestAccAwsDetectiveGraph_disappears(t *testing.T) { +func TestAccDetectiveGraph_disappears(t *testing.T) { var graphOutput detective.Graph resourceName := "aws_detective_graph.test" resource.Test(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, ProviderFactories: acctest.ProviderFactories, - CheckDestroy: testAccCheckAwsDetectiveGraphDestroy, + CheckDestroy: testAccCheckDetectiveGraphDestroy, ErrorCheck: acctest.ErrorCheck(t, detective.EndpointsID), Steps: []resource.TestStep{ { - Config: testAccAwsDetectiveGraphConfigBasic(), + Config: testAccDetectiveGraphConfigBasic(), Check: resource.ComposeTestCheckFunc( - testAccCheckAwsDetectiveGraphExists(resourceName, &graphOutput), + testAccCheckDetectiveGraphExists(resourceName, &graphOutput), acctest.CheckResourceDisappears(acctest.Provider, tfdetective.ResourceGraph(), resourceName), ), ExpectNonEmptyPlan: true, @@ -105,7 +107,7 @@ func TestAccAwsDetectiveGraph_disappears(t *testing.T) { }) } -func testAccCheckAwsDetectiveGraphDestroy(s *terraform.State) error { +func testAccCheckDetectiveGraphDestroy(s *terraform.State) error { conn := acctest.Provider.Meta().(*conns.AWSClient).DetectiveConn for _, rs := range s.RootModule().Resources { @@ -132,7 +134,7 @@ func testAccCheckAwsDetectiveGraphDestroy(s *terraform.State) error { } -func testAccCheckAwsDetectiveGraphExists(resourceName string, graph *detective.Graph) resource.TestCheckFunc { +func testAccCheckDetectiveGraphExists(resourceName string, graph *detective.Graph) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[resourceName] if !ok { @@ -156,13 +158,23 @@ func testAccCheckAwsDetectiveGraphExists(resourceName string, graph *detective.G } } -func testAccAwsDetectiveGraphConfigBasic() string { +func testAccCheckDetectiveGraphNotRecreated(before, after *detective.Graph) resource.TestCheckFunc { + return func(s *terraform.State) error { + if before, after := aws.StringValue(before.Arn), aws.StringValue(after.Arn); before != after { + return fmt.Errorf("detective graph (%s/%s) recreated", before, after) + } + + return nil + } +} + +func testAccDetectiveGraphConfigBasic() string { return ` resource "aws_detective_graph" "test" {} ` } -func testAccAwsDetectiveGraphConfigWithTags() string { +func testAccDetectiveGraphConfigWithTags() string { return ` resource "aws_detective_graph" "test" { tags = { @@ -172,7 +184,7 @@ resource "aws_detective_graph" "test" { ` } -func testAccAwsDetectiveGraphConfigWithTagsUpdate() string { +func testAccDetectiveGraphConfigTagsUpdate() string { return ` resource "aws_detective_graph" "test" { tags = { diff --git a/website/docs/r/detective_graph.html.markdown b/website/docs/r/detective_graph.html.markdown index 2f4ce14a785..3f9c359bf71 100644 --- a/website/docs/r/detective_graph.html.markdown +++ b/website/docs/r/detective_graph.html.markdown @@ -8,12 +8,16 @@ description: |- # Resource: aws_detective_graph -Provides a resource to manage an [AWS Detective Graph](https://docs.aws.amazon.com/detective/latest/APIReference/API_CreateGraph.html). +Provides a resource to manage an [AWS Detective Graph](https://docs.aws.amazon.com/detective/latest/APIReference/API_CreateGraph.html) and only one can exist per region. ## Example Usage ```terraform -resource "aws_detective_graph" "example" {} +resource "aws_detective_graph" "example" { + tags = { + Name = "example-detective-graph" + } +} ``` ## Argument Reference From 1343b6690d3e81250ed620d853482dff283a83ef Mon Sep 17 00:00:00 2001 From: Zoe Helding Date: Thu, 9 Dec 2021 12:41:42 -0600 Subject: [PATCH 06/10] Use generated ListTags function for Detective resourceGraphRead --- internal/service/detective/generate.go | 2 +- internal/service/detective/graph.go | 15 ++++++--------- internal/service/detective/tags_gen.go | 17 +++++++++++++++++ 3 files changed, 24 insertions(+), 10 deletions(-) diff --git a/internal/service/detective/generate.go b/internal/service/detective/generate.go index f83d5dd9bbc..9284016ebb4 100644 --- a/internal/service/detective/generate.go +++ b/internal/service/detective/generate.go @@ -1,4 +1,4 @@ -//go:generate go run ../../generate/tags/main.go -ServiceTagsMap -UpdateTags +//go:generate go run ../../generate/tags/main.go -ServiceTagsMap -ListTags -UpdateTags // ONLY generate directives and package declaration! Do not add anything else to this file. package detective diff --git a/internal/service/detective/graph.go b/internal/service/detective/graph.go index 13486e1e0e0..669d31d879b 100644 --- a/internal/service/detective/graph.go +++ b/internal/service/detective/graph.go @@ -3,7 +3,6 @@ package detective import ( "context" "fmt" - "log" "time" "github.com/aws/aws-sdk-go/aws" @@ -15,6 +14,7 @@ import ( "github.com/hashicorp/terraform-provider-aws/internal/conns" 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" ) func ResourceGraph() *schema.Resource { @@ -38,6 +38,7 @@ func ResourceGraph() *schema.Resource { "tags": tftags.TagsSchema(), "tags_all": tftags.TagsSchemaComputed(), }, + CustomizeDiff: verify.SetTagsDiff, } } @@ -100,17 +101,13 @@ func resourceGraphRead(ctx context.Context, d *schema.ResourceData, meta interfa d.Set("created_time", aws.TimeValue(resp.CreatedTime).Format(time.RFC3339)) d.Set("graph_arn", resp.Arn) - tg, err := conn.ListTagsForResource(&detective.ListTagsForResourceInput{ - ResourceArn: resp.Arn, - }) + tags, err := ListTags(conn, aws.StringValue(resp.Arn)) + if err != nil { return diag.FromErr(fmt.Errorf("error listing tags for Detective Graph (%s): %w", d.Id(), err)) } - if tg.Tags == nil { - log.Printf("[DEBUG] Detective Graph tags (%s) not found", d.Id()) - return nil - } - tags := KeyValueTags(tg.Tags).IgnoreAWS().IgnoreConfig(ignoreTagsConfig) + + tags = tags.IgnoreAWS().IgnoreConfig(ignoreTagsConfig) if err = d.Set("tags", tags.RemoveDefaultConfig(defaultTagsConfig).Map()); err != nil { return diag.FromErr(fmt.Errorf("error setting `%s` for Detective Graph (%s): %w", "tags", d.Id(), err)) diff --git a/internal/service/detective/tags_gen.go b/internal/service/detective/tags_gen.go index 5b7c663e8e6..434fb6f4264 100644 --- a/internal/service/detective/tags_gen.go +++ b/internal/service/detective/tags_gen.go @@ -9,6 +9,23 @@ import ( tftags "github.com/hashicorp/terraform-provider-aws/internal/tags" ) +// ListTags lists detective service tags. +// The identifier is typically the Amazon Resource Name (ARN), although +// it may also be a different identifier depending on the service. +func ListTags(conn *detective.Detective, identifier string) (tftags.KeyValueTags, error) { + input := &detective.ListTagsForResourceInput{ + ResourceArn: aws.String(identifier), + } + + output, err := conn.ListTagsForResource(input) + + if err != nil { + return tftags.New(nil), err + } + + return KeyValueTags(output.Tags), nil +} + // map[string]*string handling // Tags returns detective service tags. From 5204cd513cd765b3e34457714884bd6723588360 Mon Sep 17 00:00:00 2001 From: Zoe Helding Date: Thu, 9 Dec 2021 12:52:32 -0600 Subject: [PATCH 07/10] Amend names of Detective tag acceptance tests --- internal/service/detective/graph_test.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/internal/service/detective/graph_test.go b/internal/service/detective/graph_test.go index b0b2a52c268..50d7c0a4184 100644 --- a/internal/service/detective/graph_test.go +++ b/internal/service/detective/graph_test.go @@ -52,7 +52,7 @@ func TestAccDetectiveGraph_tags(t *testing.T) { ErrorCheck: acctest.ErrorCheck(t, detective.EndpointsID), Steps: []resource.TestStep{ { - Config: testAccDetectiveGraphConfigWithTags(), + Config: testAccDetectiveGraphConfigTags1(), Check: resource.ComposeTestCheckFunc( testAccCheckDetectiveGraphExists(resourceName, &graph1), acctest.CheckResourceAttrRFC3339(resourceName, "created_time"), @@ -63,7 +63,7 @@ func TestAccDetectiveGraph_tags(t *testing.T) { ), }, { - Config: testAccDetectiveGraphConfigTagsUpdate(), + Config: testAccDetectiveGraphConfigTags2(), Check: resource.ComposeTestCheckFunc( testAccCheckDetectiveGraphExists(resourceName, &graph2), testAccCheckDetectiveGraphNotRecreated(&graph1, &graph2), @@ -174,7 +174,7 @@ resource "aws_detective_graph" "test" {} ` } -func testAccDetectiveGraphConfigWithTags() string { +func testAccDetectiveGraphConfigTags1() string { return ` resource "aws_detective_graph" "test" { tags = { @@ -184,7 +184,7 @@ resource "aws_detective_graph" "test" { ` } -func testAccDetectiveGraphConfigTagsUpdate() string { +func testAccDetectiveGraphConfigTags2() string { return ` resource "aws_detective_graph" "test" { tags = { From e2cfd1446b4c6bfb1fb90d860d8117bbcf53acfa Mon Sep 17 00:00:00 2001 From: Zoe Helding Date: Thu, 9 Dec 2021 13:07:57 -0600 Subject: [PATCH 08/10] detective/graph: replace diag.FromErr(fmt.Errorf()) w/ diag.Errorf() --- internal/service/detective/graph.go | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/internal/service/detective/graph.go b/internal/service/detective/graph.go index 669d31d879b..f8fe6eb65ca 100644 --- a/internal/service/detective/graph.go +++ b/internal/service/detective/graph.go @@ -2,7 +2,6 @@ package detective import ( "context" - "fmt" "time" "github.com/aws/aws-sdk-go/aws" @@ -74,7 +73,7 @@ func resourceGraphCreate(ctx context.Context, d *schema.ResourceData, meta inter } if err != nil { - return diag.FromErr(fmt.Errorf("error creating detective Graph: %w", err)) + return diag.Errorf("error creating detective Graph: %s", err) } d.SetId(aws.StringValue(output.GraphArn)) @@ -95,7 +94,7 @@ func resourceGraphRead(ctx context.Context, d *schema.ResourceData, meta interfa return nil } if err != nil { - return diag.FromErr(fmt.Errorf("error reading detective Graph (%s): %w", d.Id(), err)) + return diag.Errorf("error reading detective Graph (%s): %s", d.Id(), err) } d.Set("created_time", aws.TimeValue(resp.CreatedTime).Format(time.RFC3339)) @@ -104,17 +103,17 @@ func resourceGraphRead(ctx context.Context, d *schema.ResourceData, meta interfa tags, err := ListTags(conn, aws.StringValue(resp.Arn)) if err != nil { - return diag.FromErr(fmt.Errorf("error listing tags for Detective Graph (%s): %w", d.Id(), err)) + return diag.Errorf("error listing tags for Detective Graph (%s): %s", d.Id(), err) } tags = tags.IgnoreAWS().IgnoreConfig(ignoreTagsConfig) if err = d.Set("tags", tags.RemoveDefaultConfig(defaultTagsConfig).Map()); err != nil { - return diag.FromErr(fmt.Errorf("error setting `%s` for Detective Graph (%s): %w", "tags", d.Id(), err)) + return diag.Errorf("error setting `%s` for Detective Graph (%s): %s", "tags", d.Id(), err) } if err = d.Set("tags_all", tags.Map()); err != nil { - return diag.FromErr(fmt.Errorf("error setting `%s` for Detective Graph (%s): %w", "tags_all", d.Id(), err)) + return diag.Errorf("error setting `%s` for Detective Graph (%s): %s", "tags_all", d.Id(), err) } return nil @@ -126,7 +125,7 @@ func resourceGraphUpdate(ctx context.Context, d *schema.ResourceData, meta inter if d.HasChange("tags") { o, n := d.GetChange("tags") if err := UpdateTags(conn, d.Id(), o, n); err != nil { - return diag.FromErr(fmt.Errorf("error updating detective Graph tags (%s): %w", d.Id(), err)) + return diag.Errorf("error updating detective Graph tags (%s): %s", d.Id(), err) } } @@ -145,7 +144,7 @@ func resourceGraphDelete(ctx context.Context, d *schema.ResourceData, meta inter if tfawserr.ErrCodeEquals(err, detective.ErrCodeResourceNotFoundException) { return nil } - return diag.FromErr(fmt.Errorf("error deleting detective Graph (%s): %w", d.Id(), err)) + return diag.Errorf("error deleting detective Graph (%s): %s", d.Id(), err) } return nil From 91a711b9edd09ae27047160860e7aef1e33cb8f8 Mon Sep 17 00:00:00 2001 From: Zoe Helding Date: Thu, 9 Dec 2021 13:24:04 -0600 Subject: [PATCH 09/10] Amend detective/graph docs w/ how to provision multiple graphs --- website/docs/r/detective_graph.html.markdown | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/website/docs/r/detective_graph.html.markdown b/website/docs/r/detective_graph.html.markdown index 3f9c359bf71..ba62c9e7c59 100644 --- a/website/docs/r/detective_graph.html.markdown +++ b/website/docs/r/detective_graph.html.markdown @@ -8,7 +8,7 @@ description: |- # Resource: aws_detective_graph -Provides a resource to manage an [AWS Detective Graph](https://docs.aws.amazon.com/detective/latest/APIReference/API_CreateGraph.html) and only one can exist per region. +Provides a resource to manage an [AWS Detective Graph](https://docs.aws.amazon.com/detective/latest/APIReference/API_CreateGraph.html). As an AWS account may own only one Detective graph per region, provisioning multiple Detective graphs requires a separate provider configuration for each graph. ## Example Usage @@ -40,4 +40,4 @@ In addition to all arguments above, the following attributes are exported: ``` $ terraform import aws_detective_graph.example arn:aws:detective:us-east-1:123456789101:graph:231684d34gh74g4bae1dbc7bd807d02d -``` \ No newline at end of file +``` From aae8ad9ca7ec899e2e97a79fc151cb5dee568c07 Mon Sep 17 00:00:00 2001 From: Zoe Helding Date: Thu, 9 Dec 2021 14:46:20 -0600 Subject: [PATCH 10/10] detective/graph: Add acceptance test step for detagging graph --- internal/service/detective/graph_test.go | 46 +++++++++++++++--------- 1 file changed, 29 insertions(+), 17 deletions(-) diff --git a/internal/service/detective/graph_test.go b/internal/service/detective/graph_test.go index 50d7c0a4184..5ad4cd2942d 100644 --- a/internal/service/detective/graph_test.go +++ b/internal/service/detective/graph_test.go @@ -52,28 +52,40 @@ func TestAccDetectiveGraph_tags(t *testing.T) { ErrorCheck: acctest.ErrorCheck(t, detective.EndpointsID), Steps: []resource.TestStep{ { - Config: testAccDetectiveGraphConfigTags1(), + Config: testAccDetectiveGraphConfigTags1("key1", "value1"), Check: resource.ComposeTestCheckFunc( testAccCheckDetectiveGraphExists(resourceName, &graph1), acctest.CheckResourceAttrRFC3339(resourceName, "created_time"), resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), - resource.TestCheckResourceAttr(resourceName, "tags.Key", "value"), + resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1"), resource.TestCheckResourceAttr(resourceName, "tags_all.%", "1"), - resource.TestCheckResourceAttr(resourceName, "tags_all.Key", "value"), + resource.TestCheckResourceAttr(resourceName, "tags_all.key1", "value1"), ), }, { - Config: testAccDetectiveGraphConfigTags2(), + Config: testAccDetectiveGraphConfigTags2("key1", "value1updated", "key2", "value2"), Check: resource.ComposeTestCheckFunc( testAccCheckDetectiveGraphExists(resourceName, &graph2), testAccCheckDetectiveGraphNotRecreated(&graph1, &graph2), acctest.CheckResourceAttrRFC3339(resourceName, "created_time"), resource.TestCheckResourceAttr(resourceName, "tags.%", "2"), - resource.TestCheckResourceAttr(resourceName, "tags.Key", "value"), - resource.TestCheckResourceAttr(resourceName, "tags.Key2", "value2"), + resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1updated"), + resource.TestCheckResourceAttr(resourceName, "tags.key2", "value2"), resource.TestCheckResourceAttr(resourceName, "tags_all.%", "2"), - resource.TestCheckResourceAttr(resourceName, "tags_all.Key", "value"), - resource.TestCheckResourceAttr(resourceName, "tags_all.Key2", "value2"), + resource.TestCheckResourceAttr(resourceName, "tags_all.key1", "value1updated"), + resource.TestCheckResourceAttr(resourceName, "tags_all.key2", "value2"), + ), + }, + { + Config: testAccDetectiveGraphConfigTags1("key2", "value2"), + Check: resource.ComposeTestCheckFunc( + testAccCheckDetectiveGraphExists(resourceName, &graph2), + testAccCheckDetectiveGraphNotRecreated(&graph1, &graph2), + acctest.CheckResourceAttrRFC3339(resourceName, "created_time"), + resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), + resource.TestCheckResourceAttr(resourceName, "tags.key2", "value2"), + resource.TestCheckResourceAttr(resourceName, "tags_all.%", "1"), + resource.TestCheckResourceAttr(resourceName, "tags_all.key2", "value2"), ), }, { @@ -174,23 +186,23 @@ resource "aws_detective_graph" "test" {} ` } -func testAccDetectiveGraphConfigTags1() string { - return ` +func testAccDetectiveGraphConfigTags1(tagKey1, tagValue1 string) string { + return fmt.Sprintf(` resource "aws_detective_graph" "test" { tags = { - Key = "value" + %[1]q = %[2]q } } -` +`, tagKey1, tagValue1) } -func testAccDetectiveGraphConfigTags2() string { - return ` +func testAccDetectiveGraphConfigTags2(tagKey1, tagValue1, tagKey2, tagValue2 string) string { + return fmt.Sprintf(` resource "aws_detective_graph" "test" { tags = { - Key = "value" - Key2 = "value2" + %[1]q = %[2]q + %[3]q = %[4]q } } -` +`, tagKey1, tagValue1, tagKey2, tagValue2) }