Skip to content

Commit

Permalink
Merge pull request #9 from sethkor/publish-latest
Browse files Browse the repository at this point in the history
Major rework around provisioning and publishing
  • Loading branch information
sethkor authored Sep 21, 2020
2 parents 8554c8a + cf9b41b commit 1474174
Show file tree
Hide file tree
Showing 14 changed files with 176 additions and 140 deletions.
41 changes: 21 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,8 @@ Commands:
put-bot --name=NAME [<flags>] <file>
Adds or updates a bo
provision [<flags>] <file>
Provisions and builds an entire Lex bot including slots, intents and the actual bot
```
### Getting lexbelt
## 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:
Expand All @@ -54,32 +51,36 @@ For other platforms take a look at the releases in Github. I build binaries for

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
└──monobots
```
When using the provision command for a mono bot, the `--name` flag is optional. If it is not present `lexbelt` will use the `lexBotName` in the monobot specification file.
└──bots
You can take a look at examples/yaml/monobots/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

The tool won't handle mixing json and yaml within a bot (e.g. yaml bot, json intent or slot, etc), pick one and stick with it.

## Lex Bot Odd Behaviour
AWS Lex does do some weird stuff.

### Bot versioning
For instance if your attempt to create a new version of a slot or an intent and
nothing has actually changed, you'll get the last version number returned. Smart. However, if you try to create a new
version of abot and nothing has actualy changed, you'll get a new version number. This inconsistency is a bit annoying.

### 409 ConflictException
Once a new bot is published, there seems to be some asynchronous AWS magik still going on in the background. Any
subsequent request to publish the bot again can trigger a HTTP response 409 ConflictException. Wait a minute and try it
again, it will work. I'm guessing this is related to the build process.

### AWS UI doesn't refresh or is slow
This happens quite often. Some times it requires waiting for the page to load or in the case of an alias, clicking on
other settings before clicking on Aliases again.

