diff --git a/.goreleaser.yml b/.goreleaser.yml index 4ffec21..e3abf55 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -12,6 +12,7 @@ builds: goos: - darwin - linux + - windows goarch: - amd64 - 386 @@ -31,6 +32,7 @@ archives: replacements: darwin: Darwin linux: Linux + windows: Windows # Can be used to change the archive formats for specific GOOSs. # Most common use case is to archive as zip on Windows. @@ -48,92 +50,14 @@ changelog: exclude: - '^docs:' - '^test:' +brews: + - + tap: + owner: sethkor + name: homebrew-tap + folder: Formula + description: "Fast S3 Tools built in GoLang Using Multiparts and Concurrency" + test: | + system "#{bin}/program --version" -brew: - # Name template of the recipe - # Default to project name - name: lexbelt - - # Repository to push the tap to. - github: - owner: sethkor - name: homebrew-tap - - # Template for the url. - # Default is "https://github.com///releases/download/{{ .Tag }}/{{ .ArtifactName }}" - #url_template: "http://github.mycompany.com/foo/bar/releases/{{ .Tag }}/{{ .ArtifactName }}" - - # Allows you to set a custom download strategy. - # Default is empty. - #download_strategy: GitHubPrivateRepositoryReleaseDownloadStrategy - - # Allows you to add a custom require_relative at the top of the formula template - # Default is empty - #custom_require: custom_download_strategy - - # Git author used to commit to the repository. - # Defaults are shown. - commit_author: - name: goreleaserbot - email: goreleaser@carlosbecker.com - - # Folder inside the repository to put the formula. - # Default is the root folder. - folder: Formula - - # Caveats for the user of your binary. - # Default is empty. - #caveats: "How to use this binary" - - # Your app's homepage. - # Default is empty. - homepage: "https://github.com/sethkor/lexbelt/" - - # Your app's description. - # Default is empty. - description: "cli tools for AWS Lex" - - # Setting this will prevent goreleaser to actually try to commit the updated - # formula - instead, the formula file will be stored on the dist folder only, - # leaving the responsibility of publishing it to the user. - # If set to auto, the release will not be uploaded to the homebrew tap - # in case there is an indicator for prerelease in the tag e.g. v1.0.0-rc1 - # Default is false. - #skip_upload: true - - # Custom block for brew. - # Can be used to specify alternate downloads for devel or head releases. - # Default is empty. - #custom_block: | - # head "https://github.com/some/package.git" - # ... - - # Packages your package depends on. - #dependencies: - # - git - # - zsh - - # Packages that conflict with your package. - #conflicts: - # - svn - # - bash - - # Specify for packages that run as a service. - # Default is empty. - #plist: | - # - # ... - - # So you can `brew test` your formula. - # Default is empty. - test: | - system "#{bin}/program --version" - # ... - - # Custom install script for brew. - # Default is 'bin.install "program"'. - #install: | - # bin.install "program" - # ... - diff --git a/Makefile b/Makefile index 3bb4976..8d9f53c 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ mac: - env GOOS=darwin GOARCH=amd64 go build -o lexbelt.mac + env GOOS=darwin GOARCH=amd64 go build -o lexbelt linux: env GOOS=linux GOARCH=amd64 go build -o lexbelt.linux @@ -10,3 +10,6 @@ clean: publish-test: goreleaser --snapshot --skip-publish --rm-dist + +publish: + goreleaser --rm-dist --skip-validate \ No newline at end of file diff --git a/README.md b/README.md index 5312ffe..2c867a4 100644 --- a/README.md +++ b/README.md @@ -1 +1,34 @@ -# lexbelt \ No newline at end of file +# lexbelt +A tool to provision Amazon Lex using YAML or JSON + +Amazon Lex is amazing, except it has very low automation. There's no CloudFormation for it CFN for it. + +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. + +``` +usage: lexbelt [] [ ...] + +AWS Lex CLI utilities + +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: + help [...] + Show help. + + put-slot-type --name=NAME + Adds or updates a slot type + + put-intent --name=NAME + Adds or updates an intent + + put-bot --name=NAME [] + Adds or updates a bot +``` \ No newline at end of file diff --git a/examples/json/bots/OrderFlowersBot.json b/examples/json/bots/OrderFlowersBot.json new file mode 100644 index 0000000..9855ef5 --- /dev/null +++ b/examples/json/bots/OrderFlowersBot.json @@ -0,0 +1,31 @@ +{ + "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" +} \ No newline at end of file diff --git a/examples/json/intents/AskTime.json b/examples/json/intents/AskTime.json new file mode 100644 index 0000000..a9180b6 --- /dev/null +++ b/examples/json/intents/AskTime.json @@ -0,0 +1,19 @@ +{ + "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" + } +} \ No newline at end of file diff --git a/examples/json/intents/OrderFlowers.json b/examples/json/intents/OrderFlowers.json new file mode 100644 index 0000000..9efc585 --- /dev/null +++ b/examples/json/intents/OrderFlowers.json @@ -0,0 +1,82 @@ +{ + "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" + } + ] + }, + "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" + } +} \ No newline at end of file diff --git a/examples/json/slots/FlowerTypes.json b/examples/json/slots/FlowerTypes.json new file mode 100644 index 0000000..c3229f5 --- /dev/null +++ b/examples/json/slots/FlowerTypes.json @@ -0,0 +1,16 @@ +{ + "name": "FlowerTypes", + "description": "Types of flowers to pick up", + "enumerationValues": [ + { + "value": "tulips" + }, + { + "value": "lilies" + }, + { + "value": "roses" + } + ] +} + \ No newline at end of file diff --git a/examples/yaml/bots/AskTime.yaml b/examples/yaml/bots/AskTime.yaml new file mode 100644 index 0000000..4c74f16 --- /dev/null +++ b/examples/yaml/bots/AskTime.yaml @@ -0,0 +1,13 @@ +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/TalkingClock.yaml b/examples/yaml/bots/TalkingClock.yaml new file mode 100644 index 0000000..6b344d3 --- /dev/null +++ b/examples/yaml/bots/TalkingClock.yaml @@ -0,0 +1,20 @@ +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/intents/AskDay.yaml b/examples/yaml/intents/AskDay.yaml new file mode 100644 index 0000000..f21652e --- /dev/null +++ b/examples/yaml/intents/AskDay.yaml @@ -0,0 +1,13 @@ +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 new file mode 100644 index 0000000..c8759cb --- /dev/null +++ b/examples/yaml/intents/AskTime.yaml @@ -0,0 +1,12 @@ +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/go.mod b/go.mod new file mode 100644 index 0000000..1e1fe27 --- /dev/null +++ b/go.mod @@ -0,0 +1,11 @@ +module github.com/sethkor/lexbelt + +go 1.15 + +require ( + github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 // indirect + github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d // indirect + github.com/aws/aws-sdk-go v1.34.22 + github.com/ghodss/yaml v1.0.0 + gopkg.in/alecthomas/kingpin.v2 v2.2.6 +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..d030e6b --- /dev/null +++ b/go.sum @@ -0,0 +1,32 @@ +github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM= +github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d h1:UQZhZ2O0vMHr2cI+DC1Mbh0TJxzA3RcLoMsFw+aXw7E= +github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= +github.com/aws/aws-sdk-go v1.34.22 h1:7V2sKilVVgHqdjbW+O/xaVWYfnmuLwZdF/+6JuUh6Cw= +github.com/aws/aws-sdk-go v1.34.22/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0= +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/jmespath/go-jmespath v0.3.0 h1:OS12ieG61fsCg5+qLJ+SsW9NicxNkg3b25OyT2yCeUc= +github.com/jmespath/go-jmespath v0.3.0/go.mod h1:9QtRXoHjLGCJ5IBSaohpXITPlowMeeYCZ7fLUTSywik= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2 h1:CCH4IOTTfewWjGOlSp+zGcjutRKlBEZQ6wTn8ozI/nI= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/lexbelt.go b/lexbelt.go index 4117195..dd94ca1 100644 --- a/lexbelt.go +++ b/lexbelt.go @@ -26,15 +26,15 @@ var ( putSlotTypeCommand = app.Command("put-slot-type", "Adds or updates a slot type") putSlotTypeCommandName = putSlotTypeCommand.Flag("name", "Name of Slot Type").Required().String() - putSlotTypeCommandFile = putSlotTypeCommand.Flag("cli-input-json", "JSON file of Slot Type").Required().URL() + 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.Flag("cli-input-json", "JSON file of Intent").Required().URL() + 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() - putBotCommandFile = putBotCommand.Flag("cli-input-json", "JSON file of Intent").Required().URL() + putBotCommandFile = putBotCommand.Arg("file", "The input specification in json or yaml").Required().File() poll = 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() ) @@ -67,24 +67,19 @@ func checkError(err error) { } -func putSlotType(svc *lexmodelbuildingservice.LexModelBuildingService) { - - var mySlotType lexmodelbuildingservice.PutSlotTypeInput - - file := (*putSlotTypeCommandFile).Path - - thefile, err := ioutil.ReadFile(file) +func readAndUnmarshal(fileName string, destination interface{}) { + thefile, err := ioutil.ReadFile(fileName) if err != nil { log.Fatal("reading config file", err.Error()) } - switch filepath.Ext(file) { + switch filepath.Ext(fileName) { case ".yaml", ".yml": - err = yaml.Unmarshal(thefile, &mySlotType) + err = yaml.Unmarshal(thefile, destination) default: - err = json.Unmarshal(thefile, &mySlotType) + err = json.Unmarshal(thefile, destination) } @@ -92,10 +87,16 @@ func putSlotType(svc *lexmodelbuildingservice.LexModelBuildingService) { 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"), @@ -114,27 +115,7 @@ func putSlotType(svc *lexmodelbuildingservice.LexModelBuildingService) { func putIntent(svc *lexmodelbuildingservice.LexModelBuildingService) { var myIntent lexmodelbuildingservice.PutIntentInput - - file := (*putIntentCommandFile).Path - - thefile, err := ioutil.ReadFile(file) - - if err != nil { - log.Fatal("reading config file", err.Error()) - } - - switch filepath.Ext(file) { - case ".yaml", ".yml": - err = yaml.Unmarshal(thefile, &myIntent) - - default: - err = json.Unmarshal(thefile, &myIntent) - - } - - if err != nil { - log.Fatal("parsing config file", err.Error()) - } + readAndUnmarshal((*putIntentCommandFile).Name(), &myIntent) if *putIntentCommandName != "" { myIntent.Name = putIntentCommandName @@ -158,26 +139,7 @@ func putIntent(svc *lexmodelbuildingservice.LexModelBuildingService) { func putBot(svc *lexmodelbuildingservice.LexModelBuildingService) { var myBot lexmodelbuildingservice.PutBotInput - - file := (*putBotCommandFile).Path - - thefile, err := ioutil.ReadFile(file) - if err != nil { - log.Fatal("reading config file", err.Error()) - } - - switch filepath.Ext(file) { - case ".yaml", ".yml": - err = yaml.Unmarshal(thefile, &myBot) - - default: - err = json.Unmarshal(thefile, &myBot) - - } - - if err != nil { - log.Fatal("parsing config file", err.Error()) - } + readAndUnmarshal((*putBotCommandFile).Name(), &myBot) if *putBotCommandName != "" { myBot.Name = putBotCommandName @@ -217,10 +179,8 @@ func putBot(svc *lexmodelbuildingservice.LexModelBuildingService) { Name: myBot.Name, VersionOrAlias: aws.String("$LATEST"), }) - } } - } func main() {