diff --git a/.changelog/22578.txt b/.changelog/22578.txt new file mode 100644 index 00000000000..61c7cfb3ae1 --- /dev/null +++ b/.changelog/22578.txt @@ -0,0 +1,3 @@ +```release-note:new-resource +aws_appsync_api_cache +``` \ No newline at end of file diff --git a/internal/provider/provider.go b/internal/provider/provider.go index b05196f8a35..b1b6ddda4c5 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -827,6 +827,7 @@ func Provider() *schema.Provider { "aws_appstream_user": appstream.ResourceUser(), "aws_appstream_user_stack_association": appstream.ResourceUserStackAssociation(), + "aws_appsync_api_cache": appsync.ResourceAPICache(), "aws_appsync_api_key": appsync.ResourceAPIKey(), "aws_appsync_datasource": appsync.ResourceDataSource(), "aws_appsync_function": appsync.ResourceFunction(), diff --git a/internal/service/appsync/api_cache.go b/internal/service/appsync/api_cache.go new file mode 100644 index 00000000000..c2da994b5da --- /dev/null +++ b/internal/service/appsync/api_cache.go @@ -0,0 +1,169 @@ +package appsync + +import ( + "fmt" + "log" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/appsync" + "github.com/hashicorp/aws-sdk-go-base/tfawserr" + "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/tfresource" +) + +func ResourceAPICache() *schema.Resource { + + return &schema.Resource{ + Create: resourceAPICacheCreate, + Read: resourceAPICacheRead, + Update: resourceAPICacheUpdate, + Delete: resourceAPICacheDelete, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + Schema: map[string]*schema.Schema{ + "api_id": { + Type: schema.TypeString, + Required: true, + }, + "api_caching_behavior": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice(appsync.ApiCachingBehavior_Values(), false), + }, + "type": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice(appsync.ApiCacheType_Values(), false), + }, + "ttl": { + Type: schema.TypeInt, + Required: true, + }, + "at_rest_encryption_enabled": { + Type: schema.TypeBool, + Optional: true, + ForceNew: true, + }, + "transit_encryption_enabled": { + Type: schema.TypeBool, + Optional: true, + ForceNew: true, + }, + }, + } +} + +func resourceAPICacheCreate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*conns.AWSClient).AppSyncConn + + apiID := d.Get("api_id").(string) + + params := &appsync.CreateApiCacheInput{ + ApiId: aws.String(apiID), + Type: aws.String(d.Get("type").(string)), + ApiCachingBehavior: aws.String(d.Get("api_caching_behavior").(string)), + Ttl: aws.Int64(int64(d.Get("ttl").(int))), + } + + if v, ok := d.GetOk("at_rest_encryption_enabled"); ok { + params.AtRestEncryptionEnabled = aws.Bool(v.(bool)) + } + + if v, ok := d.GetOk("transit_encryption_enabled"); ok { + params.TransitEncryptionEnabled = aws.Bool(v.(bool)) + } + + _, err := conn.CreateApiCache(params) + if err != nil { + return fmt.Errorf("error creating Appsync API Cache: %w", err) + } + + d.SetId(apiID) + + if err := waitApiCacheAvailable(conn, d.Id()); err != nil { + return fmt.Errorf("error waiting for Appsync API Cache (%s) availability: %w", d.Id(), err) + } + + return resourceAPICacheRead(d, meta) +} + +func resourceAPICacheRead(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*conns.AWSClient).AppSyncConn + + cache, err := FindApiCacheByID(conn, d.Id()) + if !d.IsNewResource() && tfresource.NotFound(err) { + log.Printf("[WARN] AppSync API Cache (%s) not found, removing from state", d.Id()) + d.SetId("") + return nil + } + + if err != nil { + return fmt.Errorf("error getting Appsync API Cache %q: %s", d.Id(), err) + } + + d.Set("api_id", d.Id()) + d.Set("type", cache.Type) + d.Set("api_caching_behavior", cache.ApiCachingBehavior) + d.Set("ttl", cache.Ttl) + d.Set("at_rest_encryption_enabled", cache.AtRestEncryptionEnabled) + d.Set("transit_encryption_enabled", cache.TransitEncryptionEnabled) + + return nil +} + +func resourceAPICacheUpdate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*conns.AWSClient).AppSyncConn + + params := &appsync.UpdateApiCacheInput{ + ApiId: aws.String(d.Id()), + } + + if d.HasChange("type") { + params.Type = aws.String(d.Get("type").(string)) + } + + if d.HasChange("api_caching_behavior") { + params.ApiCachingBehavior = aws.String(d.Get("api_caching_behavior").(string)) + } + + if d.HasChange("ttl") { + params.Ttl = aws.Int64(int64(d.Get("ttl").(int))) + } + + _, err := conn.UpdateApiCache(params) + if err != nil { + return fmt.Errorf("error updating Appsync API Cache %q: %w", d.Id(), err) + } + + if err := waitApiCacheAvailable(conn, d.Id()); err != nil { + return fmt.Errorf("error waiting for Appsync API Cache (%s) availability: %w", d.Id(), err) + } + + return resourceAPICacheRead(d, meta) + +} + +func resourceAPICacheDelete(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*conns.AWSClient).AppSyncConn + + input := &appsync.DeleteApiCacheInput{ + ApiId: aws.String(d.Id()), + } + _, err := conn.DeleteApiCache(input) + if err != nil { + if tfawserr.ErrCodeEquals(err, appsync.ErrCodeNotFoundException) { + return nil + } + return fmt.Errorf("error deleting Appsync API Cache: %w", err) + } + + if err := waitApiCacheDeleted(conn, d.Id()); err != nil { + return fmt.Errorf("error waiting for Appsync API Cache (%s) to be deleted: %w", d.Id(), err) + } + + return nil +} diff --git a/internal/service/appsync/api_cache_test.go b/internal/service/appsync/api_cache_test.go new file mode 100644 index 00000000000..90c703d9c8c --- /dev/null +++ b/internal/service/appsync/api_cache_test.go @@ -0,0 +1,124 @@ +package appsync_test + +import ( + "fmt" + "testing" + + "github.com/aws/aws-sdk-go/service/appsync" + 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" + tfappsync "github.com/hashicorp/terraform-provider-aws/internal/service/appsync" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" +) + +func testAccAppSyncApiCache_basic(t *testing.T) { + var apiCache appsync.ApiCache + resourceName := "aws_appsync_api_cache.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t); acctest.PreCheckPartitionHasService(appsync.EndpointsID, t) }, + ErrorCheck: acctest.ErrorCheck(t, appsync.EndpointsID), + Providers: acctest.Providers, + CheckDestroy: testAccCheckApiCacheDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAppsyncApiCacheBasicConfig(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckApiCacheExists(resourceName, &apiCache), + resource.TestCheckResourceAttrPair(resourceName, "api_id", "aws_appsync_graphql_api.test", "id"), + resource.TestCheckResourceAttr(resourceName, "type", "SMALL"), + resource.TestCheckResourceAttr(resourceName, "api_caching_behavior", "FULL_REQUEST_CACHING"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccAppSyncApiCache_disappears(t *testing.T) { + var apiCache appsync.ApiCache + resourceName := "aws_appsync_api_cache.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t); acctest.PreCheckPartitionHasService(appsync.EndpointsID, t) }, + ErrorCheck: acctest.ErrorCheck(t, appsync.EndpointsID), + Providers: acctest.Providers, + CheckDestroy: testAccCheckApiCacheDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAppsyncApiCacheBasicConfig(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckApiCacheExists(resourceName, &apiCache), + acctest.CheckResourceDisappears(acctest.Provider, tfappsync.ResourceAPICache(), resourceName), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +func testAccCheckApiCacheDestroy(s *terraform.State) error { + conn := acctest.Provider.Meta().(*conns.AWSClient).AppSyncConn + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_appsync_api_cache" { + continue + } + + _, err := tfappsync.FindApiCacheByID(conn, rs.Primary.ID) + if err == nil { + if tfresource.NotFound(err) { + return nil + } + return err + } + + return nil + + } + return nil +} + +func testAccCheckApiCacheExists(resourceName string, apiCache *appsync.ApiCache) resource.TestCheckFunc { + return func(s *terraform.State) error { + + rs, ok := s.RootModule().Resources[resourceName] + if !ok { + return fmt.Errorf("Appsync Api Cache Not found in state: %s", resourceName) + } + + conn := acctest.Provider.Meta().(*conns.AWSClient).AppSyncConn + cache, err := tfappsync.FindApiCacheByID(conn, rs.Primary.ID) + if err != nil { + return err + } + + *apiCache = *cache + + return nil + } +} + +func testAccAppsyncApiCacheBasicConfig(rName string) string { + return fmt.Sprintf(` +resource "aws_appsync_graphql_api" "test" { + authentication_type = "API_KEY" + name = %[1]q +} + +resource "aws_appsync_api_cache" "test" { + api_id = aws_appsync_graphql_api.test.id + type = "SMALL" + api_caching_behavior = "FULL_REQUEST_CACHING" + ttl = 60 +} +`, rName) +} diff --git a/internal/service/appsync/appsync_test.go b/internal/service/appsync/appsync_test.go index 625eedf438c..423ae1e39fd 100644 --- a/internal/service/appsync/appsync_test.go +++ b/internal/service/appsync/appsync_test.go @@ -77,6 +77,10 @@ func TestAccAppSync_serial(t *testing.T) { "caching": testAccAppSyncResolver_caching, "sync": testAccAppSyncResolver_syncConfig, }, + "ApiCache": { + "basic": testAccAppSyncApiCache_basic, + "disappears": testAccAppSyncApiCache_disappears, + }, } for group, m := range testCases { diff --git a/internal/service/appsync/find.go b/internal/service/appsync/find.go new file mode 100644 index 00000000000..c4a634b118b --- /dev/null +++ b/internal/service/appsync/find.go @@ -0,0 +1,33 @@ +package appsync + +import ( + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/appsync" + "github.com/hashicorp/aws-sdk-go-base/tfawserr" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" +) + +func FindApiCacheByID(conn *appsync.AppSync, id string) (*appsync.ApiCache, error) { + input := &appsync.GetApiCacheInput{ + ApiId: aws.String(id), + } + out, err := conn.GetApiCache(input) + + if tfawserr.ErrCodeEquals(err, appsync.ErrCodeNotFoundException) { + return nil, &resource.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + if out == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + return out.ApiCache, nil +} diff --git a/internal/service/appsync/status.go b/internal/service/appsync/status.go new file mode 100644 index 00000000000..f539fcf56db --- /dev/null +++ b/internal/service/appsync/status.go @@ -0,0 +1,24 @@ +package appsync + +import ( + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/appsync" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" +) + +func StatusApiCache(conn *appsync.AppSync, name string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + output, err := FindApiCacheByID(conn, name) + + if tfresource.NotFound(err) { + return nil, "", nil + } + + if err != nil { + return nil, "", err + } + + return output, aws.StringValue(output.Status), nil + } +} diff --git a/internal/service/appsync/wait.go b/internal/service/appsync/wait.go new file mode 100644 index 00000000000..ebc30da495f --- /dev/null +++ b/internal/service/appsync/wait.go @@ -0,0 +1,39 @@ +package appsync + +import ( + "time" + + "github.com/aws/aws-sdk-go/service/appsync" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" +) + +const ( + ApiCacheAvailableTimeout = 60 * time.Minute + ApiCacheDeletedTimeout = 60 * time.Minute +) + +func waitApiCacheAvailable(conn *appsync.AppSync, id string) error { + stateConf := &resource.StateChangeConf{ + Pending: []string{appsync.ApiCacheStatusCreating, appsync.ApiCacheStatusModifying}, + Target: []string{appsync.ApiCacheStatusAvailable}, + Refresh: StatusApiCache(conn, id), + Timeout: ApiCacheAvailableTimeout, + } + + _, err := stateConf.WaitForState() + + return err +} + +func waitApiCacheDeleted(conn *appsync.AppSync, id string) error { + stateConf := &resource.StateChangeConf{ + Pending: []string{appsync.ApiCacheStatusDeleting}, + Target: []string{}, + Refresh: StatusApiCache(conn, id), + Timeout: ApiCacheDeletedTimeout, + } + + _, err := stateConf.WaitForState() + + return err +} diff --git a/website/docs/r/appsync_api_cache.html.markdown b/website/docs/r/appsync_api_cache.html.markdown new file mode 100644 index 00000000000..37d9c558c59 --- /dev/null +++ b/website/docs/r/appsync_api_cache.html.markdown @@ -0,0 +1,50 @@ +--- +subcategory: "AppSync" +layout: "aws" +page_title: "AWS: aws_appsync_api_cache" +description: |- + Provides an AppSync API Cache. +--- + +# Resource: aws_appsync_api_cache + +Provides an AppSync API Cache. + +## Example Usage + +```terraform +resource "aws_appsync_graphql_api" "example" { + authentication_type = "API_KEY" + name = "example" +} + +resource "aws_appsync_api_cache" "example" { + api_id = aws_appsync_graphql_api.example.id + expires = "2018-05-03T04:00:00Z" +} +``` + +## Argument Reference + +The following arguments are supported: + +* `api_id` - (Required) The GraphQL API ID. +* `api_caching_behavior` - (Required) Caching behavior. Valid values are `FULL_REQUEST_CACHING` and `PER_RESOLVER_CACHING`. +* `type` - (Required) The cache instance type. Valid values are `SMALL`, `MEDIUM`, `LARGE`, `XLARGE`, `LARGE_2X`, `LARGE_4X`, `LARGE_8X`, `LARGE_12X`, `T2_SMALL`, `T2_MEDIUM`, `R4_LARGE`, `R4_XLARGE`, `R4_2XLARGE`, `R4_4XLARGE`, `R4_8XLARGE`. +* `ttl` - (Required) TTL in seconds for cache entries. +* `at_rest_encryption_enabled` - (Optional) At-rest encryption flag for cache. You cannot update this setting after creation. +* `transit_encryption_enabled` - (Optional) Transit encryption flag when connecting to cache. You cannot update this setting after creation. + +## Attributes Reference + +In addition to all arguments above, the following attributes are exported: + +* `id` - The AppSync API ID. + +## Import + +`aws_appsync_api_cache` can be imported using the AppSync API ID, e.g., + +``` +$ terraform import aws_appsync_api_cache.example xxxxx +```