diff --git a/.changelog/21234.txt b/.changelog/21234.txt new file mode 100644 index 00000000000..871aafcd064 --- /dev/null +++ b/.changelog/21234.txt @@ -0,0 +1,7 @@ +```release-note:bug +resource/aws_glue_catalog_table: change `partition_index.keys` to list instead of set +``` + +```release-note:new-resource +aws_glue_partition_index +``` diff --git a/aws/internal/service/glue/finder/finder.go b/aws/internal/service/glue/finder/finder.go index 6df9011c215..7a0498203ad 100644 --- a/aws/internal/service/glue/finder/finder.go +++ b/aws/internal/service/glue/finder/finder.go @@ -164,3 +164,57 @@ func ConnectionByName(conn *glue.Glue, name, catalogID string) (*glue.Connection return output.Connection, nil } + +// PartitionIndexByName returns the Partition Index corresponding to the specified Partition Index Name. +func PartitionIndexByName(conn *glue.Glue, id string) (*glue.PartitionIndexDescriptor, error) { + + catalogID, dbName, tableName, partIndex, err := tfglue.ReadAwsGluePartitionIndexID(id) + if err != nil { + return nil, err + } + + input := &glue.GetPartitionIndexesInput{ + CatalogId: aws.String(catalogID), + DatabaseName: aws.String(dbName), + TableName: aws.String(tableName), + } + + var result *glue.PartitionIndexDescriptor + + output, err := conn.GetPartitionIndexes(input) + + if tfawserr.ErrCodeEquals(err, glue.ErrCodeEntityNotFoundException) { + return nil, &resource.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + if output == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + for _, partInd := range output.PartitionIndexDescriptorList { + if partInd == nil { + continue + } + + if aws.StringValue(partInd.IndexName) == partIndex { + result = partInd + break + } + } + + if result == nil { + return nil, &resource.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + return result, nil +} diff --git a/aws/internal/service/glue/id.go b/aws/internal/service/glue/id.go index 77a22050498..08e426df27c 100644 --- a/aws/internal/service/glue/id.go +++ b/aws/internal/service/glue/id.go @@ -18,10 +18,22 @@ func ReadAwsGluePartitionID(id string) (catalogID string, dbName string, tableNa return idParts[0], idParts[1], idParts[2], vals, nil } +func ReadAwsGluePartitionIndexID(id string) (catalogID, dbName, tableName, indexName string, error error) { + idParts := strings.Split(id, ":") + if len(idParts) != 4 { + return "", "", "", "", fmt.Errorf("expected ID in format catalog-id:database-name:table-name:index-name, received: %s", id) + } + return idParts[0], idParts[1], idParts[2], idParts[3], nil +} + func CreateAwsGluePartitionID(catalogID, dbName, tableName string, values []interface{}) string { return fmt.Sprintf("%s:%s:%s:%s", catalogID, dbName, tableName, stringifyAwsGluePartition(values)) } +func CreateAwsGluePartitionIndexID(catalogID, dbName, tableName, indexName string) string { + return fmt.Sprintf("%s:%s:%s:%s", catalogID, dbName, tableName, indexName) +} + func stringifyAwsGluePartition(partValues []interface{}) string { var b bytes.Buffer for _, val := range partValues { diff --git a/aws/internal/service/glue/waiter/status.go b/aws/internal/service/glue/waiter/status.go index 3262cffb805..6e12d4a465e 100644 --- a/aws/internal/service/glue/waiter/status.go +++ b/aws/internal/service/glue/waiter/status.go @@ -121,3 +121,19 @@ func GlueDevEndpointStatus(conn *glue.Glue, name string) resource.StateRefreshFu return output, aws.StringValue(output.Status), nil } } + +func GluePartitionIndexStatus(conn *glue.Glue, id string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + output, err := finder.PartitionIndexByName(conn, id) + + if tfresource.NotFound(err) { + return nil, "", nil + } + + if err != nil { + return nil, "", err + } + + return output, aws.StringValue(output.IndexStatus), nil + } +} diff --git a/aws/internal/service/glue/waiter/waiter.go b/aws/internal/service/glue/waiter/waiter.go index 8c5158d3f03..411386c5328 100644 --- a/aws/internal/service/glue/waiter/waiter.go +++ b/aws/internal/service/glue/waiter/waiter.go @@ -196,3 +196,37 @@ func GlueDevEndpointDeleted(conn *glue.Glue, name string) (*glue.DevEndpoint, er return nil, err } + +func GluePartitionIndexCreated(conn *glue.Glue, id string) (*glue.PartitionIndexDescriptor, error) { + stateConf := &resource.StateChangeConf{ + Pending: []string{glue.PartitionIndexStatusCreating}, + Target: []string{glue.PartitionIndexStatusActive}, + Refresh: GluePartitionIndexStatus(conn, id), + Timeout: 2 * time.Minute, + } + + outputRaw, err := stateConf.WaitForState() + + if output, ok := outputRaw.(*glue.PartitionIndexDescriptor); ok { + return output, err + } + + return nil, err +} + +func GluePartitionIndexDeleted(conn *glue.Glue, id string) (*glue.PartitionIndexDescriptor, error) { + stateConf := &resource.StateChangeConf{ + Pending: []string{glue.PartitionIndexStatusDeleting}, + Target: []string{}, + Refresh: GluePartitionIndexStatus(conn, id), + Timeout: 2 * time.Minute, + } + + outputRaw, err := stateConf.WaitForState() + + if output, ok := outputRaw.(*glue.PartitionIndexDescriptor); ok { + return output, err + } + + return nil, err +} diff --git a/aws/provider.go b/aws/provider.go index b70f940e11c..960a3df1822 100644 --- a/aws/provider.go +++ b/aws/provider.go @@ -835,6 +835,7 @@ func Provider() *schema.Provider { "aws_glue_job": resourceAwsGlueJob(), "aws_glue_ml_transform": resourceAwsGlueMLTransform(), "aws_glue_partition": resourceAwsGluePartition(), + "aws_glue_partition_index": resourceAwsGluePartitionIndex(), "aws_glue_registry": resourceAwsGlueRegistry(), "aws_glue_resource_policy": resourceAwsGlueResourcePolicy(), "aws_glue_schema": resourceAwsGlueSchema(), diff --git a/aws/resource_aws_glue_catalog_table.go b/aws/resource_aws_glue_catalog_table.go index 0ea3c55caa4..454ff619ebb 100644 --- a/aws/resource_aws_glue_catalog_table.go +++ b/aws/resource_aws_glue_catalog_table.go @@ -318,6 +318,7 @@ func resourceAwsGlueCatalogTable() *schema.Resource { "partition_index": { Type: schema.TypeList, Optional: true, + Computed: true, ForceNew: true, MaxItems: 3, Elem: &schema.Resource{ @@ -328,7 +329,7 @@ func resourceAwsGlueCatalogTable() *schema.Resource { ValidateFunc: validation.StringLenBetween(1, 255), }, "keys": { - Type: schema.TypeSet, + Type: schema.TypeList, Required: true, MinItems: 1, Elem: &schema.Schema{Type: schema.TypeString}, @@ -563,7 +564,7 @@ func expandGlueTablePartitionIndexes(a []interface{}) []*glue.PartitionIndex { func expandGlueTablePartitionIndex(m map[string]interface{}) *glue.PartitionIndex { partitionIndex := &glue.PartitionIndex{ IndexName: aws.String(m["index_name"].(string)), - Keys: expandStringSet(m["keys"].(*schema.Set)), + Keys: expandStringList(m["keys"].([]interface{})), } return partitionIndex @@ -872,7 +873,7 @@ func flattenGluePartitionIndex(c *glue.PartitionIndexDescriptor) map[string]inte for _, key := range c.Keys { names = append(names, key.Name) } - partitionIndex["keys"] = flattenStringSet(names) + partitionIndex["keys"] = flattenStringList(names) } return partitionIndex diff --git a/aws/resource_aws_glue_partition_index.go b/aws/resource_aws_glue_partition_index.go new file mode 100644 index 00000000000..bb056040b9a --- /dev/null +++ b/aws/resource_aws_glue_partition_index.go @@ -0,0 +1,180 @@ +package aws + +import ( + "fmt" + "log" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/glue" + "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" + tfglue "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/glue" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/glue/finder" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/glue/waiter" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/tfresource" +) + +func resourceAwsGluePartitionIndex() *schema.Resource { + return &schema.Resource{ + Create: resourceAwsGluePartitionIndexCreate, + Read: resourceAwsGluePartitionIndexRead, + Delete: resourceAwsGluePartitionIndexDelete, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + Schema: map[string]*schema.Schema{ + "catalog_id": { + Type: schema.TypeString, + ForceNew: true, + Optional: true, + Computed: true, + }, + "database_name": { + Type: schema.TypeString, + ForceNew: true, + Required: true, + ValidateFunc: validation.StringLenBetween(1, 255), + }, + "table_name": { + Type: schema.TypeString, + ForceNew: true, + Required: true, + ValidateFunc: validation.StringLenBetween(1, 255), + }, + "partition_index": { + Type: schema.TypeList, + Required: true, + ForceNew: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "index_name": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + "index_status": { + Type: schema.TypeString, + Computed: true, + }, + "keys": { + Type: schema.TypeList, + Optional: true, + ForceNew: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + }, + }, + }, + }, + } +} + +func resourceAwsGluePartitionIndexCreate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).glueconn + catalogID := createAwsGlueCatalogID(d, meta.(*AWSClient).accountid) + dbName := d.Get("database_name").(string) + tableName := d.Get("table_name").(string) + + input := &glue.CreatePartitionIndexInput{ + CatalogId: aws.String(catalogID), + DatabaseName: aws.String(dbName), + TableName: aws.String(tableName), + PartitionIndex: expandAwsGluePartitionIndex(d.Get("partition_index").([]interface{})), + } + + log.Printf("[DEBUG] Creating Glue Partition Index: %#v", input) + _, err := conn.CreatePartitionIndex(input) + if err != nil { + return fmt.Errorf("error creating Glue Partition Index: %w", err) + } + + d.SetId(tfglue.CreateAwsGluePartitionIndexID(catalogID, dbName, tableName, aws.StringValue(input.PartitionIndex.IndexName))) + + if _, err := waiter.GluePartitionIndexCreated(conn, d.Id()); err != nil { + return fmt.Errorf("error while waiting for Glue Partition Index (%s) to become available: %w", d.Id(), err) + } + + return resourceAwsGluePartitionIndexRead(d, meta) +} + +func resourceAwsGluePartitionIndexRead(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).glueconn + + catalogID, dbName, tableName, _, tableErr := tfglue.ReadAwsGluePartitionIndexID(d.Id()) + if tableErr != nil { + return tableErr + } + + log.Printf("[DEBUG] Reading Glue Partition Index: %s", d.Id()) + partition, err := finder.PartitionIndexByName(conn, d.Id()) + if !d.IsNewResource() && tfresource.NotFound(err) { + log.Printf("[WARN] Glue Partition Index (%s) not found, removing from state", d.Id()) + d.SetId("") + return nil + } + + if err != nil { + return fmt.Errorf("error reading Glue Partition Index (%s): %w", d.Id(), err) + } + + d.Set("table_name", tableName) + d.Set("catalog_id", catalogID) + d.Set("database_name", dbName) + + if err := d.Set("partition_index", []map[string]interface{}{flattenGluePartitionIndex(partition)}); err != nil { + return fmt.Errorf("error setting partition_index: %w", err) + } + + return nil +} + +func resourceAwsGluePartitionIndexDelete(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).glueconn + + catalogID, dbName, tableName, partIndex, tableErr := tfglue.ReadAwsGluePartitionIndexID(d.Id()) + if tableErr != nil { + return tableErr + } + + log.Printf("[DEBUG] Deleting Glue Partition Index: %s", d.Id()) + _, err := conn.DeletePartitionIndex(&glue.DeletePartitionIndexInput{ + CatalogId: aws.String(catalogID), + TableName: aws.String(tableName), + DatabaseName: aws.String(dbName), + IndexName: aws.String(partIndex), + }) + if err != nil { + if tfawserr.ErrCodeEquals(err, glue.ErrCodeEntityNotFoundException) { + return nil + } + return fmt.Errorf("Error deleting Glue Partition Index: %w", err) + } + + if _, err := waiter.GluePartitionIndexDeleted(conn, d.Id()); err != nil { + return fmt.Errorf("error while waiting for Glue Partition Index (%s) to be deleted: %w", d.Id(), err) + } + + return nil +} + +func expandAwsGluePartitionIndex(l []interface{}) *glue.PartitionIndex { + if len(l) == 0 || l[0] == nil { + return nil + } + + s := l[0].(map[string]interface{}) + parIndex := &glue.PartitionIndex{} + + if v, ok := s["keys"].([]interface{}); ok && len(v) > 0 { + parIndex.Keys = expandStringList(v) + } + + if v, ok := s["index_name"].(string); ok && v != "" { + parIndex.IndexName = aws.String(v) + } + + return parIndex +} diff --git a/aws/resource_aws_glue_partition_index_test.go b/aws/resource_aws_glue_partition_index_test.go new file mode 100644 index 00000000000..1c648759083 --- /dev/null +++ b/aws/resource_aws_glue_partition_index_test.go @@ -0,0 +1,260 @@ +package aws + +import ( + "fmt" + "testing" + + "github.com/aws/aws-sdk-go/service/glue" + "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/terraform-providers/terraform-provider-aws/aws/internal/service/glue/finder" +) + +func TestAccAWSGluePartitionIndex_basic(t *testing.T) { + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_glue_partition_index.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ErrorCheck: testAccErrorCheck(t, glue.EndpointsID), + Providers: testAccProviders, + CheckDestroy: testAccCheckGluePartitionIndexDestroy, + Steps: []resource.TestStep{ + { + Config: testAccGluePartitionIndex_basic(rName), + Destroy: false, + Check: resource.ComposeTestCheckFunc( + testAccCheckGluePartitionIndexExists(resourceName), + resource.TestCheckResourceAttrPair(resourceName, "table_name", "aws_glue_catalog_table.test", "name"), + resource.TestCheckResourceAttrPair(resourceName, "database_name", "aws_glue_catalog_database.test", "name"), + resource.TestCheckResourceAttr(resourceName, "partition_index.#", "1"), + resource.TestCheckResourceAttr(resourceName, "partition_index.0.index_name", rName), + resource.TestCheckResourceAttr(resourceName, "partition_index.0.keys.#", "2"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccAWSGluePartitionIndex_disappears(t *testing.T) { + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_glue_partition_index.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ErrorCheck: testAccErrorCheck(t, glue.EndpointsID), + Providers: testAccProviders, + CheckDestroy: testAccCheckGluePartitionIndexDestroy, + Steps: []resource.TestStep{ + { + Config: testAccGluePartitionIndex_basic(rName), + Destroy: false, + Check: resource.ComposeTestCheckFunc( + testAccCheckGluePartitionIndexExists(resourceName), + testAccCheckResourceDisappears(testAccProvider, resourceAwsGluePartitionIndex(), resourceName), + testAccCheckResourceDisappears(testAccProvider, resourceAwsGluePartitionIndex(), resourceName), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +func TestAccAWSGluePartitionIndex_disappears_table(t *testing.T) { + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_glue_partition_index.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ErrorCheck: testAccErrorCheck(t, glue.EndpointsID), + Providers: testAccProviders, + CheckDestroy: testAccCheckGluePartitionIndexDestroy, + Steps: []resource.TestStep{ + { + Config: testAccGluePartitionIndex_basic(rName), + Destroy: false, + Check: resource.ComposeTestCheckFunc( + testAccCheckGluePartitionIndexExists(resourceName), + testAccCheckResourceDisappears(testAccProvider, resourceAwsGlueCatalogTable(), "aws_glue_catalog_table.test"), + testAccCheckResourceDisappears(testAccProvider, resourceAwsGluePartitionIndex(), resourceName), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +func TestAccAWSGluePartitionIndex_disappears_database(t *testing.T) { + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_glue_partition_index.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ErrorCheck: testAccErrorCheck(t, glue.EndpointsID), + Providers: testAccProviders, + CheckDestroy: testAccCheckGluePartitionIndexDestroy, + Steps: []resource.TestStep{ + { + Config: testAccGluePartitionIndex_basic(rName), + Destroy: false, + Check: resource.ComposeTestCheckFunc( + testAccCheckGluePartitionIndexExists(resourceName), + testAccCheckResourceDisappears(testAccProvider, resourceAwsGlueCatalogDatabase(), "aws_glue_catalog_database.test"), + testAccCheckResourceDisappears(testAccProvider, resourceAwsGluePartitionIndex(), resourceName), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +func testAccGluePartitionIndex_basic(rName string) string { + return fmt.Sprintf(` +resource "aws_glue_catalog_database" "test" { + name = %[1]q +} + +resource "aws_glue_catalog_table" "test" { + name = %[1]q + database_name = aws_glue_catalog_database.test.name + owner = "my_owner" + retention = 1 + table_type = "VIRTUAL_VIEW" + view_expanded_text = "view_expanded_text_1" + view_original_text = "view_original_text_1" + + storage_descriptor { + bucket_columns = ["bucket_column_1"] + compressed = false + input_format = "SequenceFileInputFormat" + location = "my_location" + number_of_buckets = 1 + output_format = "SequenceFileInputFormat" + stored_as_sub_directories = false + + parameters = { + param1 = "param1_val" + } + + columns { + name = "my_column_1" + type = "int" + comment = "my_column1_comment" + } + + columns { + name = "my_column_2" + type = "string" + comment = "my_column2_comment" + } + + ser_de_info { + name = "ser_de_name" + + parameters = { + param1 = "param_val_1" + } + + serialization_library = "org.apache.hadoop.hive.serde2.columnar.ColumnarSerDe" + } + + sort_columns { + column = "my_column_1" + sort_order = 1 + } + + skewed_info { + skewed_column_names = [ + "my_column_1", + ] + + skewed_column_value_location_maps = { + my_column_1 = "my_column_1_val_loc_map" + } + + skewed_column_values = [ + "skewed_val_1", + ] + } + } + + partition_keys { + name = "my_column_1" + type = "int" + comment = "my_column_1_comment" + } + + partition_keys { + name = "my_column_2" + type = "string" + comment = "my_column_2_comment" + } + + parameters = { + param1 = "param1_val" + } +} + +resource "aws_glue_partition_index" "test" { + database_name = aws_glue_catalog_database.test.name + table_name = aws_glue_catalog_table.test.name + + partition_index { + index_name = %[1]q + keys = ["my_column_1", "my_column_2"] + } +} +`, rName) +} + +func testAccCheckGluePartitionIndexDestroy(s *terraform.State) error { + conn := testAccProvider.Meta().(*AWSClient).glueconn + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_glue_partition_index" { + continue + } + + if _, err := finder.PartitionIndexByName(conn, rs.Primary.ID); err != nil { + //Verify the error is what we want + if isAWSErr(err, glue.ErrCodeEntityNotFoundException, "") { + continue + } + + return err + } + return fmt.Errorf("still exists") + } + return nil +} + +func testAccCheckGluePartitionIndexExists(name string) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[name] + if !ok { + return fmt.Errorf("Not found: %s", name) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("No ID is set") + } + + conn := testAccProvider.Meta().(*AWSClient).glueconn + out, err := finder.PartitionIndexByName(conn, rs.Primary.ID) + if err != nil { + return err + } + + if out == nil { + return fmt.Errorf("No Glue Partition Index Found") + } + + return nil + } +} diff --git a/website/docs/r/glue_partition_index.html.markdown b/website/docs/r/glue_partition_index.html.markdown new file mode 100644 index 00000000000..d2270adc17d --- /dev/null +++ b/website/docs/r/glue_partition_index.html.markdown @@ -0,0 +1,138 @@ +--- +subcategory: "Glue" +layout: "aws" +page_title: "AWS: aws_glue_partition_index" +description: |- + Provides a Glue Partition Index. +--- + +# Resource: aws_glue_partition_index + +## Example Usage + +```terraform +resource "aws_glue_catalog_database" "example" { + name = "example" +} + +resource "aws_glue_catalog_table" "example" { + name = "example" + database_name = aws_glue_catalog_database.example.name + owner = "my_owner" + retention = 1 + table_type = "VIRTUAL_VIEW" + view_expanded_text = "view_expanded_text_1" + view_original_text = "view_original_text_1" + + storage_descriptor { + bucket_columns = ["bucket_column_1"] + compressed = false + input_format = "SequenceFileInputFormat" + location = "my_location" + number_of_buckets = 1 + output_format = "SequenceFileInputFormat" + stored_as_sub_directories = false + + parameters = { + param1 = "param1_val" + } + + columns { + name = "my_column_1" + type = "int" + comment = "my_column1_comment" + } + + columns { + name = "my_column_2" + type = "string" + comment = "my_column2_comment" + } + + ser_de_info { + name = "ser_de_name" + + parameters = { + param1 = "param_val_1" + } + + serialization_library = "org.apache.hadoop.hive.serde2.columnar.ColumnarSerDe" + } + + sort_columns { + column = "my_column_1" + sort_order = 1 + } + + skewed_info { + skewed_column_names = [ + "my_column_1", + ] + + skewed_column_value_location_maps = { + my_column_1 = "my_column_1_val_loc_map" + } + + skewed_column_values = [ + "skewed_val_1", + ] + } + } + + partition_keys { + name = "my_column_1" + type = "int" + comment = "my_column_1_comment" + } + + partition_keys { + name = "my_column_2" + type = "string" + comment = "my_column_2_comment" + } + + parameters = { + param1 = "param1_val" + } +} + +resource "aws_glue_partition_index" "example" { + database_name = aws_glue_catalog_database.example.name + table_name = aws_glue_catalog_table.example.name + + partition_index { + index_name = "example" + keys = ["my_column_1", "my_column_2"] + } +} +``` + + +## Argument Reference + +The following arguments are required: + +* `table_name` - (Required) Name of the table. For Hive compatibility, this must be entirely lowercase. +* `database_name` - (Required) Name of the metadata database where the table metadata resides. For Hive compatibility, this must be all lowercase. +* `partition_index` - (Required) Configuration block for a partition index. See [`partition_index`](#partition_index) below. +* `catalog_id` - (Optional) The catalog ID where the table resides. + + +### partition_index + +* `index_name` - (Required) Name of the partition index. +* `keys` - (Required) Keys for the partition index. + +## Attributes Reference + +In addition to all arguments above, the following attributes are exported: + +* `id` - Catalog ID, Database name, table name, and index name. + +## Import + +Glue Partition Indexes can be imported with their catalog ID (usually AWS account ID), database name, table name, and index name, e.g. + +``` +$ terraform import aws_glue_partition_index.example 123456789012:MyDatabase:MyTable:index-name +```