### TODO
* Publishing bots
* Any other feature requested.
* Windows Testing
54 changes: 51 additions & 3 deletions bot.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package main
import (
"fmt"
"log"
"os"
"path/filepath"
"time"

"github.com/aws/aws-sdk-go/aws"
Expand All @@ -13,7 +15,18 @@ type botYaml struct {
LexBot *lexmodelbuildingservice.PutBotInput `locationName:"lexBot" type:"structure"`
}

func putBot(svc *lexmodelbuildingservice.LexModelBuildingService, file string, name string, poll int) {
func publishBot(svc *lexmodelbuildingservice.LexModelBuildingService, name string) string {

getResult, err := svc.CreateBotVersion(&lexmodelbuildingservice.CreateBotVersionInput{
Name: aws.String(name),
})

checkError(err)

return *getResult.Version
}

func putBot(svc *lexmodelbuildingservice.LexModelBuildingService, file string, name string, poll int, publish *string) {

var myBot botYaml
readAndUnmarshal(file, &myBot)
Expand All @@ -22,6 +35,17 @@ func putBot(svc *lexmodelbuildingservice.LexModelBuildingService, file string, n
log.Fatal("Yaml file is not as expected, please check your syntax.")
}

if publish != nil {
//publish each intent
separator := string(os.PathSeparator)
basePath := filepath.Dir(file) + separator + ".." + separator
for _, v := range myBot.LexBot.Intents {
intentVersion, err := putIntent(svc, basePath+"intents"+separator+*v.IntentName+filepath.Ext(file), true)
checkError(err)
v.IntentVersion = aws.String(intentVersion)
}
}

if name != "" {
myBot.LexBot.Name = &name
}
Expand All @@ -35,25 +59,49 @@ func putBot(svc *lexmodelbuildingservice.LexModelBuildingService, file string, n

myBot.LexBot.Checksum = getResult.Checksum

if publish != nil {
myBot.LexBot.CreateVersion = aws.Bool(true)
}

putResult, err := svc.PutBot(myBot.LexBot)

checkError(err)

//loop and poll the status
if !*dontWait {
if !*dontWait || publish != nil {
currentStatus := *putResult.Status
fmt.Print(currentStatus)
for {

if currentStatus == "READY" {
fmt.Println()

getAliasResult, err := svc.GetBotAlias(&lexmodelbuildingservice.GetBotAliasInput{
Name: publish,
BotName: myBot.LexBot.Name,
})

checkError(err)
_, err = svc.PutBotAlias(&lexmodelbuildingservice.PutBotAliasInput{
Name: publish,
BotName: myBot.LexBot.Name,
BotVersion: putResult.Version,
Checksum: getAliasResult.Checksum,
})

checkError(err)
fmt.Printf("Bot %s was published as version %s and alias \"%s\".\n",
*myBot.LexBot.Name,
*putResult.Version,
*publish)

break
} else if currentStatus == "FAILED" {
fmt.Printf("\n%s\n", *getResult.FailureReason)
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,
Expand Down
3 changes: 3 additions & 0 deletions constants.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package main

const latestVersion = "$LATEST"
10 changes: 0 additions & 10 deletions examples/json/monobots/OrderFlowersMono.json

This file was deleted.

2 changes: 1 addition & 1 deletion examples/yaml/bots/OrderFlowersBot.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
lexBot:
intents:
- intentVersion: $LATEST
- intentVersion: 1
intentName: OrderFlowers
name: OrderFlowersBot
locale: en-US
Expand Down
2 changes: 2 additions & 0 deletions examples/yaml/intents/OrderFlowers.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ lexIntent:
sampleUtterances:
- I would like to pick up flowers
- I would like to order some flowers
- Flowers please
- Can I have some flowers
slots:
- slotType: FlowerTypes
slotTypeVersion: $LATEST
Expand Down
6 changes: 0 additions & 6 deletions examples/yaml/monobots/OrderFlowersMono.yaml

This file was deleted.

14 changes: 13 additions & 1 deletion examples/yaml/slots/FlowerTypes.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
lexSlot:
name: MoreFlowerTypes
name: FlowerTypes
description: types of flowers to pick up from a yaml file
enumerationValues:
- value: tulips
Expand All @@ -9,3 +9,15 @@ lexSlot:
- value: lilies
- value: roses
- value: carnations
- value: daisy
- value: petuna
- value: lavender
synonyms:
- purple
- value: carnation
synonyms:
- english
- fancy
- fluffy
- value: orchid

46 changes: 41 additions & 5 deletions intent.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
package main

import (
"fmt"
"log"
"os"
"path/filepath"

"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/lexmodelbuildingservice"
Expand All @@ -11,29 +14,62 @@ type intentYaml struct {
LexIntent *lexmodelbuildingservice.PutIntentInput `locationName:"lexIntent" type:"structure"`
}

func putIntent(svc *lexmodelbuildingservice.LexModelBuildingService, file string) {
func publishIntent(svc *lexmodelbuildingservice.LexModelBuildingService, name string) string {

getResult, err := svc.CreateIntentVersion(&lexmodelbuildingservice.CreateIntentVersionInput{
Name: aws.String(name),
})

checkError(err)

return *getResult.Version
}

func putIntent(svc *lexmodelbuildingservice.LexModelBuildingService, file string, publish bool) (string, error) {

var myIntent intentYaml
readAndUnmarshal(file, &myIntent)

if myIntent.LexIntent == nil {
log.Fatal("Yaml file is not as expected, please check your syntax.")
}

if publish {
//For publishing, check the intent to see if it has any slots custom slots. If we find a custom slot and the
//version specified is latest, we must publish the slot and then update this intent put operation with the
//version
separator := string(os.PathSeparator)
basePath := filepath.Dir(file) + separator + ".." + separator
for _, v := range myIntent.LexIntent.Slots {
if v.SlotTypeVersion != nil {
//assume if no version is present, it's a built in type and we don't need to do anything
if *v.SlotTypeVersion == latestVersion {
slotVersion, err := putSlotType(svc, basePath+"slots"+separator+*v.SlotType+filepath.Ext(file), true)
if err != nil {
checkError(err)
}
*v.SlotTypeVersion = slotVersion
}
}
}
}

if *putIntentCommandName != "" {
myIntent.LexIntent.Name = putIntentCommandName
}

getResult, err := svc.GetIntent(&lexmodelbuildingservice.GetIntentInput{
Name: myIntent.LexIntent.Name,
Version: aws.String("$LATEST"),
Version: aws.String(latestVersion),
})

checkError(err)

myIntent.LexIntent.Checksum = getResult.Checksum

_, err = svc.PutIntent(myIntent.LexIntent)

myIntent.LexIntent.CreateVersion = aws.Bool(publish)
result, err := svc.PutIntent(myIntent.LexIntent)
fmt.Printf("Intent %s was published as version %s\n", *myIntent.LexIntent.Name, *result.Version)
checkError(err)
return *result.Version, err

}
43 changes: 19 additions & 24 deletions lexbelt.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,27 +15,24 @@ var (
pProfile = app.Flag("profile", "AWS credentials/config file profile to use").String()
pRegion = app.Flag("region", "AWS region").String()

putSlotTypeCommand = app.Command("put-slot-type", "Adds or updates a slot type")
putSlotTypeCommandName = putSlotTypeCommand.Flag("name", "Name of Slot Type").Required().String()
putSlotTypeCommandFile = putSlotTypeCommand.Arg("file", "The input specification in json or yaml").Required().File()

putIntentCommand = app.Command("put-intent", "Adds or updates an intent")
putIntentCommandName = putIntentCommand.Flag("name", "Name of Intent").Required().String()
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 Bot").Required().String()
putBotCommandFile = putBotCommand.Arg("file", "The input specification in json or yaml").Required().File()
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()
putSlotTypeCommand = app.Command("put-slot-type", "Adds or updates a slot type")
putSlotTypeCommandName = putSlotTypeCommand.Flag("name", "Name of Slot Type").String()
putSlotTypeCommandPublish = putSlotTypeCommand.Flag("publish", "Publish a new version of the slot").Default("false").Bool()
putSlotTypeCommandFile = putSlotTypeCommand.Arg("file", "The input specification in json or yaml").Required().File()

putIntentCommand = app.Command("put-intent", "Adds or updates an intent")
putIntentCommandName = putIntentCommand.Flag("name", "Name of Intent").String()
putIntentCommandPublish = putIntentCommand.Flag("publish", "Publish a new version of the intent").Default("false").Bool()
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 Bot").String()
putBotCommandFile = putBotCommand.Arg("file", "The input specification in json or yaml").Required().File()
putBotCommandPublish = putBotCommand.Flag("publish", "Publish a new version the bot with the provided alias").String()
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()
)

//version variable which can be overidden at computIntentCommandle time
var (
version = "dev-local-version"
commit = "none"
Expand Down Expand Up @@ -84,13 +81,11 @@ func main() {

switch command {
case putSlotTypeCommand.FullCommand():
putSlotType(svc, (*putSlotTypeCommandFile).Name())
putSlotType(svc, (*putSlotTypeCommandFile).Name(), *putSlotTypeCommandPublish)
case putIntentCommand.FullCommand():
putIntent(svc, (*putIntentCommandFile).Name())
putIntent(svc, (*putIntentCommandFile).Name(), *putIntentCommandPublish)
case putBotCommand.FullCommand():
putBot(svc, (*putBotCommandFile).Name(), *putBotCommandName, *putBotCommandPoll)
case provisionCommand.FullCommand():
provision(svc, (*provisionCommandFile).Name(), *provisionCommandName, *provisionCommandPoll)
putBot(svc, (*putBotCommandFile).Name(), *putBotCommandName, *putBotCommandPoll, putBotCommandPublish)
}

}
Loading

0 comments on commit 1474174

Please sign in to comment.