diff --git a/aws/data_source_aws_lex_intent.go b/aws/data_source_aws_lex_intent.go new file mode 100644 index 00000000000..aadc71b1918 --- /dev/null +++ b/aws/data_source_aws_lex_intent.go @@ -0,0 +1,97 @@ +package aws + +import ( + "fmt" + "regexp" + "time" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/arn" + "github.com/aws/aws-sdk-go/service/lexmodelbuildingservice" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" +) + +func dataSourceAwsLexIntent() *schema.Resource { + return &schema.Resource{ + Read: dataSourceAwsLexIntentRead, + + Schema: map[string]*schema.Schema{ + "arn": { + Type: schema.TypeString, + Computed: true, + }, + "checksum": { + Type: schema.TypeString, + Computed: true, + }, + "created_date": { + Type: schema.TypeString, + Computed: true, + }, + "description": { + Type: schema.TypeString, + Computed: true, + }, + "last_updated_date": { + Type: schema.TypeString, + Computed: true, + }, + "name": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.All( + validation.StringLenBetween(1, 100), + validation.StringMatch(regexp.MustCompile(`^([A-Za-z]_?)+$`), ""), + ), + }, + "parent_intent_signature": { + Type: schema.TypeString, + Computed: true, + }, + "version": { + Type: schema.TypeString, + Optional: true, + Default: LexIntentVersionLatest, + ValidateFunc: validation.All( + validation.StringLenBetween(1, 64), + validation.StringMatch(regexp.MustCompile(`\$LATEST|[0-9]+`), ""), + ), + }, + }, + } +} + +func dataSourceAwsLexIntentRead(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).lexmodelconn + + intentName := d.Get("name").(string) + resp, err := conn.GetIntent(&lexmodelbuildingservice.GetIntentInput{ + Name: aws.String(intentName), + Version: aws.String(d.Get("version").(string)), + }) + if err != nil { + return fmt.Errorf("error getting intent %s: %w", intentName, err) + } + + arn := arn.ARN{ + Partition: meta.(*AWSClient).partition, + Region: meta.(*AWSClient).region, + Service: "lex", + AccountID: meta.(*AWSClient).accountid, + Resource: fmt.Sprintf("intent:%s", d.Get("name").(string)), + } + d.Set("arn", arn.String()) + + d.Set("checksum", resp.Checksum) + d.Set("created_date", resp.CreatedDate.Format(time.RFC3339)) + d.Set("description", resp.Description) + d.Set("last_updated_date", resp.LastUpdatedDate.Format(time.RFC3339)) + d.Set("name", resp.Name) + d.Set("parent_intent_signature", resp.ParentIntentSignature) + d.Set("version", resp.Version) + + d.SetId(intentName) + + return nil +} diff --git a/aws/data_source_aws_lex_intent_test.go b/aws/data_source_aws_lex_intent_test.go new file mode 100644 index 00000000000..d501cfd27cb --- /dev/null +++ b/aws/data_source_aws_lex_intent_test.go @@ -0,0 +1,81 @@ +package aws + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" +) + +func TestAccDataSourceAwsLexIntent_basic(t *testing.T) { + rName := acctest.RandStringFromCharSet(8, acctest.CharSetAlpha) + dataSourceName := "data.aws_lex_intent.test" + resourceName := "aws_lex_intent.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: composeConfig( + testAccAwsLexIntentConfig_basic(rName), + testAccDataSourceAwsLexIntentConfig_basic(), + ), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttrPair(dataSourceName, "arn", resourceName, "arn"), + resource.TestCheckResourceAttrPair(dataSourceName, "checksum", resourceName, "checksum"), + resource.TestCheckResourceAttrPair(dataSourceName, "created_date", resourceName, "created_date"), + resource.TestCheckResourceAttrPair(dataSourceName, "description", resourceName, "description"), + resource.TestCheckResourceAttrPair(dataSourceName, "last_updated_date", resourceName, "last_updated_date"), + resource.TestCheckResourceAttrPair(dataSourceName, "name", resourceName, "name"), + resource.TestCheckResourceAttrPair(dataSourceName, "version", resourceName, "version"), + ), + }, + }, + }) +} + +func TestAccDataSourceAwsLexIntent_withVersion(t *testing.T) { + rName := acctest.RandStringFromCharSet(8, acctest.CharSetAlpha) + dataSourceName := "data.aws_lex_intent.test" + resourceName := "aws_lex_intent.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: composeConfig( + testAccAwsLexIntentConfig_createVersion(rName), + testAccDataSourceAwsLexIntentConfig_withVersion(), + ), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttrPair(dataSourceName, "arn", resourceName, "arn"), + resource.TestCheckResourceAttrPair(dataSourceName, "checksum", resourceName, "checksum"), + resource.TestCheckResourceAttrPair(dataSourceName, "created_date", resourceName, "created_date"), + resource.TestCheckResourceAttrPair(dataSourceName, "description", resourceName, "description"), + resource.TestCheckResourceAttrPair(dataSourceName, "last_updated_date", resourceName, "last_updated_date"), + resource.TestCheckResourceAttrPair(dataSourceName, "name", resourceName, "name"), + resource.TestCheckResourceAttrPair(dataSourceName, "version", resourceName, "version"), + ), + }, + }, + }) +} + +func testAccDataSourceAwsLexIntentConfig_basic() string { + return ` +data "aws_lex_intent" "test" { + name = aws_lex_intent.test.name +} +` +} + +func testAccDataSourceAwsLexIntentConfig_withVersion() string { + return ` +data "aws_lex_intent" "test" { + name = aws_lex_intent.test.name + version = "1" +} +` +} diff --git a/aws/data_source_aws_lex_slot_type_test.go b/aws/data_source_aws_lex_slot_type_test.go index 7438f1b3e84..5647533a135 100644 --- a/aws/data_source_aws_lex_slot_type_test.go +++ b/aws/data_source_aws_lex_slot_type_test.go @@ -23,15 +23,13 @@ func TestAccDataSourceAwsLexSlotType_basic(t *testing.T) { ), Check: resource.ComposeAggregateTestCheckFunc( resource.TestCheckResourceAttrPair(dataSourceName, "checksum", resourceName, "checksum"), + resource.TestCheckResourceAttrPair(dataSourceName, "created_date", resourceName, "created_date"), resource.TestCheckResourceAttrPair(dataSourceName, "description", resourceName, "description"), resource.TestCheckResourceAttrPair(dataSourceName, "enumeration_value.#", resourceName, "enumeration_value.#"), + resource.TestCheckResourceAttrPair(dataSourceName, "last_updated_date", resourceName, "last_updated_date"), resource.TestCheckResourceAttrPair(dataSourceName, "name", resourceName, "name"), resource.TestCheckResourceAttrPair(dataSourceName, "value_selection_strategy", resourceName, "value_selection_strategy"), resource.TestCheckResourceAttrPair(dataSourceName, "version", resourceName, "version"), - resource.TestCheckResourceAttrPair(dataSourceName, "created_date", resourceName, "created_date"), - testAccCheckResourceAttrRfc3339(dataSourceName, "created_date"), - resource.TestCheckResourceAttrPair(dataSourceName, "last_updated_date", resourceName, "last_updated_date"), - testAccCheckResourceAttrRfc3339(dataSourceName, "last_updated_date"), ), }, }, @@ -54,12 +52,12 @@ func TestAccDataSourceAwsLexSlotType_withVersion(t *testing.T) { ), Check: resource.ComposeAggregateTestCheckFunc( resource.TestCheckResourceAttrPair(dataSourceName, "checksum", resourceName, "checksum"), + resource.TestCheckResourceAttrPair(dataSourceName, "created_date", resourceName, "created_date"), resource.TestCheckResourceAttrPair(dataSourceName, "description", resourceName, "description"), + resource.TestCheckResourceAttrPair(dataSourceName, "last_updated_date", resourceName, "last_updated_date"), resource.TestCheckResourceAttrPair(dataSourceName, "name", resourceName, "name"), resource.TestCheckResourceAttrPair(dataSourceName, "value_selection_strategy", resourceName, "value_selection_strategy"), resource.TestCheckResourceAttrPair(dataSourceName, "version", resourceName, "version"), - testAccCheckResourceAttrRfc3339(dataSourceName, "created_date"), - testAccCheckResourceAttrRfc3339(dataSourceName, "last_updated_date"), ), }, }, diff --git a/aws/internal/service/lex/waiter/status.go b/aws/internal/service/lex/waiter/status.go index 7edbfb6a319..7c0b09703ec 100644 --- a/aws/internal/service/lex/waiter/status.go +++ b/aws/internal/service/lex/waiter/status.go @@ -32,3 +32,23 @@ func LexSlotTypeStatus(conn *lexmodelbuildingservice.LexModelBuildingService, id return output, LexModelBuildingServiceStatusCreated, nil } } + +func LexIntentStatus(conn *lexmodelbuildingservice.LexModelBuildingService, id string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + output, err := conn.GetIntentVersions(&lexmodelbuildingservice.GetIntentVersionsInput{ + Name: aws.String(id), + }) + if tfawserr.ErrCodeEquals(err, lexmodelbuildingservice.ErrCodeNotFoundException) { + return nil, LexModelBuildingServiceStatusNotFound, nil + } + if err != nil { + return nil, LexModelBuildingServiceStatusUnknown, err + } + + if output == nil || len(output.Intents) == 0 { + return nil, LexModelBuildingServiceStatusNotFound, nil + } + + return output, LexModelBuildingServiceStatusCreated, nil + } +} diff --git a/aws/internal/service/lex/waiter/waiter.go b/aws/internal/service/lex/waiter/waiter.go index 6cec69c7c20..0689ce49666 100644 --- a/aws/internal/service/lex/waiter/waiter.go +++ b/aws/internal/service/lex/waiter/waiter.go @@ -9,8 +9,25 @@ import ( const ( LexSlotTypeDeleteTimeout = 5 * time.Minute + LexIntentDeleteTimeout = 5 * time.Minute ) +func LexIntentDeleted(conn *lexmodelbuildingservice.LexModelBuildingService, intentId string) (*lexmodelbuildingservice.GetIntentVersionsOutput, error) { + stateChangeConf := &resource.StateChangeConf{ + Pending: []string{LexModelBuildingServiceStatusCreated}, + Target: []string{}, // An empty slice indicates that the resource is gone + Refresh: LexIntentStatus(conn, intentId), + Timeout: LexIntentDeleteTimeout, + } + outputRaw, err := stateChangeConf.WaitForState() + + if v, ok := outputRaw.(*lexmodelbuildingservice.GetIntentVersionsOutput); ok { + return v, err + } + + return nil, err +} + func LexSlotTypeDeleted(conn *lexmodelbuildingservice.LexModelBuildingService, slotTypeId string) (*lexmodelbuildingservice.GetSlotTypeVersionsOutput, error) { stateChangeConf := &resource.StateChangeConf{ Pending: []string{LexModelBuildingServiceStatusCreated}, diff --git a/aws/provider.go b/aws/provider.go index 76703558194..68927794f0e 100644 --- a/aws/provider.go +++ b/aws/provider.go @@ -282,6 +282,7 @@ func Provider() *schema.Provider { "aws_lambda_layer_version": dataSourceAwsLambdaLayerVersion(), "aws_launch_configuration": dataSourceAwsLaunchConfiguration(), "aws_launch_template": dataSourceAwsLaunchTemplate(), + "aws_lex_intent": dataSourceAwsLexIntent(), "aws_lex_slot_type": dataSourceAwsLexSlotType(), "aws_mq_broker": dataSourceAwsMqBroker(), "aws_msk_cluster": dataSourceAwsMskCluster(), @@ -706,6 +707,7 @@ func Provider() *schema.Provider { "aws_lambda_provisioned_concurrency_config": resourceAwsLambdaProvisionedConcurrencyConfig(), "aws_launch_configuration": resourceAwsLaunchConfiguration(), "aws_launch_template": resourceAwsLaunchTemplate(), + "aws_lex_intent": resourceAwsLexIntent(), "aws_lex_slot_type": resourceAwsLexSlotType(), "aws_licensemanager_association": resourceAwsLicenseManagerAssociation(), "aws_licensemanager_license_configuration": resourceAwsLicenseManagerLicenseConfiguration(), diff --git a/aws/resource_aws_lex_intent.go b/aws/resource_aws_lex_intent.go new file mode 100644 index 00000000000..d5b6dbec2cb --- /dev/null +++ b/aws/resource_aws_lex_intent.go @@ -0,0 +1,836 @@ +package aws + +import ( + "fmt" + "log" + "regexp" + "time" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/arn" + "github.com/aws/aws-sdk-go/service/lexmodelbuildingservice" + "github.com/hashicorp/aws-sdk-go-base/tfawserr" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/lex/waiter" +) + +const ( + LexIntentCreateTimeout = 1 * time.Minute + LexIntentUpdateTimeout = 1 * time.Minute + LexIntentDeleteTimeout = 5 * time.Minute + LexIntentVersionLatest = "$LATEST" +) + +func resourceAwsLexIntent() *schema.Resource { + return &schema.Resource{ + Create: resourceAwsLexIntentCreate, + Read: resourceAwsLexIntentRead, + Update: resourceAwsLexIntentUpdate, + Delete: resourceAwsLexIntentDelete, + + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(LexIntentCreateTimeout), + Update: schema.DefaultTimeout(LexIntentUpdateTimeout), + Delete: schema.DefaultTimeout(LexIntentDeleteTimeout), + }, + + Schema: map[string]*schema.Schema{ + "arn": { + Type: schema.TypeString, + Computed: true, + }, + "checksum": { + Type: schema.TypeString, + Computed: true, + }, + "conclusion_statement": { + Type: schema.TypeList, + Optional: true, + MinItems: 1, + MaxItems: 1, + ConflictsWith: []string{"follow_up_prompt"}, + Elem: lexStatementResource, + }, + "confirmation_prompt": { + Type: schema.TypeList, + Optional: true, + MinItems: 1, + MaxItems: 1, + RequiredWith: []string{"rejection_statement"}, + Elem: lexPromptResource, + }, + "create_version": { + Type: schema.TypeBool, + Optional: true, + Default: false, + }, + "created_date": { + Type: schema.TypeString, + Computed: true, + }, + "description": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringLenBetween(0, 200), + }, + "dialog_code_hook": { + Type: schema.TypeList, + Optional: true, + MinItems: 1, + MaxItems: 1, + Elem: lexCodeHookResource, + }, + "follow_up_prompt": { + Type: schema.TypeList, + Optional: true, + MinItems: 1, + MaxItems: 1, + ConflictsWith: []string{"conclusion_statement"}, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "prompt": { + Type: schema.TypeList, + Required: true, + MinItems: 1, + MaxItems: 1, + Elem: lexPromptResource, + }, + "rejection_statement": { + Type: schema.TypeList, + Required: true, + MinItems: 1, + MaxItems: 1, + Elem: lexStatementResource, + }, + }, + }, + }, + "fulfillment_activity": { + Type: schema.TypeList, + Required: true, + MinItems: 1, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "code_hook": { + Type: schema.TypeList, + Optional: true, + MinItems: 1, + MaxItems: 1, + Elem: lexCodeHookResource, + }, + "type": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice(lexmodelbuildingservice.FulfillmentActivityType_Values(), false), + }, + }, + }, + }, + "last_updated_date": { + Type: schema.TypeString, + Computed: true, + }, + "name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.All( + validation.StringLenBetween(1, 100), + validation.StringMatch(regexp.MustCompile(`^([A-Za-z]_?)+$`), ""), + ), + }, + "parent_intent_signature": { + Type: schema.TypeString, + Optional: true, + }, + "rejection_statement": { + Type: schema.TypeList, + Optional: true, + MinItems: 1, + MaxItems: 1, + RequiredWith: []string{"confirmation_prompt"}, + Elem: lexStatementResource, + }, + "sample_utterances": { + Type: schema.TypeSet, + Optional: true, + MinItems: 0, + MaxItems: 1500, + Elem: &schema.Schema{ + Type: schema.TypeString, + ValidateFunc: validation.StringLenBetween(1, 200), + }, + }, + "slot": { + Type: schema.TypeSet, + Optional: true, + MinItems: 0, + MaxItems: 100, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "description": { + Type: schema.TypeString, + Optional: true, + Default: "", + ValidateFunc: validation.StringLenBetween(0, 200), + }, + "name": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.All( + validation.StringLenBetween(1, 100), + validation.StringMatch(regexp.MustCompile(`^([A-Za-z]_?)+$`), ""), + ), + }, + "priority": { + Type: schema.TypeInt, + Optional: true, + Default: 0, + ValidateFunc: validation.IntBetween(0, 100), + }, + "response_card": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringLenBetween(1, 50000), + }, + "sample_utterances": { + Type: schema.TypeList, + Optional: true, + MinItems: 1, + MaxItems: 10, + Elem: &schema.Schema{ + Type: schema.TypeString, + ValidateFunc: validation.StringLenBetween(1, 200), + }, + }, + "slot_constraint": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice(lexmodelbuildingservice.SlotConstraint_Values(), false), + }, + "slot_type": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.All( + validation.StringLenBetween(1, 100), + validation.StringMatch(regexp.MustCompile(`^((AMAZON\.)_?|[A-Za-z]_?)+`), ""), + ), + }, + "slot_type_version": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.All( + validation.StringLenBetween(1, 64), + validation.StringMatch(regexp.MustCompile(`\$LATEST|[0-9]+`), ""), + ), + }, + "value_elicitation_prompt": { + Type: schema.TypeList, + Optional: true, + MinItems: 1, + MaxItems: 1, + Elem: lexPromptResource, + }, + }, + }, + }, + "version": { + Type: schema.TypeString, + Computed: true, + }, + }, + } +} + +func resourceAwsLexIntentCreate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).lexmodelconn + name := d.Get("name").(string) + + input := &lexmodelbuildingservice.PutIntentInput{ + CreateVersion: aws.Bool(d.Get("create_version").(bool)), + Description: aws.String(d.Get("description").(string)), + Name: aws.String(name), + } + + if v, ok := d.GetOk("conclusion_statement"); ok { + input.ConclusionStatement = expandLexStatement(v) + } + + if v, ok := d.GetOk("confirmation_prompt"); ok { + input.ConfirmationPrompt = expandLexPrompt(v) + } + + if v, ok := d.GetOk("dialog_code_hook"); ok { + input.DialogCodeHook = expandLexCodeHook(v) + } + + if v, ok := d.GetOk("follow_up_prompt"); ok { + input.FollowUpPrompt = expandLexFollowUpPrompt(v) + } + + if v, ok := d.GetOk("fulfillment_activity"); ok { + input.FulfillmentActivity = expandLexFulfilmentActivity(v) + } + + if v, ok := d.GetOk("parent_intent_signature"); ok { + input.ParentIntentSignature = aws.String(v.(string)) + } + + if v, ok := d.GetOk("rejection_statement"); ok { + input.RejectionStatement = expandLexStatement(v) + } + + if v, ok := d.GetOk("sample_utterances"); ok { + input.SampleUtterances = expandStringList(v.(*schema.Set).List()) + } + + if v, ok := d.GetOk("slot"); ok { + input.Slots = expandLexSlots(v.(*schema.Set).List()) + } + + err := resource.Retry(d.Timeout(schema.TimeoutCreate), func() *resource.RetryError { + output, err := conn.PutIntent(input) + + if tfawserr.ErrCodeEquals(err, lexmodelbuildingservice.ErrCodeConflictException) { + input.Checksum = output.Checksum + return resource.RetryableError(fmt.Errorf("%q intent still creating, another operation is pending: %s", d.Id(), err)) + } + if err != nil { + return resource.NonRetryableError(err) + } + + return nil + }) + if err != nil { + return fmt.Errorf("error creating intent %s: %w", name, err) + } + + d.SetId(name) + + return resourceAwsLexIntentRead(d, meta) +} + +func resourceAwsLexIntentRead(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).lexmodelconn + + resp, err := conn.GetIntent(&lexmodelbuildingservice.GetIntentInput{ + Name: aws.String(d.Id()), + Version: aws.String(LexIntentVersionLatest), + }) + if isAWSErr(err, lexmodelbuildingservice.ErrCodeNotFoundException, "") { + log.Printf("[WARN] Intent (%s) not found, removing from state", d.Id()) + d.SetId("") + return nil + } + if err != nil { + return fmt.Errorf("error getting intent %s: %w", d.Id(), err) + } + + arn := arn.ARN{ + Partition: meta.(*AWSClient).partition, + Region: meta.(*AWSClient).region, + Service: "lex", + AccountID: meta.(*AWSClient).accountid, + Resource: fmt.Sprintf("intent:%s", d.Id()), + } + d.Set("arn", arn.String()) + + d.Set("checksum", resp.Checksum) + d.Set("created_date", resp.CreatedDate.Format(time.RFC3339)) + d.Set("description", resp.Description) + d.Set("last_updated_date", resp.LastUpdatedDate.Format(time.RFC3339)) + d.Set("name", resp.Name) + + version, err := getLatestLexIntentVersion(conn, &lexmodelbuildingservice.GetIntentVersionsInput{ + Name: aws.String(d.Id()), + }) + if err != nil { + return fmt.Errorf("error reading version of intent %s: %w", d.Id(), err) + } + d.Set("version", version) + + if resp.ConclusionStatement != nil { + d.Set("conclusion_statement", flattenLexStatement(resp.ConclusionStatement)) + } + + if resp.ConfirmationPrompt != nil { + d.Set("confirmation_prompt", flattenLexPrompt(resp.ConfirmationPrompt)) + } + + if resp.DialogCodeHook != nil { + d.Set("dialog_code_hook", flattenLexCodeHook(resp.DialogCodeHook)) + } + + if resp.FollowUpPrompt != nil { + d.Set("follow_up_prompt", flattenLexFollowUpPrompt(resp.FollowUpPrompt)) + } + + if resp.FulfillmentActivity != nil { + d.Set("fulfillment_activity", flattenLexFulfilmentActivity(resp.FulfillmentActivity)) + } + + if resp.ParentIntentSignature != nil { + d.Set("parent_intent_signature", resp.ParentIntentSignature) + } + + if resp.RejectionStatement != nil { + d.Set("rejection_statement", flattenLexStatement(resp.RejectionStatement)) + } + + if resp.SampleUtterances != nil { + d.Set("sample_utterances", resp.SampleUtterances) + } + + if resp.Slots != nil { + d.Set("slot", flattenLexSlots(resp.Slots)) + } + + return nil +} + +func resourceAwsLexIntentUpdate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).lexmodelconn + + input := &lexmodelbuildingservice.PutIntentInput{ + Checksum: aws.String(d.Get("checksum").(string)), + CreateVersion: aws.Bool(d.Get("create_version").(bool)), + Description: aws.String(d.Get("description").(string)), + Name: aws.String(d.Id()), + } + + if v, ok := d.GetOk("conclusion_statement"); ok { + input.ConclusionStatement = expandLexStatement(v) + } + + if v, ok := d.GetOk("confirmation_prompt"); ok { + input.ConfirmationPrompt = expandLexPrompt(v) + } + + if v, ok := d.GetOk("dialog_code_hook"); ok { + input.DialogCodeHook = expandLexCodeHook(v) + } + + if v, ok := d.GetOk("follow_up_prompt"); ok { + input.FollowUpPrompt = expandLexFollowUpPrompt(v) + } + + if v, ok := d.GetOk("fulfillment_activity"); ok { + input.FulfillmentActivity = expandLexFulfilmentActivity(v) + } + + if v, ok := d.GetOk("parent_intent_signature"); ok { + input.ParentIntentSignature = aws.String(v.(string)) + } + + if v, ok := d.GetOk("rejection_statement"); ok { + input.RejectionStatement = expandLexStatement(v) + } + + if v, ok := d.GetOk("sample_utterances"); ok { + input.SampleUtterances = expandStringList(v.(*schema.Set).List()) + } + + if v, ok := d.GetOk("slot"); ok { + input.Slots = expandLexSlots(v.(*schema.Set).List()) + } + + err := resource.Retry(d.Timeout(schema.TimeoutUpdate), func() *resource.RetryError { + _, err := conn.PutIntent(input) + + if isAWSErr(err, lexmodelbuildingservice.ErrCodeConflictException, "") { + return resource.RetryableError(fmt.Errorf("%q: intent still updating", d.Id())) + } + if err != nil { + return resource.NonRetryableError(err) + } + + return nil + }) + if err != nil { + return fmt.Errorf("error updating intent %s: %w", d.Id(), err) + } + + return resourceAwsLexIntentRead(d, meta) +} + +func resourceAwsLexIntentDelete(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).lexmodelconn + + err := resource.Retry(d.Timeout(schema.TimeoutDelete), func() *resource.RetryError { + _, err := conn.DeleteIntent(&lexmodelbuildingservice.DeleteIntentInput{ + Name: aws.String(d.Id()), + }) + + if isAWSErr(err, lexmodelbuildingservice.ErrCodeConflictException, "") { + return resource.RetryableError(fmt.Errorf("%q: there is a pending operation, intent still deleting", d.Id())) + } + if err != nil { + return resource.NonRetryableError(err) + } + + return nil + }) + + if err != nil { + return fmt.Errorf("error deleting intent %s: %w", d.Id(), err) + } + + _, err = waiter.LexIntentDeleted(conn, d.Id()) + + return err +} + +var lexCodeHookResource = &schema.Resource{ + Schema: map[string]*schema.Schema{ + "message_version": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringLenBetween(1, 5), + }, + "uri": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validateArn, + }, + }, +} + +var lexMessageResource = &schema.Resource{ + Schema: map[string]*schema.Schema{ + "content": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringLenBetween(1, 1000), + }, + "content_type": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice(lexmodelbuildingservice.ContentType_Values(), false), + }, + "group_number": { + Type: schema.TypeInt, + Optional: true, + ValidateFunc: validation.IntBetween(1, 5), + }, + }, +} + +var lexPromptResource = &schema.Resource{ + Schema: map[string]*schema.Schema{ + "max_attempts": { + Type: schema.TypeInt, + Required: true, + ValidateFunc: validation.IntBetween(1, 5), + }, + "message": { + Type: schema.TypeSet, + Required: true, + MinItems: 1, + MaxItems: 15, + Elem: lexMessageResource, + }, + "response_card": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringLenBetween(1, 50000), + }, + }, +} + +var lexStatementResource = &schema.Resource{ + Schema: map[string]*schema.Schema{ + "message": { + Type: schema.TypeSet, + Required: true, + MinItems: 1, + MaxItems: 15, + Elem: lexMessageResource, + }, + "response_card": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringLenBetween(1, 50000), + }, + }, +} + +func getLatestLexIntentVersion(conn *lexmodelbuildingservice.LexModelBuildingService, input *lexmodelbuildingservice.GetIntentVersionsInput) (string, error) { + version := LexIntentVersionLatest + + for { + page, err := conn.GetIntentVersions(input) + if err != nil { + return "", err + } + + // At least 1 version will always be returned. + if len(page.Intents) == 1 { + break + } + + for _, intent := range page.Intents { + if *intent.Version == LexIntentVersionLatest { + continue + } + if *intent.Version > version { + version = *intent.Version + } + } + + if page.NextToken == nil { + break + } + input.NextToken = page.NextToken + } + + return version, nil +} + +func flattenLexCodeHook(hook *lexmodelbuildingservice.CodeHook) (flattened []map[string]interface{}) { + return []map[string]interface{}{ + { + "message_version": aws.StringValue(hook.MessageVersion), + "uri": aws.StringValue(hook.Uri), + }, + } +} + +func expandLexCodeHook(rawObject interface{}) (hook *lexmodelbuildingservice.CodeHook) { + m := rawObject.([]interface{})[0].(map[string]interface{}) + + return &lexmodelbuildingservice.CodeHook{ + MessageVersion: aws.String(m["message_version"].(string)), + Uri: aws.String(m["uri"].(string)), + } +} + +func flattenLexFollowUpPrompt(followUp *lexmodelbuildingservice.FollowUpPrompt) (flattened []map[string]interface{}) { + return []map[string]interface{}{ + { + "prompt": flattenLexPrompt(followUp.Prompt), + "rejection_statement": flattenLexStatement(followUp.RejectionStatement), + }, + } +} + +func expandLexFollowUpPrompt(rawObject interface{}) (followUp *lexmodelbuildingservice.FollowUpPrompt) { + m := rawObject.([]interface{})[0].(map[string]interface{}) + + return &lexmodelbuildingservice.FollowUpPrompt{ + Prompt: expandLexPrompt(m["prompt"]), + RejectionStatement: expandLexStatement(m["rejection_statement"]), + } +} + +func flattenLexFulfilmentActivity(activity *lexmodelbuildingservice.FulfillmentActivity) (flattened []map[string]interface{}) { + flattened = []map[string]interface{}{ + { + "type": aws.StringValue(activity.Type), + }, + } + + if activity.CodeHook != nil { + flattened[0]["code_hook"] = flattenLexCodeHook(activity.CodeHook) + } + + return +} + +func expandLexFulfilmentActivity(rawObject interface{}) (activity *lexmodelbuildingservice.FulfillmentActivity) { + m := rawObject.([]interface{})[0].(map[string]interface{}) + + activity = &lexmodelbuildingservice.FulfillmentActivity{} + activity.Type = aws.String(m["type"].(string)) + + if v, ok := m["code_hook"]; ok && len(v.([]interface{})) != 0 { + activity.CodeHook = expandLexCodeHook(v) + } + + return +} + +func flattenLexMessages(messages []*lexmodelbuildingservice.Message) (flattenedMessages []map[string]interface{}) { + for _, message := range messages { + flattenedMessages = append(flattenedMessages, map[string]interface{}{ + "content": aws.StringValue(message.Content), + "content_type": aws.StringValue(message.ContentType), + "group_number": aws.Int64Value(message.GroupNumber), + }) + } + + return +} + +// Expects a slice of maps representing the Lex objects. +// The value passed into this function should have been run through the expandLexSet function. +// Example: []map[content: test content_type: PlainText group_number: 1] +func expandLexMessages(rawValues []interface{}) []*lexmodelbuildingservice.Message { + messages := make([]*lexmodelbuildingservice.Message, 0, len(rawValues)) + + for _, rawValue := range rawValues { + value, ok := rawValue.(map[string]interface{}) + if !ok { + continue + } + + message := &lexmodelbuildingservice.Message{ + Content: aws.String(value["content"].(string)), + ContentType: aws.String(value["content_type"].(string)), + } + + if v, ok := value["group_number"]; ok && v != 0 { + message.GroupNumber = aws.Int64(int64(v.(int))) + } + + messages = append(messages, message) + } + + return messages +} + +func flattenLexPrompt(prompt *lexmodelbuildingservice.Prompt) (flattened []map[string]interface{}) { + flattened = []map[string]interface{}{ + { + "max_attempts": aws.Int64Value(prompt.MaxAttempts), + "message": flattenLexMessages(prompt.Messages), + }, + } + + if prompt.ResponseCard != nil { + flattened[0]["response_card"] = aws.StringValue(prompt.ResponseCard) + } + + return +} + +func expandLexPrompt(rawObject interface{}) (prompt *lexmodelbuildingservice.Prompt) { + m := rawObject.([]interface{})[0].(map[string]interface{}) + + prompt = &lexmodelbuildingservice.Prompt{} + prompt.MaxAttempts = aws.Int64(int64(m["max_attempts"].(int))) + prompt.Messages = expandLexMessages(m["message"].(*schema.Set).List()) + + if v, ok := m["response_card"]; ok && v != "" { + prompt.ResponseCard = aws.String(v.(string)) + } + + return +} + +func flattenLexSlots(slots []*lexmodelbuildingservice.Slot) (flattenedSlots []map[string]interface{}) { + for _, slot := range slots { + flattenedSlot := map[string]interface{}{ + "name": aws.StringValue(slot.Name), + "priority": aws.Int64Value(slot.Priority), + "slot_constraint": aws.StringValue(slot.SlotConstraint), + "slot_type": aws.StringValue(slot.SlotType), + } + + if slot.Description != nil { + flattenedSlot["description"] = aws.StringValue(slot.Description) + } + + if slot.ResponseCard != nil { + flattenedSlot["response_card"] = aws.StringValue(slot.ResponseCard) + } + + if slot.SampleUtterances != nil { + flattenedSlot["sample_utterances"] = flattenStringList(slot.SampleUtterances) + } + + if slot.SlotTypeVersion != nil { + flattenedSlot["slot_type_version"] = aws.StringValue(slot.SlotTypeVersion) + } + + if slot.ValueElicitationPrompt != nil { + flattenedSlot["value_elicitation_prompt"] = flattenLexPrompt(slot.ValueElicitationPrompt) + } + + flattenedSlots = append(flattenedSlots, flattenedSlot) + } + + return +} + +// Expects a slice of maps representing the Lex objects. +// The value passed into this function should have been run through the expandLexSet function. +// Example: []map[name: test priority: 0 ...] +func expandLexSlots(rawValues []interface{}) []*lexmodelbuildingservice.Slot { + slots := make([]*lexmodelbuildingservice.Slot, 0, len(rawValues)) + + for _, rawValue := range rawValues { + value, ok := rawValue.(map[string]interface{}) + if !ok { + continue + } + + slot := &lexmodelbuildingservice.Slot{ + Name: aws.String(value["name"].(string)), + Priority: aws.Int64(int64(value["priority"].(int))), + SlotConstraint: aws.String(value["slot_constraint"].(string)), + SlotType: aws.String(value["slot_type"].(string)), + } + + if v, ok := value["description"]; ok && v != "" { + slot.Description = aws.String(v.(string)) + } + + if v, ok := value["response_card"]; ok && v != "" { + slot.ResponseCard = aws.String(v.(string)) + } + + if v, ok := value["response_card"]; ok && v != "" { + slot.ResponseCard = aws.String(v.(string)) + } + + if v, ok := value["sample_utterances"]; ok && len(v.([]interface{})) != 0 { + slot.SampleUtterances = expandStringList(v.([]interface{})) + } + + if v, ok := value["slot_type_version"]; ok && v != "" { + slot.SlotTypeVersion = aws.String(v.(string)) + } + + if v, ok := value["value_elicitation_prompt"]; ok && len(v.([]interface{})) != 0 { + slot.ValueElicitationPrompt = expandLexPrompt(v) + } + + slots = append(slots, slot) + } + + return slots +} + +func flattenLexStatement(statement *lexmodelbuildingservice.Statement) (flattened []map[string]interface{}) { + flattened = []map[string]interface{}{ + { + "message": flattenLexMessages(statement.Messages), + }, + } + + if statement.ResponseCard != nil { + flattened[0]["response_card"] = aws.StringValue(statement.ResponseCard) + } + + return +} + +func expandLexStatement(rawObject interface{}) (statement *lexmodelbuildingservice.Statement) { + m := rawObject.([]interface{})[0].(map[string]interface{}) + + statement = &lexmodelbuildingservice.Statement{} + statement.Messages = expandLexMessages(m["message"].(*schema.Set).List()) + + if v, ok := m["response_card"]; ok && v != "" { + statement.ResponseCard = aws.String(v.(string)) + } + + return +} diff --git a/aws/resource_aws_lex_intent_test.go b/aws/resource_aws_lex_intent_test.go new file mode 100644 index 00000000000..9479ca030a6 --- /dev/null +++ b/aws/resource_aws_lex_intent_test.go @@ -0,0 +1,1036 @@ +package aws + +import ( + "fmt" + "testing" + "time" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/lexmodelbuildingservice" + "github.com/hashicorp/aws-sdk-go-base/tfawserr" + "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/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" +) + +func TestAccAwsLexIntent_basic(t *testing.T) { + var v lexmodelbuildingservice.GetIntentOutput + rName := "aws_lex_intent.test" + testIntentID := "test_intent_" + acctest.RandStringFromCharSet(8, acctest.CharSetAlpha) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAwsLexIntentDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAwsLexIntentConfig_basic(testIntentID), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckAwsLexIntentExists(rName, &v), + testAccCheckAwsLexIntentNotExists(testIntentID, "1"), + + resource.TestCheckResourceAttrSet(rName, "arn"), + resource.TestCheckResourceAttrSet(rName, "checksum"), + resource.TestCheckNoResourceAttr(rName, "conclusion_statement"), + resource.TestCheckNoResourceAttr(rName, "confirmation_prompt"), + resource.TestCheckResourceAttr(rName, "create_version", "false"), + testAccCheckResourceAttrRfc3339(rName, "created_date"), + resource.TestCheckResourceAttr(rName, "description", ""), + resource.TestCheckNoResourceAttr(rName, "dialog_code_hook"), + resource.TestCheckNoResourceAttr(rName, "follow_up_prompt"), + resource.TestCheckNoResourceAttr(rName, "fulfillment_activity"), + testAccCheckResourceAttrRfc3339(rName, "last_updated_date"), + resource.TestCheckResourceAttr(rName, "name", testIntentID), + resource.TestCheckNoResourceAttr(rName, "parent_intent_signature"), + resource.TestCheckNoResourceAttr(rName, "rejection_statement"), + resource.TestCheckNoResourceAttr(rName, "sample_utterances"), + resource.TestCheckNoResourceAttr(rName, "slot"), + resource.TestCheckResourceAttr(rName, "version", LexIntentVersionLatest), + ), + }, + { + ResourceName: rName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"create_version"}, + }, + }, + }) +} + +func TestAccAwsLexIntent_createVersion(t *testing.T) { + var v lexmodelbuildingservice.GetIntentOutput + rName := "aws_lex_intent.test" + testIntentID := "test_intent_" + acctest.RandStringFromCharSet(8, acctest.CharSetAlpha) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAwsLexIntentDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAwsLexIntentConfig_basic(testIntentID), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckAwsLexIntentExists(rName, &v), + testAccCheckAwsLexIntentNotExists(testIntentID, "1"), + ), + }, + { + ResourceName: rName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"create_version"}, + }, + { + Config: testAccAwsLexIntentConfig_createVersion(testIntentID), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckAwsLexIntentExists(rName, &v), + testAccCheckAwsLexIntentExistsWithVersion(rName, "1", &v), + resource.TestCheckResourceAttr(rName, "version", "1"), + ), + }, + { + ResourceName: rName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"create_version"}, + }, + }, + }) +} + +func TestAccAwsLexIntent_conclusionStatement(t *testing.T) { + var v lexmodelbuildingservice.GetIntentOutput + rName := "aws_lex_intent.test" + testIntentID := "test_intent_" + acctest.RandStringFromCharSet(8, acctest.CharSetAlpha) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAwsLexIntentDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAwsLexIntentConfig_conclusionStatement(testIntentID), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckAwsLexIntentExists(rName, &v), + resource.TestCheckResourceAttr(rName, "conclusion_statement.#", "1"), + resource.TestCheckResourceAttr(rName, "conclusion_statement.0.message.#", "1"), + resource.TestCheckResourceAttr(rName, "conclusion_statement.0.message.0.content", "Your order for {FlowerType} has been placed and will be ready by {PickupTime} on {PickupDate}"), + resource.TestCheckResourceAttr(rName, "conclusion_statement.0.message.0.content_type", "PlainText"), + resource.TestCheckNoResourceAttr(rName, "conclusion_statement.0.message.0.group_number"), + resource.TestCheckResourceAttr(rName, "conclusion_statement.0.response_card", "Your order for {FlowerType} has been placed and will be ready by {PickupTime} on {PickupDate}"), + ), + }, + { + ResourceName: rName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"create_version"}, + }, + { + Config: testAccAwsLexIntentConfig_conclusionStatementUpdate(testIntentID), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckAwsLexIntentExists(rName, &v), + resource.TestCheckResourceAttr(rName, "conclusion_statement.0.message.#", "2"), + resource.TestCheckResourceAttr(rName, "conclusion_statement.0.message.0.content", "Your order for {FlowerType} has been placed and will be ready by {PickupTime} on {PickupDate}"), + resource.TestCheckResourceAttr(rName, "conclusion_statement.0.message.0.content_type", "PlainText"), + resource.TestCheckResourceAttr(rName, "conclusion_statement.0.message.0.group_number", "1"), + resource.TestCheckResourceAttr(rName, "conclusion_statement.0.message.1.content", "Your order for {FlowerType} has been placed"), + resource.TestCheckResourceAttr(rName, "conclusion_statement.0.message.1.content_type", "PlainText"), + resource.TestCheckResourceAttr(rName, "conclusion_statement.0.message.1.group_number", "1"), + resource.TestCheckResourceAttr(rName, "conclusion_statement.0.response_card", "Your order for {FlowerType} has been placed"), + ), + }, + { + ResourceName: rName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"create_version"}, + }, + }, + }) +} + +func TestAccAwsLexIntent_confirmationPromptAndRejectionStatement(t *testing.T) { + var v lexmodelbuildingservice.GetIntentOutput + rName := "aws_lex_intent.test" + testIntentID := "test_intent_" + acctest.RandStringFromCharSet(8, acctest.CharSetAlpha) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAwsLexIntentDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAwsLexIntentConfig_confirmationPromptAndRejectionStatement(testIntentID), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckAwsLexIntentExists(rName, &v), + resource.TestCheckResourceAttr(rName, "confirmation_prompt.#", "1"), + resource.TestCheckResourceAttr(rName, "confirmation_prompt.0.max_attempts", "1"), + resource.TestCheckResourceAttr(rName, "confirmation_prompt.0.message.#", "1"), + resource.TestCheckResourceAttr(rName, "confirmation_prompt.0.message.0.content", "Okay, your {FlowerType} will be ready for pickup by {PickupTime} on {PickupDate}. Does this sound okay?"), + resource.TestCheckResourceAttr(rName, "confirmation_prompt.0.message.0.content_type", "PlainText"), + resource.TestCheckResourceAttr(rName, "confirmation_prompt.0.response_card", "{\"version\":1,\"contentType\":\"application/vnd.amazonaws.card.generic\",\"genericAttachments\":[{\"title\":\"Okay, your {FlowerType} will be ready for pickup by {PickupTime} on {PickupDate}. Does this sound okay?\",\"buttons\":[{\"text\":\"Yes\",\"value\":\"yes\"},{\"text\":\"No\",\"value\":\"no\"}]}]}"), + resource.TestCheckResourceAttr(rName, "rejection_statement.#", "1"), + resource.TestCheckResourceAttr(rName, "rejection_statement.0.message.#", "1"), + resource.TestCheckResourceAttr(rName, "rejection_statement.0.message.0.content", "Okay, I will not place your order."), + resource.TestCheckResourceAttr(rName, "rejection_statement.0.message.0.content_type", "PlainText"), + resource.TestCheckResourceAttr(rName, "rejection_statement.0.response_card", "Okay, I will not place your order."), + ), + }, + { + ResourceName: rName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"create_version"}, + }, + { + Config: testAccAwsLexIntentConfig_confirmationPromptAndRejectionStatementUpdate(testIntentID), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckAwsLexIntentExists(rName, &v), + resource.TestCheckResourceAttr(rName, "confirmation_prompt.0.max_attempts", "2"), + resource.TestCheckResourceAttr(rName, "confirmation_prompt.0.message.#", "2"), + resource.TestCheckResourceAttr(rName, "confirmation_prompt.0.message.0.content", "Okay, your {FlowerType} will be ready for pickup by {PickupTime} on {PickupDate}. Does this sound okay?"), + resource.TestCheckResourceAttr(rName, "confirmation_prompt.0.message.0.content_type", "PlainText"), + resource.TestCheckResourceAttr(rName, "confirmation_prompt.0.message.1.content", "Okay, your {FlowerType} will be ready for pickup on {PickupDate}. Does this sound okay?"), + resource.TestCheckResourceAttr(rName, "confirmation_prompt.0.message.1.content_type", "PlainText"), + resource.TestCheckResourceAttr(rName, "confirmation_prompt.0.response_card", "{\"version\":1,\"contentType\":\"application/vnd.amazonaws.card.generic\",\"genericAttachments\":[{\"title\":\"Okay, your {FlowerType} will be ready for pickup on {PickupDate}. Does this sound okay?\",\"buttons\":[{\"text\":\"Yes\",\"value\":\"yes\"},{\"text\":\"No\",\"value\":\"no\"}]}]}"), + resource.TestCheckResourceAttr(rName, "rejection_statement.0.message.#", "2"), + resource.TestCheckResourceAttr(rName, "rejection_statement.0.message.0.content", "Okay, I will not place your order."), + resource.TestCheckResourceAttr(rName, "rejection_statement.0.message.0.content_type", "PlainText"), + resource.TestCheckResourceAttr(rName, "rejection_statement.0.message.1.content", "Okay, your order has been cancelled."), + resource.TestCheckResourceAttr(rName, "rejection_statement.0.message.1.content_type", "PlainText"), + resource.TestCheckResourceAttr(rName, "rejection_statement.0.response_card", "Okay, your order has been cancelled."), + ), + }, + { + ResourceName: rName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"create_version"}, + }, + }, + }) +} + +func TestAccAwsLexIntent_dialogCodeHook(t *testing.T) { + var v lexmodelbuildingservice.GetIntentOutput + rName := "aws_lex_intent.test" + testIntentID := "test_intent_" + acctest.RandStringFromCharSet(8, acctest.CharSetAlpha) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAwsLexIntentDestroy, + Steps: []resource.TestStep{ + { + Config: composeConfig( + testAccAwsLexIntentConfig_lambda(testIntentID), + testAccAwsLexIntentConfig_dialogCodeHook(testIntentID), + ), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckAwsLexIntentExists(rName, &v), + resource.TestCheckResourceAttr(rName, "dialog_code_hook.#", "1"), + resource.TestCheckResourceAttr(rName, "dialog_code_hook.0.message_version", "1"), + resource.TestCheckResourceAttrSet(rName, "dialog_code_hook.0.uri"), + ), + }, + { + ResourceName: rName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"create_version"}, + }, + }, + }) +} + +func TestAccAwsLexIntent_followUpPrompt(t *testing.T) { + var v lexmodelbuildingservice.GetIntentOutput + rName := "aws_lex_intent.test" + testIntentID := "test_intent_" + acctest.RandStringFromCharSet(8, acctest.CharSetAlpha) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAwsLexIntentDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAwsLexIntentConfig_followUpPrompt(testIntentID), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckAwsLexIntentExists(rName, &v), + + resource.TestCheckResourceAttr(rName, "follow_up_prompt.#", "1"), + + resource.TestCheckResourceAttr(rName, "follow_up_prompt.0.prompt.#", "1"), + resource.TestCheckResourceAttr(rName, "follow_up_prompt.0.prompt.0.max_attempts", "1"), + resource.TestCheckResourceAttr(rName, "follow_up_prompt.0.prompt.0.message.#", "1"), + resource.TestCheckResourceAttr(rName, "follow_up_prompt.0.prompt.0.message.0.content", "Would you like to order more flowers?"), + resource.TestCheckResourceAttr(rName, "follow_up_prompt.0.prompt.0.message.0.content_type", "PlainText"), + resource.TestCheckResourceAttr(rName, "follow_up_prompt.0.prompt.0.response_card", "{\"version\":1,\"contentType\":\"application/vnd.amazonaws.card.generic\",\"genericAttachments\":[{\"title\":\"Would you like to order more flowers?\",\"buttons\":[{\"text\":\"Yes\",\"value\":\"yes\"},{\"text\":\"No\",\"value\":\"no\"}]}]}"), + + resource.TestCheckResourceAttr(rName, "follow_up_prompt.0.rejection_statement.#", "1"), + resource.TestCheckResourceAttr(rName, "follow_up_prompt.0.rejection_statement.0.message.#", "1"), + resource.TestCheckResourceAttr(rName, "follow_up_prompt.0.rejection_statement.0.message.0.content", "Okay, no additional flowers will be ordered."), + resource.TestCheckResourceAttr(rName, "follow_up_prompt.0.rejection_statement.0.message.0.content_type", "PlainText"), + resource.TestCheckResourceAttr(rName, "follow_up_prompt.0.rejection_statement.0.response_card", "Okay, no additional flowers will be ordered."), + ), + }, + { + ResourceName: rName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"create_version"}, + }, + { + Config: testAccAwsLexIntentConfig_followUpPromptUpdate(testIntentID), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckAwsLexIntentExists(rName, &v), + + resource.TestCheckResourceAttr(rName, "follow_up_prompt.0.prompt.0.max_attempts", "2"), + resource.TestCheckResourceAttr(rName, "follow_up_prompt.0.prompt.0.message.#", "2"), + resource.TestCheckResourceAttr(rName, "follow_up_prompt.0.prompt.0.message.0.content", "Would you like to order more flowers?"), + resource.TestCheckResourceAttr(rName, "follow_up_prompt.0.prompt.0.message.0.content_type", "PlainText"), + resource.TestCheckResourceAttr(rName, "follow_up_prompt.0.prompt.0.message.1.content", "Would you like to start another order?"), + resource.TestCheckResourceAttr(rName, "follow_up_prompt.0.prompt.0.message.1.content_type", "PlainText"), + resource.TestCheckResourceAttr(rName, "follow_up_prompt.0.prompt.0.response_card", "{\"version\":1,\"contentType\":\"application/vnd.amazonaws.card.generic\",\"genericAttachments\":[{\"title\":\"Would you like to start another order?\",\"buttons\":[{\"text\":\"Yes\",\"value\":\"yes\"},{\"text\":\"No\",\"value\":\"no\"}]}]}"), + + resource.TestCheckResourceAttr(rName, "follow_up_prompt.0.rejection_statement.0.message.#", "2"), + resource.TestCheckResourceAttr(rName, "follow_up_prompt.0.rejection_statement.0.message.0.content", "Okay, additional flowers will be ordered."), + resource.TestCheckResourceAttr(rName, "follow_up_prompt.0.rejection_statement.0.message.0.content_type", "PlainText"), + resource.TestCheckResourceAttr(rName, "follow_up_prompt.0.rejection_statement.0.message.1.content", "Okay, no additional flowers will be ordered."), + resource.TestCheckResourceAttr(rName, "follow_up_prompt.0.rejection_statement.0.message.1.content_type", "PlainText"), + resource.TestCheckResourceAttr(rName, "follow_up_prompt.0.rejection_statement.0.response_card", "Okay, additional flowers will be ordered."), + ), + }, + { + ResourceName: rName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"create_version"}, + }, + }, + }) +} + +func TestAccAwsLexIntent_fulfillmentActivity(t *testing.T) { + var v lexmodelbuildingservice.GetIntentOutput + rName := "aws_lex_intent.test" + testIntentID := "test_intent_" + acctest.RandStringFromCharSet(8, acctest.CharSetAlpha) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAwsLexIntentDestroy, + Steps: []resource.TestStep{ + { + Config: composeConfig( + testAccAwsLexIntentConfig_lambda(testIntentID), + testAccAwsLexIntentConfig_fulfillmentActivity(testIntentID), + ), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckAwsLexIntentExists(rName, &v), + resource.TestCheckResourceAttr(rName, "fulfillment_activity.#", "1"), + resource.TestCheckResourceAttr(rName, "fulfillment_activity.0.code_hook.#", "1"), + resource.TestCheckResourceAttr(rName, "fulfillment_activity.0.code_hook.0.message_version", "1"), + resource.TestCheckResourceAttrSet(rName, "fulfillment_activity.0.code_hook.0.uri"), + resource.TestCheckResourceAttr(rName, "fulfillment_activity.0.type", "CodeHook"), + ), + }, + { + ResourceName: rName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"create_version"}, + }, + }, + }) +} + +func TestAccAwsLexIntent_sampleUtterances(t *testing.T) { + var v lexmodelbuildingservice.GetIntentOutput + rName := "aws_lex_intent.test" + testIntentID := "test_intent_" + acctest.RandStringFromCharSet(8, acctest.CharSetAlpha) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAwsLexIntentDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAwsLexIntentConfig_sampleUtterances(testIntentID), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckAwsLexIntentExists(rName, &v), + resource.TestCheckResourceAttr(rName, "sample_utterances.#", "1"), + resource.TestCheckResourceAttr(rName, "sample_utterances.0", "I would like to pick up flowers"), + ), + }, + { + ResourceName: rName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"create_version"}, + }, + { + Config: testAccAwsLexIntentConfig_sampleUtterancesUpdate(testIntentID), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckAwsLexIntentExists(rName, &v), + resource.TestCheckResourceAttr(rName, "sample_utterances.#", "2"), + ), + }, + { + ResourceName: rName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"create_version"}, + }, + }, + }) +} + +func TestAccAwsLexIntent_slots(t *testing.T) { + var v lexmodelbuildingservice.GetIntentOutput + rName := "aws_lex_intent.test" + testIntentID := "test_intent_" + acctest.RandStringFromCharSet(8, acctest.CharSetAlpha) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAwsLexIntentDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAwsLexIntentConfig_slots(testIntentID), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckAwsLexIntentExists(rName, &v), + resource.TestCheckResourceAttr(rName, "slot.#", "1"), + resource.TestCheckResourceAttr(rName, "slot.0.description", "The date to pick up the flowers"), + resource.TestCheckResourceAttr(rName, "slot.0.name", "PickupDate"), + resource.TestCheckResourceAttr(rName, "slot.0.priority", "1"), + resource.TestCheckResourceAttr(rName, "slot.0.sample_utterances.#", "1"), + resource.TestCheckResourceAttr(rName, "slot.0.sample_utterances.0", "I would like to order {FlowerType}"), + resource.TestCheckResourceAttr(rName, "slot.0.slot_constraint", "Required"), + resource.TestCheckResourceAttr(rName, "slot.0.slot_type", "AMAZON.DATE"), + resource.TestCheckResourceAttr(rName, "slot.0.value_elicitation_prompt.#", "1"), + resource.TestCheckResourceAttr(rName, "slot.0.value_elicitation_prompt.0.max_attempts", "1"), + resource.TestCheckResourceAttr(rName, "slot.0.value_elicitation_prompt.0.message.#", "1"), + resource.TestCheckResourceAttr(rName, "slot.0.value_elicitation_prompt.0.message.0.content", "What day do you want the {FlowerType} to be picked up?"), + resource.TestCheckResourceAttr(rName, "slot.0.value_elicitation_prompt.0.message.0.content_type", "PlainText"), + ), + }, + { + ResourceName: rName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"create_version"}, + }, + { + Config: testAccAwsLexIntentConfig_slotsUpdate(testIntentID), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckAwsLexIntentExists(rName, &v), + resource.TestCheckResourceAttr(rName, "slot.#", "2"), + ), + }, + { + ResourceName: rName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"create_version"}, + }, + }, + }) +} + +func TestAccAwsLexIntent_slotsCustom(t *testing.T) { + var v lexmodelbuildingservice.GetIntentOutput + rName := "aws_lex_intent.test" + testIntentID := "test_intent_" + acctest.RandStringFromCharSet(8, acctest.CharSetAlpha) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAwsLexIntentDestroy, + Steps: []resource.TestStep{ + { + Config: composeConfig( + testAccAwsLexSlotTypeConfig_basic(testIntentID), + testAccAwsLexIntentConfig_slotsCustom(testIntentID), + ), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckAwsLexIntentExists(rName, &v), + resource.TestCheckResourceAttr(rName, "slot.#", "1"), + resource.TestCheckResourceAttr(rName, "slot.0.description", "Types of flowers to pick up"), + resource.TestCheckResourceAttr(rName, "slot.0.name", "FlowerType"), + resource.TestCheckResourceAttr(rName, "slot.0.priority", "1"), + resource.TestCheckResourceAttr(rName, "slot.0.sample_utterances.#", "1"), + resource.TestCheckResourceAttr(rName, "slot.0.sample_utterances.0", "I would like to order {FlowerType}"), + resource.TestCheckResourceAttr(rName, "slot.0.slot_constraint", "Required"), + resource.TestCheckResourceAttr(rName, "slot.0.slot_type", testIntentID), + resource.TestCheckResourceAttr(rName, "slot.0.value_elicitation_prompt.#", "1"), + resource.TestCheckResourceAttr(rName, "slot.0.value_elicitation_prompt.0.max_attempts", "2"), + resource.TestCheckResourceAttr(rName, "slot.0.value_elicitation_prompt.0.message.#", "1"), + resource.TestCheckResourceAttr(rName, "slot.0.value_elicitation_prompt.0.message.0.content", "What type of flowers would you like to order?"), + resource.TestCheckResourceAttr(rName, "slot.0.value_elicitation_prompt.0.message.0.content_type", "PlainText"), + resource.TestCheckResourceAttr(rName, "slot.0.value_elicitation_prompt.0.response_card", "{\"version\":1,\"contentType\":\"application/vnd.amazonaws.card.generic\",\"genericAttachments\":[{\"title\":\"What type of flowers?\",\"buttons\":[{\"text\":\"Tulips\",\"value\":\"tulips\"},{\"text\":\"Lilies\",\"value\":\"lilies\"},{\"text\":\"Roses\",\"value\":\"roses\"}]}]}"), + ), + }, + { + ResourceName: rName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"create_version"}, + }, + }, + }) +} + +func TestAccAwsLexIntent_disappears(t *testing.T) { + var v lexmodelbuildingservice.GetIntentOutput + rName := "aws_lex_intent.test" + testIntentID := "test_intent_" + acctest.RandStringFromCharSet(8, acctest.CharSetAlpha) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAwsLexIntentDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAwsLexIntentConfig_basic(testIntentID), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsLexIntentExists(rName, &v), + testAccCheckResourceDisappears(testAccProvider, resourceAwsLexIntent(), rName), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +func TestAccAwsLexIntent_updateWithExternalChange(t *testing.T) { + var v lexmodelbuildingservice.GetIntentOutput + rName := "aws_lex_intent.test" + testIntentID := "test_intent_" + acctest.RandStringFromCharSet(8, acctest.CharSetAlpha) + + testAccCheckAwsLexIntentUpdateDescription := func(provider *schema.Provider, _ *schema.Resource, resourceName string) resource.TestCheckFunc { + return func(s *terraform.State) error { + conn := provider.Meta().(*AWSClient).lexmodelconn + + resourceState, ok := s.RootModule().Resources[resourceName] + if !ok { + return fmt.Errorf("intent not found: %s", resourceName) + } + + input := &lexmodelbuildingservice.PutIntentInput{ + Checksum: aws.String(resourceState.Primary.Attributes["checksum"]), + Description: aws.String("Updated externally without Terraform"), + Name: aws.String(resourceState.Primary.ID), + FulfillmentActivity: &lexmodelbuildingservice.FulfillmentActivity{ + Type: aws.String("ReturnIntent"), + }, + } + err := resource.Retry(1*time.Minute, func() *resource.RetryError { + _, err := conn.PutIntent(input) + + if isAWSErr(err, lexmodelbuildingservice.ErrCodeConflictException, "") { + return resource.RetryableError(fmt.Errorf("%q: intent still updating", resourceName)) + } + if err != nil { + return resource.NonRetryableError(err) + } + + return nil + }) + if err != nil { + return fmt.Errorf("error updating intent %s: %w", resourceName, err) + } + + return nil + } + } + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAwsLexIntentDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAwsLexIntentConfig_basic(testIntentID), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsLexIntentExists(rName, &v), + testAccCheckAwsLexIntentUpdateDescription(testAccProvider, resourceAwsLexIntent(), rName), + ), + ExpectNonEmptyPlan: true, + }, + { + Config: testAccAwsLexIntentConfig_basic(testIntentID), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsLexIntentExists(rName, &v), + ), + }, + }, + }) +} + +func testAccCheckAwsLexIntentExistsWithVersion(rName, intentVersion string, output *lexmodelbuildingservice.GetIntentOutput) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[rName] + if !ok { + return fmt.Errorf("Not found: %s", rName) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("No Lex intent ID is set") + } + + var err error + conn := testAccProvider.Meta().(*AWSClient).lexmodelconn + + output, err = conn.GetIntent(&lexmodelbuildingservice.GetIntentInput{ + Name: aws.String(rs.Primary.ID), + Version: aws.String(intentVersion), + }) + if tfawserr.ErrCodeEquals(err, lexmodelbuildingservice.ErrCodeNotFoundException) { + return fmt.Errorf("error intent %q version %s not found", rs.Primary.ID, intentVersion) + } + if err != nil { + return fmt.Errorf("error getting intent %q version %s: %w", rs.Primary.ID, intentVersion, err) + } + + return nil + } +} + +func testAccCheckAwsLexIntentExists(rName string, output *lexmodelbuildingservice.GetIntentOutput) resource.TestCheckFunc { + return testAccCheckAwsLexIntentExistsWithVersion(rName, LexIntentVersionLatest, output) +} + +func testAccCheckAwsLexIntentNotExists(intentName, intentVersion string) resource.TestCheckFunc { + return func(s *terraform.State) error { + conn := testAccProvider.Meta().(*AWSClient).lexmodelconn + + _, err := conn.GetIntent(&lexmodelbuildingservice.GetIntentInput{ + Name: aws.String(intentName), + Version: aws.String(intentVersion), + }) + if tfawserr.ErrCodeEquals(err, lexmodelbuildingservice.ErrCodeNotFoundException) { + return nil + } + if err != nil { + return fmt.Errorf("error getting intent %s version %s: %s", intentName, intentVersion, err) + } + + return fmt.Errorf("error intent %s version %s exists", intentName, intentVersion) + } +} + +func testAccCheckAwsLexIntentDestroy(s *terraform.State) error { + conn := testAccProvider.Meta().(*AWSClient).lexmodelconn + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_lex_intent" { + continue + } + + output, err := conn.GetIntentVersions(&lexmodelbuildingservice.GetIntentVersionsInput{ + Name: aws.String(rs.Primary.ID), + }) + if tfawserr.ErrCodeEquals(err, lexmodelbuildingservice.ErrCodeNotFoundException) { + continue + } + if err != nil { + return err + } + + if output == nil || len(output.Intents) == 0 { + return nil + } + + return fmt.Errorf("Lex intent %q still exists", rs.Primary.ID) + } + + return nil +} + +func testAccAwsLexIntentConfig_lambda(rName string) string { + return fmt.Sprintf(` +data "aws_iam_policy_document" "lambda_assume_role" { + statement { + actions = ["sts:AssumeRole"] + principals { + type = "Service" + identifiers = ["lambda.amazonaws.com"] + } + } +} + +resource "aws_iam_role" "test" { + name = "%[1]s" + assume_role_policy = data.aws_iam_policy_document.lambda_assume_role.json +} + +resource "aws_lambda_permission" "lex" { + action = "lambda:InvokeFunction" + function_name = aws_lambda_function.test.function_name + principal = "lex.amazonaws.com" +} + +resource "aws_lambda_function" "test" { + filename = "test-fixtures/lambdatest.zip" + function_name = "%[1]s" + handler = "lambdatest.handler" + role = aws_iam_role.test.arn + runtime = "nodejs12.x" +} +`, rName) +} + +func testAccAwsLexIntentConfig_basic(rName string) string { + return fmt.Sprintf(` +resource "aws_lex_intent" "test" { + name = "%s" + fulfillment_activity { + type = "ReturnIntent" + } +} +`, rName) +} + +func testAccAwsLexIntentConfig_createVersion(rName string) string { + return fmt.Sprintf(` +resource "aws_lex_intent" "test" { + name = "%s" + create_version = true + fulfillment_activity { + type = "ReturnIntent" + } +} +`, rName) +} + +func testAccAwsLexIntentConfig_conclusionStatement(rName string) string { + return fmt.Sprintf(` +resource "aws_lex_intent" "test" { + name = "%s" + fulfillment_activity { + type = "ReturnIntent" + } + conclusion_statement { + message { + content = "Your order for {FlowerType} has been placed and will be ready by {PickupTime} on {PickupDate}" + content_type = "PlainText" + } + response_card = "Your order for {FlowerType} has been placed and will be ready by {PickupTime} on {PickupDate}" + } +} +`, rName) +} + +func testAccAwsLexIntentConfig_conclusionStatementUpdate(rName string) string { + return fmt.Sprintf(` +resource "aws_lex_intent" "test" { + name = "%s" + fulfillment_activity { + type = "ReturnIntent" + } + conclusion_statement { + message { + content = "Your order for {FlowerType} has been placed and will be ready by {PickupTime} on {PickupDate}" + content_type = "PlainText" + group_number = 1 + } + message { + content = "Your order for {FlowerType} has been placed" + content_type = "PlainText" + group_number = 1 + } + response_card = "Your order for {FlowerType} has been placed" + } +} +`, rName) +} + +func testAccAwsLexIntentConfig_confirmationPromptAndRejectionStatement(rName string) string { + return fmt.Sprintf(` +resource "aws_lex_intent" "test" { + name = "%s" + fulfillment_activity { + type = "ReturnIntent" + } + confirmation_prompt { + max_attempts = 1 + message { + content = "Okay, your {FlowerType} will be ready for pickup by {PickupTime} on {PickupDate}. Does this sound okay?" + content_type = "PlainText" + } + response_card = "{\"version\":1,\"contentType\":\"application/vnd.amazonaws.card.generic\",\"genericAttachments\":[{\"title\":\"Okay, your {FlowerType} will be ready for pickup by {PickupTime} on {PickupDate}. Does this sound okay?\",\"buttons\":[{\"text\":\"Yes\",\"value\":\"yes\"},{\"text\":\"No\",\"value\":\"no\"}]}]}" + } + rejection_statement { + message { + content = "Okay, I will not place your order." + content_type = "PlainText" + } + response_card = "Okay, I will not place your order." + } +} +`, rName) +} + +func testAccAwsLexIntentConfig_confirmationPromptAndRejectionStatementUpdate(rName string) string { + return fmt.Sprintf(` +resource "aws_lex_intent" "test" { + name = "%s" + fulfillment_activity { + type = "ReturnIntent" + } + confirmation_prompt { + max_attempts = 2 + message { + content = "Okay, your {FlowerType} will be ready for pickup by {PickupTime} on {PickupDate}. Does this sound okay?" + content_type = "PlainText" + } + message { + content = "Okay, your {FlowerType} will be ready for pickup on {PickupDate}. Does this sound okay?" + content_type = "PlainText" + } + response_card = "{\"version\":1,\"contentType\":\"application/vnd.amazonaws.card.generic\",\"genericAttachments\":[{\"title\":\"Okay, your {FlowerType} will be ready for pickup on {PickupDate}. Does this sound okay?\",\"buttons\":[{\"text\":\"Yes\",\"value\":\"yes\"},{\"text\":\"No\",\"value\":\"no\"}]}]}" + } + rejection_statement { + message { + content = "Okay, I will not place your order." + content_type = "PlainText" + } + message { + content = "Okay, your order has been cancelled." + content_type = "PlainText" + } + response_card = "Okay, your order has been cancelled." + } +} +`, rName) +} + +func testAccAwsLexIntentConfig_dialogCodeHook(rName string) string { + return fmt.Sprintf(` +resource "aws_lex_intent" "test" { + name = "%s" + fulfillment_activity { + type = "ReturnIntent" + } + dialog_code_hook { + message_version = "1" + uri = aws_lambda_function.test.arn + } +} +`, rName) +} + +func testAccAwsLexIntentConfig_followUpPrompt(rName string) string { + return fmt.Sprintf(` +resource "aws_lex_intent" "test" { + name = "%s" + fulfillment_activity { + type = "ReturnIntent" + } + follow_up_prompt { + prompt { + max_attempts = 1 + message { + content = "Would you like to order more flowers?" + content_type = "PlainText" + } + response_card = "{\"version\":1,\"contentType\":\"application/vnd.amazonaws.card.generic\",\"genericAttachments\":[{\"title\":\"Would you like to order more flowers?\",\"buttons\":[{\"text\":\"Yes\",\"value\":\"yes\"},{\"text\":\"No\",\"value\":\"no\"}]}]}" + } + rejection_statement { + message { + content = "Okay, no additional flowers will be ordered." + content_type = "PlainText" + } + response_card = "Okay, no additional flowers will be ordered." + } + } +} +`, rName) +} + +func testAccAwsLexIntentConfig_followUpPromptUpdate(rName string) string { + return fmt.Sprintf(` +resource "aws_lex_intent" "test" { + name = "%s" + fulfillment_activity { + type = "ReturnIntent" + } + follow_up_prompt { + prompt { + max_attempts = 2 + message { + content = "Would you like to order more flowers?" + content_type = "PlainText" + } + message { + content = "Would you like to start another order?" + content_type = "PlainText" + } + response_card = "{\"version\":1,\"contentType\":\"application/vnd.amazonaws.card.generic\",\"genericAttachments\":[{\"title\":\"Would you like to start another order?\",\"buttons\":[{\"text\":\"Yes\",\"value\":\"yes\"},{\"text\":\"No\",\"value\":\"no\"}]}]}" + } + rejection_statement { + message { + content = "Okay, no additional flowers will be ordered." + content_type = "PlainText" + } + message { + content = "Okay, additional flowers will be ordered." + content_type = "PlainText" + } + response_card = "Okay, additional flowers will be ordered." + } + } +} +`, rName) +} + +func testAccAwsLexIntentConfig_fulfillmentActivity(rName string) string { + return fmt.Sprintf(` +resource "aws_lex_intent" "test" { + name = "%s" + fulfillment_activity { + code_hook { + message_version = "1" + uri = aws_lambda_function.test.arn + } + type = "CodeHook" + } +} +`, rName) +} + +func testAccAwsLexIntentConfig_sampleUtterances(rName string) string { + return fmt.Sprintf(` +resource "aws_lex_intent" "test" { + name = "%s" + fulfillment_activity { + type = "ReturnIntent" + } + sample_utterances = [ + "I would like to pick up flowers", + ] +} +`, rName) +} + +func testAccAwsLexIntentConfig_sampleUtterancesUpdate(rName string) string { + return fmt.Sprintf(` +resource "aws_lex_intent" "test" { + name = "%s" + fulfillment_activity { + type = "ReturnIntent" + } + sample_utterances = [ + "I would like to pick up flowers", + "I would like to order some flowers", + ] +} +`, rName) +} + +func testAccAwsLexIntentConfig_slots(rName string) string { + return fmt.Sprintf(` +resource "aws_lex_intent" "test" { + name = "%s" + fulfillment_activity { + type = "ReturnIntent" + } + slot { + description = "The date to pick up the flowers" + name = "PickupDate" + priority = 1 + sample_utterances = [ + "I would like to order {FlowerType}", + ] + slot_constraint = "Required" + slot_type = "AMAZON.DATE" + value_elicitation_prompt { + max_attempts = 1 + message { + content = "What day do you want the {FlowerType} to be picked up?" + content_type = "PlainText" + } + } + } +} +`, rName) +} + +func testAccAwsLexIntentConfig_slotsUpdate(rName string) string { + return fmt.Sprintf(` +resource "aws_lex_intent" "test" { + name = "%s" + fulfillment_activity { + type = "ReturnIntent" + } + slot { + description = "The date to pick up the flowers" + name = "PickupDate" + priority = 2 + sample_utterances = [ + "I would like to order {FlowerType}", + ] + slot_constraint = "Required" + slot_type = "AMAZON.DATE" + value_elicitation_prompt { + max_attempts = 2 + message { + content = "What day do you want the {FlowerType} to be picked up?" + content_type = "PlainText" + } + } + } + slot { + description = "The time to pick up the flowers" + name = "PickupTime" + priority = 1 + sample_utterances = [ + "I would like to order {FlowerType}", + ] + slot_constraint = "Required" + slot_type = "AMAZON.TIME" + value_elicitation_prompt { + max_attempts = 2 + message { + content = "Pick up the {FlowerType} at what time on {PickupDate}?" + content_type = "PlainText" + } + } + } +} +`, rName) +} + +func testAccAwsLexIntentConfig_slotsCustom(rName string) string { + return fmt.Sprintf(` +resource "aws_lex_intent" "test" { + name = "%[1]s" + fulfillment_activity { + type = "ReturnIntent" + } + slot { + description = "Types of flowers to pick up" + name = "FlowerType" + priority = 1 + sample_utterances = [ + "I would like to order {FlowerType}", + ] + slot_constraint = "Required" + slot_type = aws_lex_slot_type.test.name + slot_type_version = "$LATEST" + value_elicitation_prompt { + max_attempts = 2 + message { + content = "What type of flowers would you like to order?" + content_type = "PlainText" + } + response_card = "{\"version\":1,\"contentType\":\"application/vnd.amazonaws.card.generic\",\"genericAttachments\":[{\"title\":\"What type of flowers?\",\"buttons\":[{\"text\":\"Tulips\",\"value\":\"tulips\"},{\"text\":\"Lilies\",\"value\":\"lilies\"},{\"text\":\"Roses\",\"value\":\"roses\"}]}]}" + } + } +} +`, rName) +} diff --git a/aws/resource_aws_lex_slot_type.go b/aws/resource_aws_lex_slot_type.go index 8affb6daad6..14f5ecbd9cb 100644 --- a/aws/resource_aws_lex_slot_type.go +++ b/aws/resource_aws_lex_slot_type.go @@ -95,13 +95,10 @@ func resourceAwsLexSlotType() *schema.Resource { ), }, "value_selection_strategy": { - Type: schema.TypeString, - Optional: true, - Default: lexmodelbuildingservice.SlotValueSelectionStrategyOriginalValue, - ValidateFunc: validation.StringInSlice([]string{ - lexmodelbuildingservice.SlotValueSelectionStrategyOriginalValue, - lexmodelbuildingservice.SlotValueSelectionStrategyTopResolution, - }, false), + Type: schema.TypeString, + Optional: true, + Default: lexmodelbuildingservice.SlotValueSelectionStrategyOriginalValue, + ValidateFunc: validation.StringInSlice(lexmodelbuildingservice.SlotValueSelectionStrategy_Values(), false), }, "version": { Type: schema.TypeString, @@ -186,7 +183,7 @@ func resourceAwsLexSlotTypeRead(d *schema.ResourceData, meta interface{}) error } func getLatestLexSlotTypeVersion(conn *lexmodelbuildingservice.LexModelBuildingService, input *lexmodelbuildingservice.GetSlotTypeVersionsInput) (string, error) { - version := "$LATEST" + version := LexSlotTypeVersionLatest for { page, err := conn.GetSlotTypeVersions(input) diff --git a/website/docs/d/lex_intent.html.markdown b/website/docs/d/lex_intent.html.markdown new file mode 100644 index 00000000000..dc2c2d4233b --- /dev/null +++ b/website/docs/d/lex_intent.html.markdown @@ -0,0 +1,44 @@ +--- +subcategory: "Lex" +layout: "aws" +page_title: "AWS: aws_lex_intent" +description: |- + Provides details about a specific Amazon Lex Intent +--- + +# Data Source: aws_lex_intent + +Provides details about a specific Amazon Lex Intent. + +## Example Usage + +```hcl +data "aws_lex_intent" "order_flowers" { + name = "OrderFlowers" + version = "$LATEST" +} +``` + +## Argument Reference + +The following arguments are supported: + +* `name` - (Required) The name of the intent. The name is case sensitive. +* `version` - (Optional) The version of the intent. + +## Attributes Reference + +The following attributes are exported. + +* `arn` - The ARN of the Lex intent. +* `checksum` - Checksum identifying the version of the intent that was created. The checksum is not +included as an argument because the resource will add it automatically when updating the intent. +* `created_date` - The date when the intent version was created. +* `description` - A description of the intent. +* `last_updated_date` - The date when the $LATEST version of this intent was updated. +* `name` - The name of the intent, not case sensitive. +* `parent_intent_signature` - A unique identifier for the built-in intent to base this +intent on. To find the signature for an intent, see +[Standard Built-in Intents](https://developer.amazon.com/public/solutions/alexa/alexa-skills-kit/docs/built-in-intent-ref/standard-intents) +in the Alexa Skills Kit. +* `version` - The version of the bot. diff --git a/website/docs/r/lex_intent.html.markdown b/website/docs/r/lex_intent.html.markdown new file mode 100644 index 00000000000..4be22a63f23 --- /dev/null +++ b/website/docs/r/lex_intent.html.markdown @@ -0,0 +1,289 @@ +--- +subcategory: "Lex" +layout: "aws" +page_title: "AWS: aws_lex_intent" +description: |- + Provides an Amazon Lex intent resource. +--- + +# Resource: aws_lex_intent + +Provides an Amazon Lex Intent resource. For more information see +[Amazon Lex: How It Works](https://docs.aws.amazon.com/lex/latest/dg/how-it-works.html) + +## Example Usage + +```hcl +resource "aws_lex_intent" "order_flowers_intent" { + confirmation_prompt { + max_attempts = 2 + + message { + content = "Okay, your {FlowerType} will be ready for pickup by {PickupTime} on {PickupDate}. Does this sound okay?" + content_type = "PlainText" + } + } + + create_version = false + description = "Intent to order a bouquet of flowers for pick up" + + fulfillment_activity { + type = "ReturnIntent" + } + + rejection_statement { + message { + content = "Okay, I will not place your order." + content_type = "PlainText" + } + } + + sample_utterances = [ + "I would like to order some flowers", + "I would like to pick up flowers", + ] + + slot { + description = "The type of flowers to pick up" + name = "FlowerType" + priority = 1 + + sample_utterances = [ + "I would like to order {FlowerType}", + ] + + slot_constraint = "Required" + slot_type = "FlowerTypes" + slot_type_version = "$$LATEST" + + value_elicitation_prompt { + max_attempts = 2 + + message { + content = "What type of flowers would you like to order?" + content_type = "PlainText" + } + } + } + + slot { + description = "The date to pick up the flowers" + name = "PickupDate" + priority = 2 + + sample_utterances = [ + "I would like to order {FlowerType}", + ] + + slot_constraint = "Required" + slot_type = "AMAZON.DATE" + slot_type_version = "$$LATEST" + + value_elicitation_prompt { + max_attempts = 2 + + message { + content = "What day do you want the {FlowerType} to be picked up?" + content_type = "PlainText" + } + } + } + + slot { + description = "The time to pick up the flowers" + name = "PickupTime" + priority = 3 + + sample_utterances = [ + "I would like to order {FlowerType}", + ] + + slot_constraint = "Required" + slot_type = "AMAZON.TIME" + slot_type_version = "$$LATEST" + + value_elicitation_prompt { + max_attempts = 2 + + message { + content = "Pick up the {FlowerType} at what time on {PickupDate}?" + content_type = "PlainText" + } + } + } +} +``` + +## Argument Reference + +The following arguments are supported: + +* `conclusion_statement` - (Optional) The statement that you want Amazon Lex to convey to the user +after the intent is successfully fulfilled by the Lambda function. + +This element is relevant only if you provide a Lambda function in the `fulfillment_activity`. If you +return the intent to the client application, you can't specify this element. + +The `follow_up_prompt` and `conclusion_statement` are mutually exclusive. You can specify only one. + +Attributes are documented under [statement](#statement). + +* `confirmation_prompt` - (Optional) Prompts the user to confirm the intent. This question should +have a yes or no answer. You you must provide both the `rejection_statement` and `confirmation_prompt`, +or neither. Attributes are documented under [prompt](#prompt-1). +* `create_version` - (Optional) Determines if a new slot type version is created when the initial +resource is created and on each update. Defaults to true. +* `description` - (Optional) A description of the intent. +* `dialog_code_hook` - (Optional) Specifies a Lambda function to invoke for each user input. You can +invoke this Lambda function to personalize user interaction. Attributes are documented under [code_hook](#code_hook-1). +* `follow_up_prompt` - (Optional) Amazon Lex uses this prompt to solicit additional activity after +fulfilling an intent. For example, after the OrderPizza intent is fulfilled, you might prompt the +user to order a drink. + +The `follow_up_prompt` field and the `conclusion_statement` field are mutually exclusive. You can +specify only one. + +Attributes are documented under [follow_up_prompt](#follow_up_prompt-1). + +* `fulfillment_activity` - (Optional) Describes how the intent is fulfilled. For example, after a +user provides all of the information for a pizza order, `fulfillment_activity` defines how the bot +places an order with a local pizza store. + +Attributes are documented under [fulfillment_activity](#fulfillment_activity-1). + +* `name` - (Required) The name of the intent, not case sensitive. +* `parent_intent_signature` - (Optional) A unique identifier for the built-in intent to base this +intent on. To find the signature for an intent, see +[Standard Built-in Intents](https://developer.amazon.com/public/solutions/alexa/alexa-skills-kit/docs/built-in-intent-ref/standard-intents) +in the Alexa Skills Kit. +* `rejection_statement` - (Optional) When the user answers "no" to the question defined in +`confirmation_prompt`, Amazon Lex responds with this statement to acknowledge that the intent was +canceled. + +You must provide both the `rejection_statement` and the `confirmation_prompt`, or neither. + +Attributes are documented under [statement](#statement). + +* `sample_utterances` - (Optional) An array of utterances (strings) that a user might say to signal +the intent. For example, "I want {PizzaSize} pizza", "Order {Quantity} {PizzaSize} pizzas". + +In each utterance, a slot name is enclosed in curly braces. + +* `slot` - (Optional) An list of intent slots. At runtime, Amazon Lex elicits required slot values +from the user using prompts defined in the slots. Attributes are documented under [slot](#slot-1). + +### code_hook + +Specifies a Lambda function that verifies requests to a bot or fulfills the user's request to a bot. + +* `message_version` - (Required) The version of the request-response that you want Amazon Lex to use +to invoke your Lambda function. For more information, see +[Using Lambda Functions](https://docs.aws.amazon.com/lex/latest/dg/using-lambda.html). +* `uri` - (Required) The Amazon Resource Name (ARN) of the Lambda function. + +### follow_up_prompt + +A prompt for additional activity after an intent is fulfilled. For example, after the OrderPizza +intent is fulfilled, you might prompt the user to find out whether the user wants to order drinks. + +* `prompt` - (Required) Prompts for information from the user. Attributes are documented under [prompt](#prompt-1). +* `rejectionStatement` - (Optional) If the user answers "no" to the question defined in the prompt field, +Amazon Lex responds with this statement to acknowledge that the intent was canceled. Attributes are +documented below under [statement](#statement). + +### fulfillment_activity + +Describes how the intent is fulfilled after the user provides all of the information required for the intent. + +* `type` - (Required) How the intent should be fulfilled, either by running a Lambda function or by +returning the slot data to the client application. +* `code_hook` - (Optional) A description of the Lambda function that is run to fulfill the intent. +Required if type is CodeHook. Attributes are documented under [code_hook](#code_hook-1). + +### message + +The message object that provides the message text and its type. + +* `content` - (Required) The text of the message. +* `content_type` - (Required) The content type of the message string. +* `group_number` - (Optional) Identifies the message group that the message belongs to. When a group +is assigned to a message, Amazon Lex returns one message from each group in the response. + +### prompt + +Obtains information from the user. To define a prompt, provide one or more messages and specify the +number of attempts to get information from the user. If you provide more than one message, Amazon +Lex chooses one of the messages to use to prompt the user. + +* `max_attempts` - (Required) The number of times to prompt the user for information. +* `message` - (Required) A set of messages, each of which provides a message string and its type. +You can specify the message string in plain text or in Speech Synthesis Markup Language (SSML). +Attributes are documented under [message](#message-1). +* `response_card` - (Optional) The response card. Amazon Lex will substitute session attributes and +slot values into the response card. For more information, see +[Example: Using a Response Card](https://docs.aws.amazon.com/lex/latest/dg/ex-resp-card.html). + +### slot + +Identifies the version of a specific slot. + +* `name` - (Required) The name of the intent slot that you want to create. The name is case sensitive. +* `slot_constraint` - (Required) Specifies whether the slot is required or optional. +* `description` - (Optional) A description of the bot. +* `priority` - (Optional) Directs Lex the order in which to elicit this slot value from the user. +For example, if the intent has two slots with priorities 1 and 2, AWS Lex first elicits a value for +the slot with priority 1. + +If multiple slots share the same priority, the order in which Lex elicits values is arbitrary. + +* `response_card` - (Optional) The response card. Amazon Lex will substitute session attributes and +slot values into the response card. For more information, see +[Example: Using a Response Card](https://docs.aws.amazon.com/lex/latest/dg/ex-resp-card.html). +* `sample_utterances` - (Optional) If you know a specific pattern with which users might respond to +an Amazon Lex request for a slot value, you can provide those utterances to improve accuracy. This +is optional. In most cases, Amazon Lex is capable of understanding user utterances. +* `slot_type` - (Optional) The type of the slot, either a custom slot type that you defined or one of +the built-in slot types. +* `slot_type_version` - (Optional) The version of the slot type. +* `value_elicitation_prompt` - (Optional) The prompt that Amazon Lex uses to elicit the slot value +from the user. Attributes are documented under [prompt](#prompt-1). + +### statement + +A statement is a map with a set of message maps and an optional response card string. Messages +convey information to the user. At runtime, Amazon Lex selects the message to convey. + +* `message` - (Required) A set of messages, each of which provides a message string and its type. +You can specify the message string in plain text or in Speech Synthesis Markup Language (SSML). +Attributes are documented under [message](#message-1). +* `response_card` - (Optional) The response card. Amazon Lex will substitute session attributes and +slot values into the response card. For more information, see +[Example: Using a Response Card](https://docs.aws.amazon.com/lex/latest/dg/ex-resp-card.html). + +### Timeouts + +The `timeouts` block allows you to specify [timeouts](https://www.terraform.io/docs/configuration/resources.html#timeouts) for certain actions: + +* `create` - (Defaults to 1 min) Used when creating the intent +* `update` - (Defaults to 1 min) Used when updating the intent +* `delete` - (Defaults to 5 mins) Used when deleting the intent + + +## Attributes Reference + +The following attributes are exported in addition to the arguments listed above: + +* `arn` - The ARN of the Lex intent. +* `checksum` - Checksum identifying the version of the intent that was created. The checksum is not +included as an argument because the resource will add it automatically when updating the intent. +* `created_date` - The date when the intent version was created. +* `last_updated_date` - The date when the $LATEST version of this intent was updated. +* `version` - The version of the bot. + +## Import + +Intents can be imported using their name. + +``` +$ terraform import aws_lex_intent.order_flowers_intent OrderFlowers +``` diff --git a/website/docs/r/lex_slot_type.html.markdown b/website/docs/r/lex_slot_type.html.markdown index aa794b04f4b..e62e578f3f8 100644 --- a/website/docs/r/lex_slot_type.html.markdown +++ b/website/docs/r/lex_slot_type.html.markdown @@ -3,7 +3,7 @@ subcategory: "Lex" layout: "aws" page_title: "AWS: aws_lex_slot_type" description: |- - Provides details about a specific Amazon Lex Slot Type + Provides details about a specific Amazon Lex Slot Type --- # Resource: aws_lex_slot_type