From fd1181afa9efcb3f02f4c8f37f4c68f51cecf037 Mon Sep 17 00:00:00 2001 From: Seth <18116602+sethkor@users.noreply.github.com> Date: Tue, 15 Sep 2020 21:29:25 +1000 Subject: [PATCH 1/4] Tidy up status reporting when a new bot is built --- lexbelt.go | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/lexbelt.go b/lexbelt.go index dd94ca1..bf11bc2 100644 --- a/lexbelt.go +++ b/lexbelt.go @@ -159,17 +159,16 @@ func putBot(svc *lexmodelbuildingservice.LexModelBuildingService) { checkError(err) //loop and poll the status - if !*dontWait { - getResult.Status = putResult.Status + currentStatus := *putResult.Status + fmt.Print(currentStatus) for { - fmt.Println(*getResult.Status) - - if *getResult.Status == "READY" { + if currentStatus == "READY" { + fmt.Println() break - } else if *getResult.Status == "FAILED" { - fmt.Println(*getResult.FailureReason) + } else if currentStatus == "FAILED" { + fmt.Printf("\n%s\n", *getResult.FailureReason) break } @@ -179,6 +178,13 @@ func putBot(svc *lexmodelbuildingservice.LexModelBuildingService) { Name: myBot.Name, VersionOrAlias: aws.String("$LATEST"), }) + + if currentStatus != *getResult.Status { + currentStatus = *getResult.Status + fmt.Printf("\n" + currentStatus) + } else { + fmt.Print(".") + } } } } From e4d9ab8ae1c47149975f5eab2dbf5188be713eba Mon Sep 17 00:00:00 2001 From: Seth <18116602+sethkor@users.noreply.github.com> Date: Wed, 16 Sep 2020 10:01:56 +1000 Subject: [PATCH 2/4] Change yaml definition in preperation for specifying a mono bot --- bot.go | 71 +++++++++ examples/yaml/bots/AskTime.yaml | 27 ++-- examples/yaml/bots/OrderFlowersBot.yaml | 37 ++--- examples/yaml/bots/TalkingClock.yaml | 41 ++--- examples/yaml/config.yaml | 2 + examples/yaml/intents/AskDay.yaml | 27 ++-- examples/yaml/intents/AskTime.yaml | 25 +-- examples/yaml/intents/OrderFlowers.yaml | 101 ++++++------ examples/yaml/slots/FlowerTypes.yaml | 21 +-- intent.go | 39 +++++ lexbelt.go | 200 +++++------------------- slot.go | 39 +++++ utils.go | 55 +++++++ 13 files changed, 389 insertions(+), 296 deletions(-) create mode 100644 bot.go create mode 100644 examples/yaml/config.yaml create mode 100644 intent.go create mode 100644 slot.go create mode 100644 utils.go diff --git a/bot.go b/bot.go new file mode 100644 index 0000000..41e43f1 --- /dev/null +++ b/bot.go @@ -0,0 +1,71 @@ +package main + +import ( + "fmt" + "log" + "time" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/lexmodelbuildingservice" +) + +type botYaml struct { + LexBot *lexmodelbuildingservice.PutBotInput `locationName:"lexBot" type:"structure"` +} + +func putBot(svc *lexmodelbuildingservice.LexModelBuildingService) { + + var myBot botYaml + readAndUnmarshal((*putBotCommandFile).Name(), &myBot) + + if myBot.LexBot == nil { + log.Fatal("Yaml file is not as expected, please check your syntax.") + } + + if *putBotCommandName != "" { + myBot.LexBot.Name = putBotCommandName + } + + getResult, err := svc.GetBot(&lexmodelbuildingservice.GetBotInput{ + Name: myBot.LexBot.Name, + VersionOrAlias: aws.String("$LATEST"), + }) + + checkError(err) + + myBot.LexBot.Checksum = getResult.Checksum + + putResult, err := svc.PutBot(myBot.LexBot) + + checkError(err) + + //loop and poll the status + if !*dontWait { + currentStatus := *putResult.Status + fmt.Print(currentStatus) + for { + + if currentStatus == "READY" { + fmt.Println() + break + } else if currentStatus == "FAILED" { + fmt.Printf("\n%s\n", *getResult.FailureReason) + break + } + + time.Sleep((time.Duration(*poll) * time.Second)) + + getResult, err = svc.GetBot(&lexmodelbuildingservice.GetBotInput{ + Name: myBot.LexBot.Name, + VersionOrAlias: aws.String("$LATEST"), + }) + + if currentStatus != *getResult.Status { + currentStatus = *getResult.Status + fmt.Printf("\n" + currentStatus) + } else { + fmt.Print(".") + } + } + } +} diff --git a/examples/yaml/bots/AskTime.yaml b/examples/yaml/bots/AskTime.yaml index 4c74f16..aff60cc 100644 --- a/examples/yaml/bots/AskTime.yaml +++ b/examples/yaml/bots/AskTime.yaml @@ -1,13 +1,14 @@ -name: Talking Clock -description: A Lex bot that tells you the time -locale: en-US -voiceId: Salli -childDirected: false -idleSessionTTLInSeconds: 600 -intents: - - intentName: AskTime - intentVersion: $LATEST -abortStatement: - messages: - - content: Sorry, I can't help you right now - contentType: PlainText \ No newline at end of file +lexBot: + name: Talking Clock + description: A Lex bot that tells you the time + locale: en-US + voiceId: Salli + childDirected: false + idleSessionTTLInSeconds: 600 + intents: + - intentName: AskTime + intentVersion: $LATEST + abortStatement: + messages: + - content: Sorry, I can't help you right now + contentType: PlainText \ No newline at end of file diff --git a/examples/yaml/bots/OrderFlowersBot.yaml b/examples/yaml/bots/OrderFlowersBot.yaml index ae53906..baac805 100644 --- a/examples/yaml/bots/OrderFlowersBot.yaml +++ b/examples/yaml/bots/OrderFlowersBot.yaml @@ -1,18 +1,19 @@ -intents: - - intentVersion: $LATEST - intentName: OrderFlowers -name: OrderFlowersBot -locale: en-US -abortStatement: - messages: - - content: Sorry, I'm not able to assist at this time - contentType: PlainText -clarificationPrompt: - maxAttempts: 2 - messages: - - content: I didn't understand you, what would you like to do? - contentType: PlainText -voiceId: Salli -childDirected: false -idleSessionTTLInSeconds: 600 -description: Bot to order flowers on the behalf of a user +lexBot: + intents: + - intentVersion: $LATEST + intentName: OrderFlowers + name: OrderFlowersBot + locale: en-US + abortStatement: + messages: + - content: Sorry, I'm not able to assist at this time + contentType: PlainText + clarificationPrompt: + maxAttempts: 2 + messages: + - content: I didn't understand you, what would you like to do? + contentType: PlainText + voiceId: Salli + childDirected: false + idleSessionTTLInSeconds: 600 + description: Bot to order flowers on the behalf of a user diff --git a/examples/yaml/bots/TalkingClock.yaml b/examples/yaml/bots/TalkingClock.yaml index 6b344d3..5eb6f25 100644 --- a/examples/yaml/bots/TalkingClock.yaml +++ b/examples/yaml/bots/TalkingClock.yaml @@ -1,20 +1,21 @@ -name: Another Clock -description: A Lex bot that tells you the time -locale: en-US -voiceId: Salli -childDirected: false -idleSessionTTLInSeconds: 600 -intents: - - intentName: AskTime - intentVersion: $LATEST - - intentName: AskDay - intentVersion: $LATEST -clarificationPrompt: - maxAttempts: 3 - messages: - - content: Sorry, I don't understand what you would like to do - contentType: PlainText -abortStatement: - messages: - - content: Sorry, I can't help you right now - contentType: PlainText \ No newline at end of file +lexBot: + name: Another Clock + description: A Lex bot that tells you the time + locale: en-US + voiceId: Salli + childDirected: false + idleSessionTTLInSeconds: 600 + intents: + - intentName: AskTime + intentVersion: $LATEST + - intentName: AskDay + intentVersion: $LATEST + clarificationPrompt: + maxAttempts: 3 + messages: + - content: Sorry, I don't understand what you would like to do + contentType: PlainText + abortStatement: + messages: + - content: Sorry, I can't help you right now + contentType: PlainText \ No newline at end of file diff --git a/examples/yaml/config.yaml b/examples/yaml/config.yaml new file mode 100644 index 0000000..711d61e --- /dev/null +++ b/examples/yaml/config.yaml @@ -0,0 +1,2 @@ +region: ap-southeast-2 +profile: versent \ No newline at end of file diff --git a/examples/yaml/intents/AskDay.yaml b/examples/yaml/intents/AskDay.yaml index f21652e..6fcaf31 100644 --- a/examples/yaml/intents/AskDay.yaml +++ b/examples/yaml/intents/AskDay.yaml @@ -1,13 +1,14 @@ -name: AskDay -description: Intent to ask what the day is -version: 1 -sampleUtterances: - - 'Whats the day' - - 'What day is it' - - 'Day please' - - 'Four' -fulfillmentActivity: - codeHook: - uri: arn:aws:lambda:us-west-2:166330533654:function:LexLambdaSampleFulfilment - messageVersion: "1.0" - type: CodeHook +lexIntent: + name: AskDay + description: Intent to ask what the day is + version: 1 + sampleUtterances: + - 'Whats the day' + - 'What day is it' + - 'Day please' + - 'Four' + fulfillmentActivity: + codeHook: + uri: arn:aws:lambda:us-west-2:166330533654:function:LexLambdaSampleFulfilment + messageVersion: "1.0" + type: CodeHook diff --git a/examples/yaml/intents/AskTime.yaml b/examples/yaml/intents/AskTime.yaml index c8759cb..1171534 100644 --- a/examples/yaml/intents/AskTime.yaml +++ b/examples/yaml/intents/AskTime.yaml @@ -1,12 +1,13 @@ -name: AskTime -description: Intent to ask what the time is -version: 1 -sampleUtterances: - - 'Whats the time' - - 'What time is it' - - 'Time please' -fulfillmentActivity: - codeHook: - uri: arn:aws:lambda:ap-southeast-2:293499315857:function:LexLambdaSampleFulfilment - messageVersion: "1.0" - type: CodeHook +lexIntent: + name: AskTime + description: Intent to ask what the time is + version: 1 + sampleUtterances: + - 'Whats the time' + - 'What time is it' + - 'Time please' + fulfillmentActivity: + codeHook: + uri: arn:aws:lambda:ap-southeast-2:293499315857:function:LexLambdaSampleFulfilment + messageVersion: "1.0" + type: CodeHook diff --git a/examples/yaml/intents/OrderFlowers.yaml b/examples/yaml/intents/OrderFlowers.yaml index cbfe874..135f01b 100644 --- a/examples/yaml/intents/OrderFlowers.yaml +++ b/examples/yaml/intents/OrderFlowers.yaml @@ -1,51 +1,52 @@ -name: OrderFlowers -description: Intent to order a bouquet of flowers for pick up -sampleUtterances: - - I would like to pick up flowers - - I would like to order some flowers -slots: - - slotType: FlowerTypes - slotTypeVersion: $LATEST - name: FlowerType - slotConstraint: Required - valueElicitationPrompt: - maxAttempts: 2 - messages: - - content: What type of flowers would you like to order? - contentType: PlainText - priority: 1 - sampleUtterances: - - I would like to order {FlowerType} - description: The type of flowers to pick up - - slotType: AMAZON.DATE - name: PickupDate - slotConstraint: Required - valueElicitationPrompt: - maxAttempts: 2 - messages: - - content: What day do you want the {FlowerType} to be picked up? - contentType: PlainText - priority: 2 - description: The date to pick up the flowers - - slotType: AMAZON.TIME - name: PickupTime - slotConstraint: Required - valueElicitationPrompt: - maxAttempts: 2 - messages: - - content: Pick up the {FlowerType} at what time on {PickupDate}? - contentType: PlainText - priority: 3 - description: The time to pick up the flowers -confirmationPrompt: - maxAttempts: 2 - messages: - - content: Okay, your {FlowerType} will be ready for pickup by {PickupTime} on {PickupDate}. Does this sound okay? - contentType: PlainText -rejectionStatement: - messages: - - content: Okay, I will not place your order. - contentType: PlainText -fulfillmentActivity: - type: ReturnIntent +lexIntent: + name: OrderFlowers + description: Intent to order a bouquet of flowers for pick up + sampleUtterances: + - I would like to pick up flowers + - I would like to order some flowers + slots: + - slotType: FlowerTypes + slotTypeVersion: $LATEST + name: FlowerType + slotConstraint: Required + valueElicitationPrompt: + maxAttempts: 2 + messages: + - content: What type of flowers would you like to order? + contentType: PlainText + priority: 1 + sampleUtterances: + - I would like to order {FlowerType} + description: The type of flowers to pick up + - slotType: AMAZON.DATE + name: PickupDate + slotConstraint: Required + valueElicitationPrompt: + maxAttempts: 2 + messages: + - content: What day do you want the {FlowerType} to be picked up? + contentType: PlainText + priority: 2 + description: The date to pick up the flowers + - slotType: AMAZON.TIME + name: PickupTime + slotConstraint: Required + valueElicitationPrompt: + maxAttempts: 2 + messages: + - content: Pick up the {FlowerType} at what time on {PickupDate}? + contentType: PlainText + priority: 3 + description: The time to pick up the flowers + confirmationPrompt: + maxAttempts: 2 + messages: + - content: Okay, your {FlowerType} will be ready for pickup by {PickupTime} on {PickupDate}. Does this sound okay? + contentType: PlainText + rejectionStatement: + messages: + - content: Okay, I will not place your order. + contentType: PlainText + fulfillmentActivity: + type: ReturnIntent diff --git a/examples/yaml/slots/FlowerTypes.yaml b/examples/yaml/slots/FlowerTypes.yaml index 44d5790..6700661 100644 --- a/examples/yaml/slots/FlowerTypes.yaml +++ b/examples/yaml/slots/FlowerTypes.yaml @@ -1,10 +1,11 @@ -name: Flower Types yaml -description: types of flowers to pick up from a yaml file -enumerationValues: - - value: tulips - synonyms: - - dutch - - holland - - value: lilies - - value: roses - - value: carnations +lextSlot: + name: Flower Types yaml + description: types of flowers to pick up from a yaml file + enumerationValues: + - value: tulips + synonyms: + - dutch + - holland + - value: lilies + - value: roses + - value: carnations diff --git a/intent.go b/intent.go new file mode 100644 index 0000000..c3bd49b --- /dev/null +++ b/intent.go @@ -0,0 +1,39 @@ +package main + +import ( + "log" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/lexmodelbuildingservice" +) + +type intentYaml struct { + LexIntent *lexmodelbuildingservice.PutIntentInput `locationName:"lexIntent" type:"structure"` +} + +func putIntent(svc *lexmodelbuildingservice.LexModelBuildingService) { + + var myIntent intentYaml + readAndUnmarshal((*putIntentCommandFile).Name(), &myIntent) + + if myIntent.LexIntent == nil { + log.Fatal("Yaml file is not as expected, please check your syntax.") + } + if *putIntentCommandName != "" { + myIntent.LexIntent.Name = putIntentCommandName + } + + getResult, err := svc.GetIntent(&lexmodelbuildingservice.GetIntentInput{ + Name: myIntent.LexIntent.Name, + Version: aws.String("$LATEST"), + }) + + checkError(err) + + myIntent.LexIntent.Checksum = getResult.Checksum + + _, err = svc.PutIntent(myIntent.LexIntent) + + checkError(err) + +} diff --git a/lexbelt.go b/lexbelt.go index bf11bc2..e6ad076 100644 --- a/lexbelt.go +++ b/lexbelt.go @@ -1,19 +1,11 @@ package main import ( - "encoding/json" - "fmt" - "io/ioutil" - "log" "os" - "path/filepath" - "time" "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/aws/awserr" "github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/service/lexmodelbuildingservice" - "github.com/ghodss/yaml" "gopkg.in/alecthomas/kingpin.v2" ) @@ -46,173 +38,61 @@ var ( date = "unknown" ) -func checkError(err error) { - if err != nil { - - if aerr, ok := err.(awserr.Error); ok { - switch aerr.Code() { - - case lexmodelbuildingservice.ErrCodeNotFoundException: - break - default: - log.Fatal(aerr.Error()) - } - } else { - // Print the error, cast err to awserr.Error to get the Code and - // Message from an error. - log.Fatal(err.Error()) - } - - } - -} - -func readAndUnmarshal(fileName string, destination interface{}) { - thefile, err := ioutil.ReadFile(fileName) - - if err != nil { - log.Fatal("reading config file", err.Error()) - } - - switch filepath.Ext(fileName) { - case ".yaml", ".yml": - err = yaml.Unmarshal(thefile, destination) - - default: - err = json.Unmarshal(thefile, destination) - - } - - if err != nil { - log.Fatal("parsing config file", err.Error()) - } - -} - -func putSlotType(svc *lexmodelbuildingservice.LexModelBuildingService) { - var mySlotType lexmodelbuildingservice.PutSlotTypeInput - - readAndUnmarshal((*putSlotTypeCommandFile).Name(), &mySlotType) - - if *putSlotTypeCommandName != "" { - mySlotType.Name = putSlotTypeCommandName - } - getResult, err := svc.GetSlotType(&lexmodelbuildingservice.GetSlotTypeInput{ - Name: mySlotType.Name, - Version: aws.String("$LATEST"), - }) - - checkError(err) - - mySlotType.Checksum = getResult.Checksum - - _, err = svc.PutSlotType(&mySlotType) - - checkError(err) - -} - -func putIntent(svc *lexmodelbuildingservice.LexModelBuildingService) { - - var myIntent lexmodelbuildingservice.PutIntentInput - readAndUnmarshal((*putIntentCommandFile).Name(), &myIntent) - - if *putIntentCommandName != "" { - myIntent.Name = putIntentCommandName - } - - getResult, err := svc.GetIntent(&lexmodelbuildingservice.GetIntentInput{ - Name: myIntent.Name, - Version: aws.String("$LATEST"), - }) - - checkError(err) - - myIntent.Checksum = getResult.Checksum - - _, err = svc.PutIntent(&myIntent) - - checkError(err) - -} - -func putBot(svc *lexmodelbuildingservice.LexModelBuildingService) { - - var myBot lexmodelbuildingservice.PutBotInput - readAndUnmarshal((*putBotCommandFile).Name(), &myBot) - - if *putBotCommandName != "" { - myBot.Name = putBotCommandName - } - - getResult, err := svc.GetBot(&lexmodelbuildingservice.GetBotInput{ - Name: myBot.Name, - VersionOrAlias: aws.String("$LATEST"), - }) - - checkError(err) - - myBot.Checksum = getResult.Checksum - - putResult, err := svc.PutBot(&myBot) - - checkError(err) - - //loop and poll the status - if !*dontWait { - currentStatus := *putResult.Status - fmt.Print(currentStatus) - for { - - if currentStatus == "READY" { - fmt.Println() - break - } else if currentStatus == "FAILED" { - fmt.Printf("\n%s\n", *getResult.FailureReason) - break - } - - time.Sleep((time.Duration(*poll) * time.Second)) - - getResult, err = svc.GetBot(&lexmodelbuildingservice.GetBotInput{ - Name: myBot.Name, - VersionOrAlias: aws.String("$LATEST"), - }) - - if currentStatus != *getResult.Status { - currentStatus = *getResult.Status - fmt.Printf("\n" + currentStatus) - } else { - fmt.Print(".") - } - } - } -} - -func main() { - - app.Version(version) - app.HelpFlag.Short('h') - app.VersionFlag.Short('v') - - command := kingpin.MustParse(app.Parse(os.Args[1:])) - +func getAwsSession() *session.Session { var sess *session.Session if *pProfile != "" { + sess = session.Must(session.NewSessionWithOptions(session.Options{ Profile: *pProfile, SharedConfigState: session.SharedConfigEnable, + Config: aws.Config{ + CredentialsChainVerboseErrors: aws.Bool(true), + MaxRetries: aws.Int(30), + }, })) } else { sess = session.Must(session.NewSessionWithOptions(session.Options{ SharedConfigState: session.SharedConfigEnable, + Config: aws.Config{ + CredentialsChainVerboseErrors: aws.Bool(true), + MaxRetries: aws.Int(30), + }, })) - } //else + if *pRegion != "" { sess.Config.Region = aws.String(*pRegion) } + return sess +} + +func main() { + + app.Version(version) + app.HelpFlag.Short('h') + app.VersionFlag.Short('v') + + command := kingpin.MustParse(app.Parse(os.Args[1:])) + + sess := getAwsSession() + + //var sess *session.Session + //if *pProfile != "" { + // sess = session.Must(session.NewSessionWithOptions(session.Options{ + // Profile: *pProfile, + // SharedConfigState: session.SharedConfigEnable, + // })) + // + //} else { + // sess = session.Must(session.NewSessionWithOptions(session.Options{ + // SharedConfigState: session.SharedConfigEnable, + // })) + // + //} //else + //if *pRegion != "" { + // sess.Config.Region = aws.String(*pRegion) + //} svc := lexmodelbuildingservice.New(sess) diff --git a/slot.go b/slot.go new file mode 100644 index 0000000..abae038 --- /dev/null +++ b/slot.go @@ -0,0 +1,39 @@ +package main + +import ( + "log" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/lexmodelbuildingservice" +) + +type slotYaml struct { + LexSlot *lexmodelbuildingservice.PutSlotTypeInput `locationName:"lexSlot" type:"structure"` +} + +func putSlotType(svc *lexmodelbuildingservice.LexModelBuildingService) { + var mySlotType slotYaml + + readAndUnmarshal((*putSlotTypeCommandFile).Name(), &mySlotType) + + if mySlotType.LexSlot == nil { + log.Fatal("Yaml file is not as expected, please check your syntax.") + } + + if *putSlotTypeCommandName != "" { + mySlotType.LexSlot.Name = putSlotTypeCommandName + } + getResult, err := svc.GetSlotType(&lexmodelbuildingservice.GetSlotTypeInput{ + Name: mySlotType.LexSlot.Name, + Version: aws.String("$LATEST"), + }) + + checkError(err) + + mySlotType.LexSlot.Checksum = getResult.Checksum + + _, err = svc.PutSlotType(mySlotType.LexSlot) + + checkError(err) + +} diff --git a/utils.go b/utils.go new file mode 100644 index 0000000..c8ff456 --- /dev/null +++ b/utils.go @@ -0,0 +1,55 @@ +package main + +import ( + "encoding/json" + "io/ioutil" + "log" + "path/filepath" + + "github.com/aws/aws-sdk-go/aws/awserr" + "github.com/aws/aws-sdk-go/service/lexmodelbuildingservice" + "github.com/ghodss/yaml" +) + +func checkError(err error) { + if err != nil { + + if aerr, ok := err.(awserr.Error); ok { + switch aerr.Code() { + + case lexmodelbuildingservice.ErrCodeNotFoundException: + break + default: + log.Fatal(aerr.Error()) + } + } else { + // Print the error, cast err to awserr.Error to get the Code and + // Message from an error. + log.Fatal(err.Error()) + } + + } + +} + +func readAndUnmarshal(fileName string, destination interface{}) { + thefile, err := ioutil.ReadFile(fileName) + + if err != nil { + log.Fatal("reading config file", err.Error()) + } + + switch filepath.Ext(fileName) { + case ".yaml", ".yml": + err = yaml.Unmarshal(thefile, destination) + + default: + err = json.Unmarshal(thefile, destination) + + } + + if err != nil { + log.Fatal("parsing config file", err.Error()) + } + +} From 250a5299ccbb00317dce4df1b84116bf50061ecd Mon Sep 17 00:00:00 2001 From: Seth <18116602+sethkor@users.noreply.github.com> Date: Wed, 16 Sep 2020 18:59:27 +1000 Subject: [PATCH 3/4] Finish testing of monobot, we have a fairly stable release now --- README.md | 57 +++++++++++++++++- bot.go | 8 ++- examples/yaml/monobot/OrderFlowersMono.yaml | 6 ++ examples/yaml/slots/FlowerTypes.yaml | 4 +- intent.go | 4 +- lexbelt.go | 38 +++++------- provisioner.go | 64 +++++++++++++++++++++ slot.go | 4 +- 8 files changed, 148 insertions(+), 37 deletions(-) create mode 100644 examples/yaml/monobot/OrderFlowersMono.yaml create mode 100644 provisioner.go diff --git a/README.md b/README.md index 2c867a4..6d8bd61 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ Amazon Lex is amazing, except it has very low automation. There's no CloudForma Also, when trying to create or update anything, the CLI wants you to pass in a checksum so it can figure out if it needs to update or create, this can be annoying. -Lexbelt fixes all this. +`lexbelt` fixes all this. ``` usage: lexbelt [] [ ...] @@ -16,7 +16,6 @@ Flags: -h, --help Show context-sensitive help (also try --help-long and --help-man). --profile=PROFILE AWS credentials/config file profile to use --region=REGION AWS region - --verbose Verbose Logging - not implemented yet -v, --version Show application version. Commands: @@ -31,4 +30,56 @@ Commands: put-bot --name=NAME [] Adds or updates a bot -``` \ No newline at end of file + + provision [] + Provisions and builds an entire Lex bot including slots, intents and the actual bot +``` +###Getting lexbelt +Easiest way to install if you're on a Mac or Linux (amd64 or arm64) is to use [Homebrew](https://brew.sh/) + +Type: + +``` +brew tap sethkor/tap +brew install lexbelt +``` + +For other platforms take a look at the releases in Github. I build binaries for: + +|OS | Architecture | +|:------------ |:-------------------------------------- | +|Mac (Darwin) | amd64 (aka x86_64) | +|Linux | amd64, arm64, 386 (32 bit) | +|Windows | amd64, 386 (32 bit) | + +Let me know if you would like a particular os/arch binary regularly built. + +###Monobots +A monobot will provision everything you need for a lex bot. This includes the slots, intents and the bot plus it will build it +lexbelt expects your lex yaml files in the following directory structure for a mono bot to be provisioned. +``` +your-lex-workspace + ├──slots + ├──intents + ├──bots + └──monobot +``` + +You can take a look at examples/yaml/monobot/OrderFlowersMono.yaml to see an example monobot yaml file like so: +``` +LexBotProvisioner: + lexBotName: OrderFlowersOnceMore + lexBot: OrderFlowersBot.yaml + lexIntent: OrderFlowers.yaml + lexSlot: + - FlowerTypes.yaml +``` + +The yaml/json syntax for slots, intents and bots are all based directly of the AWS API Put API calls, so any attribute +supported by the AWS API will be supported in the API now or in the future can be included in a yaml file + +###TODO +* Test monobot in json format +* Publishing bots +* Any other feature requested. +* Windows Testing \ No newline at end of file diff --git a/bot.go b/bot.go index 41e43f1..ee5d65c 100644 --- a/bot.go +++ b/bot.go @@ -13,10 +13,10 @@ type botYaml struct { LexBot *lexmodelbuildingservice.PutBotInput `locationName:"lexBot" type:"structure"` } -func putBot(svc *lexmodelbuildingservice.LexModelBuildingService) { +func putBot(svc *lexmodelbuildingservice.LexModelBuildingService, file string, poll int) { var myBot botYaml - readAndUnmarshal((*putBotCommandFile).Name(), &myBot) + readAndUnmarshal(file, &myBot) if myBot.LexBot == nil { log.Fatal("Yaml file is not as expected, please check your syntax.") @@ -53,13 +53,15 @@ func putBot(svc *lexmodelbuildingservice.LexModelBuildingService) { break } - time.Sleep((time.Duration(*poll) * time.Second)) + time.Sleep((time.Duration(poll) * time.Second)) getResult, err = svc.GetBot(&lexmodelbuildingservice.GetBotInput{ Name: myBot.LexBot.Name, VersionOrAlias: aws.String("$LATEST"), }) + checkError(err) + if currentStatus != *getResult.Status { currentStatus = *getResult.Status fmt.Printf("\n" + currentStatus) diff --git a/examples/yaml/monobot/OrderFlowersMono.yaml b/examples/yaml/monobot/OrderFlowersMono.yaml new file mode 100644 index 0000000..b16d6a8 --- /dev/null +++ b/examples/yaml/monobot/OrderFlowersMono.yaml @@ -0,0 +1,6 @@ +LexBotProvisioner: + lexBotName: OrderFlowersOnceMore + lexBot: OrderFlowersBot.yaml + lexIntent: OrderFlowers.yaml + lexSlot: + - FlowerTypes.yaml \ No newline at end of file diff --git a/examples/yaml/slots/FlowerTypes.yaml b/examples/yaml/slots/FlowerTypes.yaml index 6700661..dffe6bb 100644 --- a/examples/yaml/slots/FlowerTypes.yaml +++ b/examples/yaml/slots/FlowerTypes.yaml @@ -1,5 +1,5 @@ -lextSlot: - name: Flower Types yaml +lexSlot: + name: MoreFlowerTypes description: types of flowers to pick up from a yaml file enumerationValues: - value: tulips diff --git a/intent.go b/intent.go index c3bd49b..8a82cce 100644 --- a/intent.go +++ b/intent.go @@ -11,10 +11,10 @@ type intentYaml struct { LexIntent *lexmodelbuildingservice.PutIntentInput `locationName:"lexIntent" type:"structure"` } -func putIntent(svc *lexmodelbuildingservice.LexModelBuildingService) { +func putIntent(svc *lexmodelbuildingservice.LexModelBuildingService, file string) { var myIntent intentYaml - readAndUnmarshal((*putIntentCommandFile).Name(), &myIntent) + readAndUnmarshal(file, &myIntent) if myIntent.LexIntent == nil { log.Fatal("Yaml file is not as expected, please check your syntax.") diff --git a/lexbelt.go b/lexbelt.go index e6ad076..8722963 100644 --- a/lexbelt.go +++ b/lexbelt.go @@ -14,7 +14,6 @@ var ( app = kingpin.New("lexbelt", "AWS Lex CLI utilities") pProfile = app.Flag("profile", "AWS credentials/config file profile to use").String() pRegion = app.Flag("region", "AWS region").String() - pVerbose = app.Flag("verbose", "Verbose Logging - not implemented yet").Default("false").Bool() putSlotTypeCommand = app.Command("put-slot-type", "Adds or updates a slot type") putSlotTypeCommandName = putSlotTypeCommand.Flag("name", "Name of Slot Type").Required().String() @@ -25,10 +24,15 @@ var ( putIntentCommandFile = putIntentCommand.Arg("file", "The input specification in json or yaml").Required().File() putBotCommand = app.Command("put-bot", "Adds or updates a bot") - putBotCommandName = putBotCommand.Flag("name", "Name of Intent").Required().String() + putBotCommandName = putBotCommand.Flag("name", "Name of Bot").Required().String() putBotCommandFile = putBotCommand.Arg("file", "The input specification in json or yaml").Required().File() - poll = putBotCommand.Flag("poll", "Poll time").Default("3").Int() + putBotCommandPoll = putBotCommand.Flag("poll", "Poll time").Default("3").Int() dontWait = putBotCommand.Flag("dont-wait", "Don't wait for the build to completed before exiting").Default("false").Bool() + + provisionCommand = app.Command("provision", "Provisions and builds an entire Lex bot including slots, intents and the actual bot") + provisionCommandName = provisionCommand.Flag("name", "Name of Lex Bot to Provision").String() + provisionCommandPoll = provisionCommand.Flag("poll", "Poll time").Default("3").Int() + provisionCommandFile = provisionCommand.Arg("file", "The input specification in json or yaml").Required().File() ) //version variable which can be overidden at computIntentCommandle time @@ -69,40 +73,24 @@ func getAwsSession() *session.Session { func main() { - app.Version(version) + app.Version(version + " " + date + " " + commit) app.HelpFlag.Short('h') app.VersionFlag.Short('v') command := kingpin.MustParse(app.Parse(os.Args[1:])) sess := getAwsSession() - - //var sess *session.Session - //if *pProfile != "" { - // sess = session.Must(session.NewSessionWithOptions(session.Options{ - // Profile: *pProfile, - // SharedConfigState: session.SharedConfigEnable, - // })) - // - //} else { - // sess = session.Must(session.NewSessionWithOptions(session.Options{ - // SharedConfigState: session.SharedConfigEnable, - // })) - // - //} //else - //if *pRegion != "" { - // sess.Config.Region = aws.String(*pRegion) - //} - svc := lexmodelbuildingservice.New(sess) switch command { case putSlotTypeCommand.FullCommand(): - putSlotType(svc) + putSlotType(svc, (*putSlotTypeCommandFile).Name()) case putIntentCommand.FullCommand(): - putIntent(svc) + putIntent(svc, (*putIntentCommandFile).Name()) case putBotCommand.FullCommand(): - putBot(svc) + putBot(svc, (*putBotCommandFile).Name(), *putBotCommandPoll) + case provisionCommand.FullCommand(): + provision(svc, (*provisionCommandFile).Name(), *provisionCommandPoll) } } diff --git a/provisioner.go b/provisioner.go new file mode 100644 index 0000000..56fa93f --- /dev/null +++ b/provisioner.go @@ -0,0 +1,64 @@ +package main + +import ( + "fmt" + "log" + "os" + "path/filepath" + + "github.com/aws/aws-sdk-go/service/lexmodelbuildingservice" +) + +type provisioner struct { + LexBot lexmodelbuildingservice.PutBotInput `locationName:"lexBot" type:"structure"` + LexIntent lexmodelbuildingservice.PutIntentInput `locationName:"lexIntent" type:"structure"` + LexSlot []lexmodelbuildingservice.PutSlotTypeInput `locationName:"lexISlot" type:"structure"` +} + +type provisionerSpecification struct { + LexBotProvisioner struct { + LexBotName *string `locationName:"lexBotName" type:"string"` + LexBot *string `locationName:"lexBot" type:"string"` + LexIntent *string `locationName:"lexIntent" type:"string"` + LexSlot []*string `locationName:"lexSlot" type:"string"` + } `locationName:"lexBotProvisioner" type:"structure"` +} + +func provision(svc *lexmodelbuildingservice.LexModelBuildingService, file string, poll int) { + + var myProvisionerSpecification provisionerSpecification + readAndUnmarshal(file, &myProvisionerSpecification) + + if myProvisionerSpecification.LexBotProvisioner.LexBot == nil || myProvisionerSpecification.LexBotProvisioner.LexIntent == nil { + log.Fatal("Yaml file is not as expected, please check your syntax. You must specify at least a bot and intent") + } + + var myProvisioner provisioner + if *provisionCommandName != "" { + myProvisioner.LexBot.Name = putIntentCommandName + } else { + myProvisioner.LexBot.Name = myProvisionerSpecification.LexBotProvisioner.LexBotName + } + + //based on the mono bot yaml, load slots, intents and the bot and provision in the correct order + + //Slots first. Slots are optional + separator := string(os.PathSeparator) + basePath := filepath.Dir(file) + separator + ".." + separator + if len(myProvisionerSpecification.LexBotProvisioner.LexSlot) > 0 { + fmt.Println("Adding Slots") + //get the slot file. + for _, v := range myProvisionerSpecification.LexBotProvisioner.LexSlot { + putSlotType(svc, basePath+"slots"+separator+*v) + } + } + + //next intent + fmt.Println("Adding the intent") + putIntent(svc, basePath+"intents"+separator+*myProvisionerSpecification.LexBotProvisioner.LexIntent) + + //lastly the bot + fmt.Println("Adding the bot and building it") + putBot(svc, basePath+"bots"+separator+*myProvisionerSpecification.LexBotProvisioner.LexBot, poll) + +} diff --git a/slot.go b/slot.go index abae038..bb6f499 100644 --- a/slot.go +++ b/slot.go @@ -11,10 +11,10 @@ type slotYaml struct { LexSlot *lexmodelbuildingservice.PutSlotTypeInput `locationName:"lexSlot" type:"structure"` } -func putSlotType(svc *lexmodelbuildingservice.LexModelBuildingService) { +func putSlotType(svc *lexmodelbuildingservice.LexModelBuildingService, file string) { var mySlotType slotYaml - readAndUnmarshal((*putSlotTypeCommandFile).Name(), &mySlotType) + readAndUnmarshal(file, &mySlotType) if mySlotType.LexSlot == nil { log.Fatal("Yaml file is not as expected, please check your syntax.") From de1bfdd13693e07ca142e20f5efe1b66ceb9df87 Mon Sep 17 00:00:00 2001 From: Seth <18116602+sethkor@users.noreply.github.com> Date: Wed, 16 Sep 2020 19:13:55 +1000 Subject: [PATCH 4/4] Fix and test json monobot example --- README.md | 3 +- examples/json/bots/OrderFlowersBot.json | 56 ++++---- examples/json/intents/AskTime.json | 26 ++-- examples/json/intents/OrderFlowers.json | 152 ++++++++++---------- examples/json/monobot/OrderFlowersMono.json | 10 ++ examples/json/slots/FlowerTypes.json | 34 ++--- 6 files changed, 149 insertions(+), 132 deletions(-) create mode 100644 examples/json/monobot/OrderFlowersMono.json diff --git a/README.md b/README.md index 6d8bd61..45e6e0d 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ Commands: Adds or updates an intent put-bot --name=NAME [] - Adds or updates a bot + Adds or updates a bo provision [] Provisions and builds an entire Lex bot including slots, intents and the actual bot @@ -79,7 +79,6 @@ The yaml/json syntax for slots, intents and bots are all based directly of the A supported by the AWS API will be supported in the API now or in the future can be included in a yaml file ###TODO -* Test monobot in json format * Publishing bots * Any other feature requested. * Windows Testing \ No newline at end of file diff --git a/examples/json/bots/OrderFlowersBot.json b/examples/json/bots/OrderFlowersBot.json index 9855ef5..1be3bd2 100644 --- a/examples/json/bots/OrderFlowersBot.json +++ b/examples/json/bots/OrderFlowersBot.json @@ -1,31 +1,33 @@ { - "intents": [ - { - "intentVersion": "$LATEST", - "intentName": "OrderFlowers" - } - ], - "name": "OrderFlowersBot", - "locale": "en-US", - "abortStatement": { - "messages": [ + "lexBot" : { + "intents": [ { - "content": "Sorry, I'm not able to assist at this time", - "contentType": "PlainText" + "intentVersion": "$LATEST", + "intentName": "OrderFlowers" } - ] - }, - "clarificationPrompt": { - "maxAttempts": 2, - "messages": [ - { - "content": "I didn't understand you, what would you like to do?", - "contentType": "PlainText" - } - ] - }, - "voiceId": "Salli", - "childDirected": false, - "idleSessionTTLInSeconds": 600, - "description": "Bot to order flowers on the behalf of a user" + ], + "name": "OrderFlowersBot", + "locale": "en-US", + "abortStatement": { + "messages": [ + { + "content": "Sorry, I'm not able to assist at this time", + "contentType": "PlainText" + } + ] + }, + "clarificationPrompt": { + "maxAttempts": 2, + "messages": [ + { + "content": "I didn't understand you, what would you like to do?", + "contentType": "PlainText" + } + ] + }, + "voiceId": "Salli", + "childDirected": false, + "idleSessionTTLInSeconds": 600, + "description": "Bot to order flowers on the behalf of a user" + } } \ No newline at end of file diff --git a/examples/json/intents/AskTime.json b/examples/json/intents/AskTime.json index a9180b6..53a22f2 100644 --- a/examples/json/intents/AskTime.json +++ b/examples/json/intents/AskTime.json @@ -1,19 +1,21 @@ { - "name": "AskTime", - "description": "Intent to ask what the time is", - "version": "1", - "sampleUtterances": [ + "lexIntent" : { + "name": "AskTime", + "description": "Intent to ask what the time is", + "version": "1", + "sampleUtterances": [ "Whats the time", "What time is it", "Time please", "Time" - ], - "slots": [], - "fulfillmentActivity": { - "codeHook": { - "uri": "arn:aws:lambda:us-west-2:166330533654:function:LexLambdaSampleFulfilment", - "messageVersion": "1.0" - }, - "type": "CodeHook" + ], + "slots": [], + "fulfillmentActivity": { + "codeHook": { + "uri": "arn:aws:lambda:ap-southeast-2:293499315857:function:LexLambdaSampleFulfilment", + "messageVersion": "1.0" + }, + "type": "CodeHook" + } } } \ No newline at end of file diff --git a/examples/json/intents/OrderFlowers.json b/examples/json/intents/OrderFlowers.json index 9efc585..543a2a2 100644 --- a/examples/json/intents/OrderFlowers.json +++ b/examples/json/intents/OrderFlowers.json @@ -1,82 +1,84 @@ { - "name": "OrderFlowers", - "description": "Intent to order a bouquet of flowers for pick up", - "sampleUtterances": [ - "I would like to pick up flowers", - "I would like to order some flowers" - ], - "slots": [ - { - "slotType": "FlowerTypes", - "name": "FlowerType", - "slotConstraint": "Required", - "valueElicitationPrompt": { - "maxAttempts": 2, - "messages": [ - { - "content": "What type of flowers would you like to order?", - "contentType": "PlainText" - } - ] - }, - "priority": 1, - "slotTypeVersion": "$LATEST", - "sampleUtterances": [ - "I would like to order {FlowerType}" - ], - "description": "The type of flowers to pick up" - }, - { - "slotType": "AMAZON.DATE", - "name": "PickupDate", - "slotConstraint": "Required", - "valueElicitationPrompt": { - "maxAttempts": 2, - "messages": [ - { - "content": "What day do you want the {FlowerType} to be picked up?", - "contentType": "PlainText" - } - ] - }, - "priority": 2, - "description": "The date to pick up the flowers" - }, - { - "slotType": "AMAZON.TIME", - "name": "PickupTime", - "slotConstraint": "Required", - "valueElicitationPrompt": { - "maxAttempts": 2, - "messages": [ - { - "content": "Pick up the {FlowerType} at what time on {PickupDate}?", - "contentType": "PlainText" - } - ] + "lexIntent" : { + "name": "OrderFlowers", + "description": "Intent to order a bouquet of flowers for pick up", + "sampleUtterances": [ + "I would like to pick up flowers", + "I would like to order some flowers" + ], + "slots": [ + { + "slotType": "FlowerTypes", + "name": "FlowerType", + "slotConstraint": "Required", + "valueElicitationPrompt": { + "maxAttempts": 2, + "messages": [ + { + "content": "What type of flowers would you like to order?", + "contentType": "PlainText" + } + ] + }, + "priority": 1, + "slotTypeVersion": "$LATEST", + "sampleUtterances": [ + "I would like to order {FlowerType}" + ], + "description": "The type of flowers to pick up" }, - "priority": 3, - "description": "The time to pick up the flowers" - } - ], - "confirmationPrompt": { - "maxAttempts": 2, - "messages": [ { - "content": "Okay, your {FlowerType} will be ready for pickup by {PickupTime} on {PickupDate}. Does this sound okay?", - "contentType": "PlainText" - } - ] - }, - "rejectionStatement": { - "messages": [ + "slotType": "AMAZON.DATE", + "name": "PickupDate", + "slotConstraint": "Required", + "valueElicitationPrompt": { + "maxAttempts": 2, + "messages": [ + { + "content": "What day do you want the {FlowerType} to be picked up?", + "contentType": "PlainText" + } + ] + }, + "priority": 2, + "description": "The date to pick up the flowers" + }, { - "content": "Okay, I will not place your order.", - "contentType": "PlainText" + "slotType": "AMAZON.TIME", + "name": "PickupTime", + "slotConstraint": "Required", + "valueElicitationPrompt": { + "maxAttempts": 2, + "messages": [ + { + "content": "Pick up the {FlowerType} at what time on {PickupDate}?", + "contentType": "PlainText" + } + ] + }, + "priority": 3, + "description": "The time to pick up the flowers" } - ] - }, - "fulfillmentActivity": { - "type": "ReturnIntent" + ], + "confirmationPrompt": { + "maxAttempts": 2, + "messages": [ + { + "content": "Okay, your {FlowerType} will be ready for pickup by {PickupTime} on {PickupDate}. Does this sound okay?", + "contentType": "PlainText" + } + ] + }, + "rejectionStatement": { + "messages": [ + { + "content": "Okay, I will not place your order.", + "contentType": "PlainText" + } + ] + }, + "fulfillmentActivity": { + "type": "ReturnIntent" + } } } \ No newline at end of file diff --git a/examples/json/monobot/OrderFlowersMono.json b/examples/json/monobot/OrderFlowersMono.json new file mode 100644 index 0000000..4858686 --- /dev/null +++ b/examples/json/monobot/OrderFlowersMono.json @@ -0,0 +1,10 @@ +{ + "LexBotProvisioner": { + "lexBotName": "OrderFlowersOnceMore", + "lexBot": "OrderFlowersBot.json", + "lexIntent": "OrderFlowers.json", + "lexSlot": [ + "FlowerTypes.json" + ] + } +} \ No newline at end of file diff --git a/examples/json/slots/FlowerTypes.json b/examples/json/slots/FlowerTypes.json index ec6fb07..b051246 100644 --- a/examples/json/slots/FlowerTypes.json +++ b/examples/json/slots/FlowerTypes.json @@ -1,19 +1,21 @@ { - "name": "FlowerTypes", - "description": "Types of flowers to pick up", - "enumerationValues": [ - { - "value": "tulips" - }, - { - "value": "lilies" - }, - { - "value": "roses" - }, - { - "value": "carnations" - } - ] + "lexSlot" : { + "name": "FlowerTypes", + "description": "Types of flowers to pick up", + "enumerationValues": [ + { + "value": "tulips" + }, + { + "value": "lilies" + }, + { + "value": "roses" + }, + { + "value": "carnations" + } + ] + } } \ No newline at end of file