diff --git a/.changelog/35429.txt b/.changelog/35429.txt new file mode 100644 index 00000000000..ddd1f8736a0 --- /dev/null +++ b/.changelog/35429.txt @@ -0,0 +1,3 @@ +```release-note:new-resource +aws_rekognition_project +``` \ No newline at end of file diff --git a/internal/service/rekognition/exports_test.go b/internal/service/rekognition/exports_test.go new file mode 100644 index 00000000000..1c9fbfdbb26 --- /dev/null +++ b/internal/service/rekognition/exports_test.go @@ -0,0 +1,14 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package rekognition + +// Exports for use in tests only. + +var ( + ResourceProject = newResourceProject +) + +var ( + FindProjectByName = findProjectByName +) diff --git a/internal/service/rekognition/project.go b/internal/service/rekognition/project.go new file mode 100644 index 00000000000..77fa6561647 --- /dev/null +++ b/internal/service/rekognition/project.go @@ -0,0 +1,344 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package rekognition + +import ( + "context" + "errors" + "time" + + "github.com/aws/aws-sdk-go-v2/service/rekognition" + awstypes "github.com/aws/aws-sdk-go-v2/service/rekognition/types" + "github.com/hashicorp/terraform-plugin-framework-timeouts/resource/timeouts" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry" + "github.com/hashicorp/terraform-provider-aws/internal/create" + "github.com/hashicorp/terraform-provider-aws/internal/enum" + "github.com/hashicorp/terraform-provider-aws/internal/errs" + "github.com/hashicorp/terraform-provider-aws/internal/framework" + "github.com/hashicorp/terraform-provider-aws/internal/framework/flex" + fwtypes "github.com/hashicorp/terraform-provider-aws/internal/framework/types" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" + "github.com/hashicorp/terraform-provider-aws/names" +) + +// @FrameworkResource(name="Project") +func newResourceProject(_ context.Context) (resource.ResourceWithConfigure, error) { + r := &resourceProject{} + + r.SetDefaultCreateTimeout(10 * time.Minute) + r.SetDefaultDeleteTimeout(10 * time.Minute) + + return r, nil +} + +type resourceProject struct { + framework.ResourceWithConfigure + framework.WithTimeouts + framework.WithImportByID +} + +const ( + ResNameProject = "Project" +) + +func (r *resourceProject) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = "aws_rekognition_project" +} + +func (r *resourceProject) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "arn": framework.ARNAttributeComputedOnly(), + "auto_update": schema.StringAttribute{ + CustomType: fwtypes.StringEnumType[awstypes.ProjectAutoUpdate](), + Optional: true, + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + stringplanmodifier.UseStateForUnknown(), + }, + }, + "feature": schema.StringAttribute{ + CustomType: fwtypes.StringEnumType[awstypes.CustomizationFeature](), + Optional: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "id": framework.IDAttribute(), + "name": schema.StringAttribute{ + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + }, + Blocks: map[string]schema.Block{ + "timeouts": timeouts.Block(ctx, timeouts.Opts{ + Create: true, + Delete: true, + }), + }, + } +} + +func (r *resourceProject) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + conn := r.Meta().RekognitionClient(ctx) + + var plan resourceProjectData + resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) + if resp.Diagnostics.HasError() { + return + } + + in := rekognition.CreateProjectInput{} + + resp.Diagnostics.Append(flex.Expand(ctx, plan, &in)...) + if resp.Diagnostics.HasError() { + return + } + + in.ProjectName = flex.StringFromFramework(ctx, plan.Name) + + if plan.Feature.ValueEnum() == awstypes.CustomizationFeatureCustomLabels { + in.AutoUpdate = "" + } + + out, err := conn.CreateProject(ctx, &in) + if err != nil { + resp.Diagnostics.AddError( + create.ProblemStandardMessage(names.Rekognition, create.ErrActionCreating, ResNameProject, plan.Name.ValueString(), err), + err.Error(), + ) + return + } + + if out == nil || out.ProjectArn == nil { + resp.Diagnostics.AddError( + create.ProblemStandardMessage(names.Rekognition, create.ErrActionCreating, ResNameProject, plan.Name.ValueString(), nil), + errors.New("empty output").Error(), + ) + return + } + + state := plan + state.ARN = flex.StringToFramework(ctx, out.ProjectArn) + state.ID = state.Name + + // API returns empty string so we set a null + if state.Feature.ValueEnum() == awstypes.CustomizationFeatureCustomLabels { + state.AutoUpdate = fwtypes.StringEnumNull[awstypes.ProjectAutoUpdate]() + } + + createTimeout := r.CreateTimeout(ctx, state.Timeouts) + _, err = waitProjectCreated(ctx, conn, state.ID.ValueString(), in.Feature, createTimeout) + if err != nil { + resp.Diagnostics.AddError( + create.ProblemStandardMessage(names.Rekognition, create.ErrActionWaitingForCreation, ResNameProject, state.ID.ValueString(), err), + err.Error(), + ) + return + } + + resp.Diagnostics.Append(resp.State.Set(ctx, &state)...) +} + +func (r *resourceProject) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + conn := r.Meta().RekognitionClient(ctx) + + var state resourceProjectData + + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } + + out, err := findProjectByName(ctx, conn, state.ID.ValueString(), awstypes.CustomizationFeature(state.Feature.ValueString())) + + if tfresource.NotFound(err) { + resp.State.RemoveResource(ctx) + return + } + + if err != nil { + resp.Diagnostics.AddError( + create.ProblemStandardMessage(names.Rekognition, create.ErrActionReading, ResNameProject, state.ID.ValueString(), err), + err.Error(), + ) + return + } + + resp.Diagnostics.Append(flex.Flatten(ctx, out, &state)...) + if resp.Diagnostics.HasError() { + return + } + + state.Name = state.ID + state.ARN = flex.StringToFramework(ctx, out.ProjectArn) + + if state.Feature.ValueString() == "" { + // API returns empty string for default CUSTOM_LABELS value, so we have to set it forcibly to avoid drift + state.Feature = fwtypes.StringEnumValue(awstypes.CustomizationFeatureCustomLabels) + } + + // API returns empty string for default DISABLED value, so we have to set it forcibly to avoid drift + if state.AutoUpdate.ValueString() == "" { + if state.Feature.ValueEnum() == awstypes.CustomizationFeatureCustomLabels { + state.AutoUpdate = fwtypes.StringEnumNull[awstypes.ProjectAutoUpdate]() + } else { + state.AutoUpdate = fwtypes.StringEnumValue(awstypes.ProjectAutoUpdateDisabled) + } + } + + resp.Diagnostics.Append(resp.State.Set(ctx, &state)...) +} + +func (r *resourceProject) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var plan resourceProjectData + resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) + + if resp.Diagnostics.HasError() { + return + } + + resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...) +} + +func (r *resourceProject) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + conn := r.Meta().RekognitionClient(ctx) + + var state resourceProjectData + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } + + in := &rekognition.DeleteProjectInput{ + ProjectArn: state.ARN.ValueStringPointer(), + } + + _, err := conn.DeleteProject(ctx, in) + + if errs.IsA[*awstypes.ResourceNotFoundException](err) { + return + } + + if err != nil { + resp.Diagnostics.AddError( + create.ProblemStandardMessage(names.Rekognition, create.ErrActionDeleting, ResNameProject, state.ID.ValueString(), err), + err.Error(), + ) + } + + deleteTimeout := r.DeleteTimeout(ctx, state.Timeouts) + _, err = waitProjectDeleted(ctx, conn, state.ID.ValueString(), state.Feature.ValueEnum(), deleteTimeout) + if err != nil { + resp.Diagnostics.AddError( + create.ProblemStandardMessage(names.Rekognition, create.ErrActionWaitingForDeletion, ResNameProject, state.ID.ValueString(), err), + err.Error(), + ) + return + } +} + +func waitProjectCreated(ctx context.Context, conn *rekognition.Client, name string, feature awstypes.CustomizationFeature, timeout time.Duration) (*awstypes.ProjectDescription, error) { + stateConf := &retry.StateChangeConf{ + Pending: enum.Slice(awstypes.ProjectStatusCreating), + Target: enum.Slice(awstypes.ProjectStatusCreated), + Refresh: statusProject(ctx, conn, name, feature), + Timeout: timeout, + NotFoundChecks: 20, + ContinuousTargetOccurence: 2, + } + + outputRaw, err := stateConf.WaitForStateContext(ctx) + if out, ok := outputRaw.(*awstypes.ProjectDescription); ok { + return out, err + } + + return nil, err +} + +func waitProjectDeleted(ctx context.Context, conn *rekognition.Client, name string, feature awstypes.CustomizationFeature, timeout time.Duration) (*awstypes.ProjectDescription, error) { + stateConf := &retry.StateChangeConf{ + Pending: enum.Slice(awstypes.ProjectStatusDeleting), + Target: []string{}, + Refresh: statusProject(ctx, conn, name, feature), + Timeout: timeout, + NotFoundChecks: 20, + ContinuousTargetOccurence: 2, + } + + outputRaw, err := stateConf.WaitForStateContext(ctx) + if out, ok := outputRaw.(*awstypes.ProjectDescription); ok { + return out, err + } + + return nil, err +} + +func findProjectByName(ctx context.Context, conn *rekognition.Client, name string, feature awstypes.CustomizationFeature) (*awstypes.ProjectDescription, error) { + features := []awstypes.CustomizationFeature{} + if len((string)(feature)) == 0 { + // we don't know the type on import, so we lookup both + features = append(features, awstypes.CustomizationFeatureContentModeration, awstypes.CustomizationFeatureCustomLabels) + } else { + features = append(features, feature) + } + + in := &rekognition.DescribeProjectsInput{ + ProjectNames: []string{ + name, + }, + Features: features, + } + + out, err := conn.DescribeProjects(ctx, in) + + if errs.IsA[*awstypes.ResourceNotFoundException](err) { + return nil, &retry.NotFoundError{ + LastError: err, + LastRequest: in, + } + } + + if err != nil { + return nil, err + } + + if out == nil || len(out.ProjectDescriptions) == 0 { + return nil, tfresource.NewEmptyResultError(in) + } + + return &out.ProjectDescriptions[0], nil +} + +func statusProject(ctx context.Context, conn *rekognition.Client, name string, feature awstypes.CustomizationFeature) retry.StateRefreshFunc { + return func() (interface{}, string, error) { + out, err := findProjectByName(ctx, conn, name, feature) + if tfresource.NotFound(err) { + return nil, "", nil + } + + if err != nil { + return nil, "", err + } + + return out, string(out.Status), nil + } +} + +type resourceProjectData struct { + ARN types.String `tfsdk:"arn"` + AutoUpdate fwtypes.StringEnum[awstypes.ProjectAutoUpdate] `tfsdk:"auto_update"` + Feature fwtypes.StringEnum[awstypes.CustomizationFeature] `tfsdk:"feature"` + ID types.String `tfsdk:"id"` + Name types.String `tfsdk:"name"` + Timeouts timeouts.Value `tfsdk:"timeouts"` +} diff --git a/internal/service/rekognition/project_test.go b/internal/service/rekognition/project_test.go new file mode 100644 index 00000000000..6a7369c8228 --- /dev/null +++ b/internal/service/rekognition/project_test.go @@ -0,0 +1,250 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package rekognition_test + +import ( + "context" + "errors" + "fmt" + "testing" + + "github.com/aws/aws-sdk-go-v2/service/rekognition" + awstypes "github.com/aws/aws-sdk-go-v2/service/rekognition/types" + sdkacctest "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/terraform" + "github.com/hashicorp/terraform-provider-aws/internal/acctest" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/create" + tfrekognition "github.com/hashicorp/terraform-provider-aws/internal/service/rekognition" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" + "github.com/hashicorp/terraform-provider-aws/names" +) + +func TestAccRekognitionProject_basic(t *testing.T) { + ctx := acctest.Context(t) + + rProjectId := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_rekognition_project.test" + feature := "CONTENT_MODERATION" + autoUpdate := "ENABLED" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(ctx, t) + acctest.PreCheckPartitionHasService(t, names.Rekognition) + testAccProjectPreCheck(ctx, t) + }, + ErrorCheck: acctest.ErrorCheck(t, names.Rekognition), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckProjectDestroy(ctx, feature, rProjectId), + Steps: []resource.TestStep{ + { + Config: testAccProjectConfig_contentModeration(rProjectId, autoUpdate), + Check: resource.ComposeTestCheckFunc( + testAccCheckProjectExists(ctx, resourceName), + resource.TestCheckResourceAttr(resourceName, "id", rProjectId), + resource.TestCheckResourceAttr(resourceName, "name", rProjectId), + resource.TestCheckResourceAttrSet(resourceName, "arn"), + resource.TestCheckResourceAttr(resourceName, "auto_update", autoUpdate), + resource.TestCheckResourceAttr(resourceName, "feature", feature), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccRekognitionProject_ContentModeration(t *testing.T) { + ctx := acctest.Context(t) + + rProjectId := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_rekognition_project.test" + feature := "CONTENT_MODERATION" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(ctx, t) + acctest.PreCheckPartitionHasService(t, names.Rekognition) + testAccProjectPreCheck(ctx, t) + }, + ErrorCheck: acctest.ErrorCheck(t, names.Rekognition), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + Steps: []resource.TestStep{ + { + Config: testAccProjectConfig_contentModeration(rProjectId+"-1", "ENABLED"), + Check: resource.ComposeTestCheckFunc( + testAccCheckProjectExists(ctx, resourceName), + resource.TestCheckResourceAttr(resourceName, "id", rProjectId+"-1"), + resource.TestCheckResourceAttr(resourceName, "name", rProjectId+"-1"), + resource.TestCheckResourceAttrSet(resourceName, "arn"), + resource.TestCheckResourceAttr(resourceName, "auto_update", "ENABLED"), + resource.TestCheckResourceAttr(resourceName, "feature", feature), + ), + }, + { + Config: testAccProjectConfig_contentModeration(rProjectId+"-2", "DISABLED"), + Check: resource.ComposeTestCheckFunc( + testAccCheckProjectExists(ctx, resourceName), + resource.TestCheckResourceAttr(resourceName, "id", rProjectId+"-2"), + resource.TestCheckResourceAttr(resourceName, "name", rProjectId+"-2"), + resource.TestCheckResourceAttrSet(resourceName, "arn"), + resource.TestCheckResourceAttr(resourceName, "auto_update", "DISABLED"), + resource.TestCheckResourceAttr(resourceName, "feature", feature), + ), + }, + }, + }) +} + +func TestAccRekognitionProject_CustomLabels(t *testing.T) { + ctx := acctest.Context(t) + + rProjectId := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_rekognition_project.test" + feature := "CUSTOM_LABELS" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(ctx, t) + acctest.PreCheckPartitionHasService(t, names.Rekognition) + testAccProjectPreCheck(ctx, t) + }, + ErrorCheck: acctest.ErrorCheck(t, names.Rekognition), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckProjectDestroy(ctx, feature, rProjectId), + Steps: []resource.TestStep{ + { + Config: testAccProjectConfig_customLabels(rProjectId), + Check: resource.ComposeTestCheckFunc( + testAccCheckProjectExists(ctx, resourceName), + resource.TestCheckResourceAttrSet(resourceName, "arn"), + resource.TestCheckResourceAttr(resourceName, "id", rProjectId), + resource.TestCheckResourceAttr(resourceName, "name", rProjectId), + resource.TestCheckResourceAttr(resourceName, "feature", feature), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccRekognitionProject_disappears(t *testing.T) { + ctx := acctest.Context(t) + + rProjectId := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_rekognition_project.test" + feature := "CONTENT_MODERATION" + autoUpdate := "ENABLED" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(ctx, t) + acctest.PreCheckPartitionHasService(t, names.Rekognition) + testAccProjectPreCheck(ctx, t) + }, + ErrorCheck: acctest.ErrorCheck(t, names.Rekognition), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckProjectDestroy(ctx, feature, rProjectId), + Steps: []resource.TestStep{ + { + Config: testAccProjectConfig_contentModeration(rProjectId, autoUpdate), + Check: resource.ComposeTestCheckFunc( + testAccCheckProjectExists(ctx, resourceName), + acctest.CheckFrameworkResourceDisappears(ctx, acctest.Provider, tfrekognition.ResourceProject, resourceName), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +func testAccCheckProjectExists(ctx context.Context, name string) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[name] + if !ok { + return create.Error(names.Rekognition, create.ErrActionCheckingExistence, tfrekognition.ResNameProject, name, errors.New("not found")) + } + + if rs.Primary.ID == "" { + return create.Error(names.Rekognition, create.ErrActionCheckingExistence, tfrekognition.ResNameProject, name, errors.New("not set")) + } + + conn := acctest.Provider.Meta().(*conns.AWSClient).RekognitionClient(ctx) + _, err := tfrekognition.FindProjectByName(ctx, conn, rs.Primary.ID, "") + + if err != nil { + return create.Error(names.Rekognition, create.ErrActionCheckingExistence, tfrekognition.ResNameProject, rs.Primary.ID, err) + } + + return nil + } +} + +func testAccCheckProjectDestroy(ctx context.Context, feature string, name string) resource.TestCheckFunc { + return func(s *terraform.State) error { + conn := acctest.Provider.Meta().(*conns.AWSClient).RekognitionClient(ctx) + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_rekognition_project" { + continue + } + + _, err := tfrekognition.FindProjectByName(ctx, conn, name, awstypes.CustomizationFeature(feature)) + if tfresource.NotFound(err) { + continue + } + + if err != nil { + return err + } + + return create.Error(names.Rekognition, create.ErrActionCheckingDestroyed, tfrekognition.ResNameProject, rs.Primary.ID, errors.New("not destroyed")) + } + + return nil + } +} + +func testAccProjectPreCheck(ctx context.Context, t *testing.T) { + conn := acctest.Provider.Meta().(*conns.AWSClient).RekognitionClient(ctx) + + input := &rekognition.DescribeProjectsInput{} + _, err := conn.DescribeProjects(ctx, input) + + if acctest.PreCheckSkipError(err) { + t.Skipf("skipping acceptance testing: %s", err) + } + if err != nil { + t.Fatalf("unexpected PreCheck error: %s", err) + } +} + +func testAccProjectConfig_contentModeration(rProjectId string, autoUpdate string) string { + return fmt.Sprintf(` +resource "aws_rekognition_project" "test" { + name = %[1]q + auto_update = %[2]q + feature = "CONTENT_MODERATION" +} +`, rProjectId, autoUpdate) +} + +// auto-update not supported for custom_labels +func testAccProjectConfig_customLabels(rProjectId string) string { + return fmt.Sprintf(` +resource "aws_rekognition_project" "test" { + name = %[1]q + feature = "CUSTOM_LABELS" +} +`, rProjectId) +} diff --git a/internal/service/rekognition/service_package_gen.go b/internal/service/rekognition/service_package_gen.go index 8002e5542d4..dcf8ddf60a0 100644 --- a/internal/service/rekognition/service_package_gen.go +++ b/internal/service/rekognition/service_package_gen.go @@ -19,7 +19,12 @@ func (p *servicePackage) FrameworkDataSources(ctx context.Context) []*types.Serv } func (p *servicePackage) FrameworkResources(ctx context.Context) []*types.ServicePackageFrameworkResource { - return []*types.ServicePackageFrameworkResource{} + return []*types.ServicePackageFrameworkResource{ + { + Factory: newResourceProject, + Name: "Project", + }, + } } func (p *servicePackage) SDKDataSources(ctx context.Context) []*types.ServicePackageSDKDataSource { diff --git a/website/docs/r/rekognition_project.html.markdown b/website/docs/r/rekognition_project.html.markdown new file mode 100644 index 00000000000..9ebc6e3e74e --- /dev/null +++ b/website/docs/r/rekognition_project.html.markdown @@ -0,0 +1,62 @@ +--- +subcategory: "Rekognition" +layout: "aws" +page_title: "AWS: aws_rekognition_project" +description: |- + Terraform resource for managing an AWS Rekognition Project. +--- + +# Resource: aws_rekognition_project + +Terraform resource for managing an AWS Rekognition Project. + +## Example Usage + +```terraform +resource "aws_rekognition_project" "example" { + name = "example-project" + auto_update = "ENABLED" + feature = "CONTENT_MODERATION" +} +``` + +## Argument Reference + +The following arguments are required: + +* `name` - (Required) Desired name of the project + +The following arguments are optional: + +* `auto_update` - (Optional) Specify if automatic retraining should occur. Valid values are `ENABLED` or `DISABLED`. Defaults to `DISABLED` +* `feature` - (Optional) Specify the feature being customized. Valid values are `CONTENT_MODERATION` or `CUSTOM_LABELS`. Defaults to `CUSTOM_LABELS` + +## Attribute Reference + +This resource exports the following attributes in addition to the arguments above: + +* `arn` - ARN of the Project. + +## Timeouts + +[Configuration options](https://developer.hashicorp.com/terraform/language/resources/syntax#operation-timeouts): + +* `create` - (Default `10m`) +* `delete` - (Default `10m`) + +## Import + +In Terraform v1.5.0 and later, use an [`import` block](https://developer.hashicorp.com/terraform/language/import) to import Rekognition Project using the `example_id_arg`. For example: + +```terraform +import { + to = aws_rekognition_project.example + id = "project-id-12345678" +} +``` + +Using `terraform import`, import Rekognition Project using the `name`. For example: + +```console +% terraform import aws_rekognition_project.example project-id-12345678 +```